想來想去,還是得先寫一個程式,找下感覺,增強一下自信心,那就國際慣例Hello World吧。先到這個網址下一個Instant Contiki 2.7。之所以沒用3.0的,是因為有些問題,我源碼是下的3.0的。http://sourceforge.net/projects/contiki/files...
想來想去,還是得先寫一個程式,找下感覺,增強一下自信心,那就國際慣例Hello World吧。
先到這個網址下一個Instant Contiki 2.7。之所以沒用3.0的,是因為有些問題,我源碼是下的3.0的。
http://sourceforge.net/projects/contiki/files/Instant%20Contiki/
下完後裝個VMWear,載入Instant Contiki 2.7虛擬機,就可以在Ubuntu上用contiki了。
打開終端,預設是用user用戶名登錄,密碼也是user。ls一下,看見有contiki目錄就對了。接下來在user根目錄下建一個demo目錄用來存放自己的工程,然後在demo目錄下建一個helloworld目錄,然後進去。
建一個hello-world.c文件,輸入如下代碼:
1 #include "contiki.h" 2 #include <stdio.h> 3 PROCESS(HW, "HWP"); 4 AUTOSTART_PROCESSES(&HW); 5 PROCESS_THREAD(HW, ev, data) 6 { 7 PROCESS_BEGIN(); 8 printf("Hello world!\n"); //此處放自己的代碼 9 PROCESS_END(); 10 }
接下來回到user根目錄,然後進入contiki目錄,敲pwd命令,記下當前路徑,等下要用。重新進入helloworld目錄,新建一個Makefile文件,輸入如下代碼:
CONTIKI_PROJECT = hello-world all: $(CONTIKI_PROJECT) /* Contiki源文件根目錄,使用前面記下的路徑 */ CONTIKI = /home/user/contiki include $(CONTIKI)/Makefile.include
準備工作完成,敲入命令make,編譯、生成可執行文件。此處相當坑爹,代碼寫錯幾處,編譯不過,要刪除生成的文件再編譯,折磨死我了。先將就著,以後要換個工具寫代碼。生成完後,如圖所示,生成很多文件。
看到綠色文件沒?執行它,結果如圖所示: 出現Hello World!後程式不會自動退出,這跟在Linux下寫C程式可是不一樣的。按Ctrl+C退出程式。 好,舉杯慶祝邁出了關鍵一步。下麵的大段分析就以此展開。 hello-world.c的代碼真正屬於自己的代碼只有printf語句,其他都是固定格式。也就是說將來寫程式是在PROCESS_BEGIN();和PROCESS_END();之間寫自己的代碼。main()方法呢?main方法是有,不在這裡,不用我們自己寫,習慣就好。 好,先分析第一句代碼 PROCESS(HW, "HWP"); 先看看PROCESS源碼,就在前一篇process結構體上面:1 #if PROCESS_CONF_NO_PROCESS_NAMES 2 #define PROCESS(name, strname) \ 3 PROCESS_THREAD(name, ev, data); \ 4 struct process name = { NULL, \ 5 process_thread_##name } 6 #else 7 #define PROCESS(name, strname) \ 8 PROCESS_THREAD(name, ev, data); \ 9 struct process name = { NULL, strname, \ 10 process_thread_##name }和之前一樣,只考慮有名字的情況,代入實參PROCESS變為: PROCESS(HW, "HWP"); 再代入下麵公式,變為: PROCESS_THREAD(HW, ev, data); \ struct process HW= { NULL, "HWP", process_thread_HW } 接下來看看PROCESS_THREAD的聲明,
1 /** 2 * Define the body of a process. 3 *定義process主體 4 * This macro is used to define the body (protothread) of a 5 * process. The process is called whenever an event occurs in the 6 * system, A process always start with the PROCESS_BEGIN() macro and 7 * end with the PROCESS_END() macro. 8 *此巨集用於定義一個process的主體,當某事件發生時,process被調用。process總是從PROCESS_BEGIN()巨集開始,並結束於 9 *PROCESS_END() 巨集 10 */ 11 #define PROCESS_THREAD(name, ev, data) \ 12 static PT_THREAD(process_thread_##name(struct pt *process_pt, \ 13 process_event_t ev, \ 14 process_data_t data))
越來越複雜了,繼續代入吧 PROCESS_THREAD(HW, ev, data); 變為:
static PT_THREAD(process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data))
還沒完,還得跟蹤PT_THREAD,在Pt.h頭文件中,先看看定義:
#define PT_THREAD(name_args) char name_args
這個……這個上一篇日誌中剛接觸過,用於把一個東西變成函數指針,先代入看看:
static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data)
這回沒變成函數指針,而是一個方法,看來PT_THREAD這個巨集定義專門用來生成函數,它有註釋,看看怎麼說:
* Declaration of a protothread. *聲明一個線程原型 * This macro is used to declare a protothread. All protothreads must * be declared with this macro. *此巨集用於聲明線程原型,所有的線程原型必須通過此巨集聲明 * \param name_args The name and arguments of the C function *參數name_args:C函數的名稱和參數 * implementing the protothread. 小結:一系列動作下來觀察到,PROCESS巨集實際上就是給定一個name參數,此處可看做函數名稱的一部分,生成一個靜態函數,函數返回值為char,名稱為process_thread_+name,函數裡面有三個參數。結構體pt也是第二次碰到了(參考上一篇日誌),就是一個數字。剩餘兩參數後面用到再回來討論。 還沒完,下麵來分析第二句 struct process HW= { NULL, "HWP", process_thread_HW } 這裡初始化了一個process結構體變數HW,上一篇日誌我們已經分析了process結構,再貼上來看看struct process { struct process *next; const char *name; char (* thread)(struct pt *, process_event_t, process_data_t) struct pt pt; unsigned char state; unsigned char needspoll; };結構體HW變數的第一個成員是指向下一元素指針,設為NULL,還未加入鏈表,只是NULL了。 第二個成員表示進程的名稱,這裡為"HWP",這是我們起的名字。 第三個成員,表示一個函數指針,每個process都有一個函數,process執行的就是這個函數,我們看看它的名字:process_thread_HW ,這不正是我們之前通過PT_THREAD展開的函數嘛。 還有三個參數沒賦初值。 好,現在可以做一個總結了: helloworld的第一句代碼PROCESS(HW, "HWP");聲明瞭此進程所對應的函數原型process_thread_HW ,還聲明瞭此進程所對應的process結構體HW。並將函數原型作為HW的成員。 天啊,太複雜了,這才是第一句代碼。 越來越有意思了,有些東西,外面看很抗拒,一旦鑽進去,卻又愛不釋手。C語言一些簡單的語法,能實現C#要非常複雜才能實現的功能。真是不同的世界觀,準確地說,應該是讓在不同的角度去看世界。繼續分析第二句代碼
AUTOSTART_PROCESSES(&HW);
先找到AUTOSTART_PROCESSES定義,在Autostart.h頭文件中
#define AUTOSTART_PROCESSES(...) \
struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
先上網查查__VA_ARGS__是什麼:
__VA_ARGS__ 是一個可變參數的巨集(gcc支持)。實現思想就是巨集定義中參數列表的最後一個參數為省略號(也就是三個點)。這樣預定義巨集_ _VA_ARGS_ _就可以被用在替換部分中,替換省略號所代表的字元串。
經過替換,語句變成如下形式:
struct process * const autostart_processes[] = {&HW, NULL}
這裡聲明瞭一個指針數組,數組裡的每一個指針都指向了一個process,數組聲明的同時初始化了兩個元素,第一個元素是指向HW的指針。回頭看上一篇日誌,HW正是表示當前process的結構體變數。這個數組用來做什麼呢?後面用到再講解。
下麵繼續講解第三條語句:PROCESS_THREAD(HW, ev, data)
PROCESS_THREAD巨集是老朋友了(請閱上一篇日誌),專門用於生成process所要執行的函數原型。代入後的語句如下:
static char process_thread_HW(struct pt *process_pt, \
process_event_t ev, \
process_data_t data)
和上一篇日誌展開後一樣,上一次用於聲明函數原型,這次聲明的是函數主體。
繼續第四條語句:PROCESS_BEGIN();
這個是關鍵,先找找巨集定義,Process.h頭文件中:
/**
* Define the beginning of a process.
*定義process的開始部分
* This macro defines the beginning of a process, and must always
* appear in a PROCESS_THREAD() definition. The PROCESS_END() macro
* must come at the end of the process.
*此巨集用於定義一個process的開始部分,並只能在PROCESS_THREAD() 函數體中定義。在process結尾處必須緊接著定義
*PROCESS_END() 巨集。
*/
#define PROCESS_BEGIN() PT_BEGIN(process_pt)
繼續代入吧,有啥可說的呢,語句變為:
PT_BEGIN(process_pt);
接下來找PT_BEGIN巨集,Pt.h頭文件中,原型如下:
/** * Declare the start of a protothread inside the C function * implementing the protothread. *用於線上程原型函數主體中聲明一個線程的開始部分 * This macro is used to declare the starting point of a * protothread. It should be placed at the start of the function in * which the protothread runs. All C statements above the PT_BEGIN() * invokation will be executed each time the protothread is scheduled. *此巨集放線上程運行的開始部分。線程將會根據執行在PT_BEGIN()中聲明的調用。 * \param pt A pointer to the protothread control structure. * \hideinitializer */ #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((pt)->lc);
代入,語句變為:
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((process_pt)->lc)
整下容,變為
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((process_pt)->lc);
繼續追蹤LC_RESUME巨集:
#define LC_RESUME(s) switch(s) { case 0:
代入上式,最終PROCESS_BEGIN();變成:
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((process_pt)->lc) { case 0:;這……這……這是什麼?半條語句,if (PT_YIELD_FLAG) {;} 意義何在?先不管了,將來有機會弄明白再說吧。 最後,PROCESS_END() 找巨集
#define PROCESS_END() PT_END(process_pt)
再找PT_END
#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(pt); return PT_ENDED; }
最終,語句變為:
LC_END((process_pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(process_pt); return PT_ENDED; } 整下容變成: LC_END((process_pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(process_pt); return PT_ENDED; }
LC_END定義為:
#define LC_END(s) }
PT_INIT定義為:
#define PT_INIT(pt) LC_INIT((pt)->lc)
LC_INIT定義為:
#define LC_INIT(s) s = 0;
PT_ENDED定義為:
#define PT_ENDED 3
一層層代入,最終PROCESS_END()變成:
}
PT_YIELD_FLAG = 0; \
(process_pt)->lc = 0;
return 3;
}
凌亂了,整理下思緒,休息一下把Helloworld.c全部展開看看
腦袋有點不夠用了,慢慢展開吧,看看廬山真面目:
1 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) 2 struct process HW= { NULL, "HWP", process_thread_HW } 3 struct process * const autostart_processes[] = {&HW, NULL} 4 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) 5 { 6 char PT_YIELD_FLAG = 1; 7 if (PT_YIELD_FLAG) 8 {;} 9 switch((process_pt)->lc) 10 { 11 case 0: 12 printf("Hello world!\n"); 13 }; 14 PT_YIELD_FLAG = 0; \ 15 (process_pt)->lc = 0; 16 return 3; 17 }
下麵給代碼加上我自己的理解
1 //聲明一個函數原型,用於process所執行的方法 2 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) 3 //聲明代表進程的結構體,並把之前的函數原型做為其參數代入 4 struct process HW= { NULL, "HWP", process_thread_HW } 5 //聲明一個process的指針數組,用於存放多個process(此程式只有一個),最後放入NULL只是為了方便查找到數組結尾。這 6 //里沒有用鏈表,說明不需要刪除process(個人猜測) 7 struct process * const autostart_processes[] = {&HW, NULL} 8 //函數主體,對應上面的函數原型 9 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) 10 { 11 //由於這個程式沒用到事件,此參數無用,所以下麵三句都是廢話 12 char PT_YIELD_FLAG = 1; 13 if (PT_YIELD_FLAG) 14 {;} 15 //process_pt為函數第一個參數,並無賦值,此時值為0 16 switch((process_pt)->lc) 17 { 18 case 0: 19 printf("Hello world!\n"); //由於process_pt的值為0,所以執行此句 20 }; 21 PT_YIELD_FLAG = 0; //此處無用 22 (process_pt)->lc = 0; //此處無用 23 return 3; //返回PT_ENDED,從字面意義上理解protothread_ended,指示此process已經game over。 24 }
有點凌亂,但也只能如此理解。這個程式只列印一句話,沒用到事件,所以產生了一些無用語句。只能等下次代入事件,看看會不會有什麼新的理解。