寫在前面: 按照main()函數的代碼一行一行的分析,該是看到了 etimer_process 這個位置。可是etimer_process實現裏的一個宏 PROCESS_YIELD()引出了不少故事,因而單獨把整個宏的東西整理成筆記,貼出來,和學習contiki的夥伴分享。ide
在說這個宏以前,得先記下c 語言的switch()遭遇。函數
switch()從表面上來看,或許應該是很是簡單的問題--C語言的基本功吧。它的使用方式,按照常規來講,以下圖所示:學習
好吧,那就貼一段常規的代碼:測試
1 int main(void) 2 { 3 int k = 1; 4 switch(k) { 5 case 0: printf("good job!\n");break; 6 case 1: printf("hello world!\n");break; 7 default:break; 8 } 9 return 0; 10 }
很規矩的一段switch()的代碼,幾乎是全部C語言書上的教程。輸出結果就再也不說了。Hello world 嘛。spa
再來看一段:code
1 int main(void) 2 { 3 int k = 1; 4 switch(k) { 5 case 0: 6 do{ 7 printf("good job !\n"); 8 case 1: printf("Hello world!\n"); 11 }while(0); 12 }; 13 return 0; 14 }
這段呢? 能編譯經過嗎?能執行嗎?或者,結果如何?blog
在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 彙編