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文件,輸入如下代碼:

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

 准備工作完成,敲入命令make,編譯、生成可執行文件。此處相當坑爹,代碼寫錯幾處,編譯不過,要刪除生成的文件再編譯,折磨死我了。先將就着,以后要換個工具寫代碼。生成完后,如圖所示,生成很多文件。

第一個程序: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

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

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 }

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM