写在前面: 按照main()函数的代码一行一行的分析,该是看到了 etimer_process 这个位置。但是etimer_process实现里的一个宏 PROCESS_YIELD()引出了很多故事,于是单独把整个宏的东西整理成笔记,贴出来,和学习contiki的伙伴分享。
在说这个宏之前,得先记下c 语言的switch()遭遇。
switch()从表面上来看,或许应该是非常简单的问题--C语言的基本功吧。它的使用方式,按照常规来说,如下图所示:
好吧,那就贴一段常规的代码:
1 int main(void) 2 { 3 int k = 1; 4 switch(k) { 5 case 0: printf("good job! ");break; 6 case 1: printf("hello world! ");break; 7 default:break; 8 } 9 return 0; 10 }
很规矩的一段switch()的代码,几乎是所有C语言书上的教程。输出结果就不再说了。Hello world 嘛。
再来看一段:
1 int main(void) 2 { 3 int k = 1; 4 switch(k) { 5 case 0: 6 do{ 7 printf("good job ! "); 8 case 1: printf("Hello world! "); 11 }while(0); 12 }; 13 return 0; 14 }
这段呢? 能编译通过吗?能执行吗?或者,结果如何?
在QQ群里问了一些,众说纷纭,其中有怀疑者,有否定者,还有猜测者。最难堪的是,有大牛知道这个程序的原理,然后直接让我看C语言书..
在这种鄙视下,果断的使用以下这个命令:
gcc -S test.c
生成了 test.s文件
打开如下:
1 .file "test.c" 2 .section .rodata 3 .LC0: 4 .string "good job !" 5 .LC1: 6 .string "Hello world!" 7 .text 8 .globl main 9 .type main, @function 10 main: 11 .LFB0: 12 .cfi_startproc 13 pushq %rbp 14 .cfi_def_cfa_offset 16 15 .cfi_offset 6, -16 16 movq %rsp, %rbp 17 .cfi_def_cfa_register 6 18 subq $16, %rsp 19 movl $1, -4(%rbp) 20 movl -4(%rbp), %eax 21 testl %eax, %eax 22 je .L3 23 cmpl $1, %eax 24 je .L4 25 jmp .L2 26 .L3: 27 movl $.LC0, %edi 28 call puts 29 .L4: 30 movl $.LC1, %edi 31 call puts 32 .L2: 33 movl $0, %eax 34 leave 35 .cfi_def_cfa 7, 8 36 ret 37 .cfi_endproc 38 .LFE0: 39 .size main, .-main 40 .ident "GCC: (GNU) 4.8.2 20131212 (Red Hat 4.8.2-7)" 41 .section .note.GNU-stack,"",@progbits
好吧,简略的说下这个汇编代码,其中 .LC0 .LC1里面放了我们要打印的字符串。第10行开始是main()的入口地址。然后下面的第26行.L3 和第29行.L4,就是分别打印了.LC0和.LC1里面的字符串。
像C语言一样,从Main()开始看吧 <中间杂乱的就略过了>,
第21行, testl 指令开始测试 %eax是正数负数还是0,然后下一条 je 指令,在汇编中表示相等/为0 的话就跳转到某个地方去,这里就跳转到.L3里面去。
第23行,cmpl 指令,就是检测 %eax 寄存器的值是否为 1,。若是,则跳转到某个地方去,这里就跳转到.L4里面去。
第31行和36行,都call 了 puts函数,其实就是gcc 把printf()偷换成了puts吧。
解释的就这么多,其他无非就是push pop的操作。这是汇编最爱干的事情。
另外就是,上面的解释中,不断的使用了 "跳转" 一词,那么,还有一个关键字,在C语言中也是跳转功能,那就是 goto。
那么,按照上面的思路来说,switch()语句的底层实现,其实就是一个goto原理:先是判断一个值,然后根据这个值跳到某个地方去,这个地方用标签标出来。那么switch(k)这个时候就是在对值进行判断,case 就是那个标签,至于其中goto 被隐含了。一般的人不知道,但是编译器知道---这就足够了。
一言以蔽之: switch() = 判断 + Lable + goto. --->case语句随便写在switch(){}里面的某个位置,都无所谓了(如果没有break 和return的话)。
那么,这还是教科书上的switch()吗? 答案有待商榷。但是,按照这段汇编,我们上面那段怪怪的C语言,结果有了,当K = 1的时候,"Hello world";当k = 0的时候,"good job" "Hello world". 现在是case 0语句里面套了一个case 1语句,若有兴趣,可以在case 1里面再套一个case 2子句,然后把k = 2 看看结果如何....
好吧,switch()就先说在这里。接下来看PROCESS_YIELD()宏的展开。
--------------------------------------------------------------------------------------------------
PROCESS_YIELD()宏
contiki/./core/sys/process.h:
1 #define PROCESS_YIELD() PT_YIELD(process_pt)
PROCESS_YIELD 被 PT_YIELD替换掉:
PT_YIELD(pt)
contiki/./core/sys/pt.h
1 #define PT_YIELD(pt) 2 do { 3 PT_YIELD_FLAG = 0; 4 LC_SET((pt)->lc); 5 if(PT_YIELD_FLAG == 0) { 6 return PT_YIELDED; 7 } 8 } while(0)
先把其中的 LC_SET()宏也打开吧:
contiki/./core/sys/lc-switch.h
1 #define LC_SET(s) s = __LINE__; case __LINE__:
回溯一下,PROCESS_YIELD()宏被替换成这个样子:
1 do { 2 PT_YIELD_FLAG = 0; 3 (process_pt)->lc = __LINE__; 4 case __LINE__: 5 if(PT_YIELD_FLAG == 0) { 6 return PT_YIELDED; 7 } 8 }while(0);
说明:
PT_YIELD_FLAG 这个是在 PROCESS_BEGIN()宏中的产物,一个char 型变量,在PROCESS_BEGIN()中被初始化成了 1。当然在PROCESS_END()里面也有它,并把它置为0了。当然,在PROCESS_YIELD()这里也有它。
(process_pt)->lc = __LINE__; 就是把程序当前行给保存下来了。<但并没有保存现场>
case __LINE__: 这个肯定要和switch()配合用,而且,当switch()的测试值为这个 __LINE__的时候,就跳到这个case里面去执行。
if(PT_YIELD_FLAG == 0) {
return PT_YIELDED;
}
这就没解释的必要了,无非就是在某种条件下,是继续执行程序还是直接返回去了。
但,就上面展开的8行代码,铺陈开了故事的所有脉络....
关键字: contiki switch 汇编