Contiki學習筆記  第一個程序:Hello World

想來想去,仍是得先寫一個程序,找下感受,加強一下自信心,那就國際慣例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文件,輸入以下代碼:app

CONTIKI_PROJECT = hello-world
all: $(CONTIKI_PROJECT)
/* Contiki源文件根目錄,使用前面記下的路徑 */
CONTIKI = /home/user/contiki
include $(CONTIKI)/Makefile.include

 準備工做完成,敲入命令make,編譯、生成可執行文件。此處至關坑爹,代碼寫錯幾處,編譯不過,要刪除生成的文件再編譯,折磨死我了。先將就着,之後要換個工具寫代碼。生成完後,如圖所示,生成不少文件。ide

第一個程序:Hello World - 阿巴睇 - 阿巴睇的博客
 看到綠色文件沒?執行它,結果如圖所示:
第一個程序:Hello World - 阿巴睇 - 阿巴睇的博客
 出現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

這個……這個上一篇日誌中剛接觸過,用於把一個東西變成函數指針,先代入看看:this

static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)

這回沒變成函數指針,而是一個方法,看來PT_THREAD這個宏定義專門用來生成函數,它有註釋,看看怎麼說:spa

* 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頭文件中.net

#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 }

有點凌亂,但也只能如此理解。這個程序只打印一句話,沒用到事件,因此產生了一些無用語句。只能等下次代入事件,看看會不會有什麼新的理解。

相關文章
相關標籤/搜索