首页 > 代码库 > 从hello-world 开始 <contiki学习之四>

从hello-world 开始 <contiki学习之四>

      按照contiki 官方给出的example下的例子之hello world来说,所有的工程里面都有一个唯一的Makefile。然后这个Makefile会去调用其他makefile文件。于是,一切就从此出发吧。

 

说明: 本文依赖于  contiki-2.6/examples/hello-world/hello-world.c 文件

 

在hello-world.c里面给出的示例非常简单:

 1 #include "contiki.h" 2  3 #include <stdio.h> /* For printf() */ 4 /*---------------------------------------------------------------------------*/ 5 PROCESS(hello_world_process, "Hello world process"); 6 AUTOSTART_PROCESSES(&hello_world_process); 7 /*---------------------------------------------------------------------------*/ 8 PROCESS_THREAD(hello_world_process, ev, data) 9 {10   PROCESS_BEGIN();11 12   printf("Hello, world\n");13   14   PROCESS_END();15 }16 /*---------------------------------------------------------------------------*/

      首先说明,整个contiki OS都是C语言编写,不存在C的扩展语言。比如tinyos 就是采用了C的扩展语言写成的。那么,但凡C语言编程中的一些默认习惯,在contiki里也是存在的。 

      短短的十几行代码,却是充满了各种有趣的大写。按照C编程的习惯,这应该是宏---而最大的可能是定义在某个“.h”d的头文件中。下面将在contiki/  根目录下使用以下这个命令进行查找:

find -name "*.h" | xargs grep "宏名"

比如,查找第一个  "PROCESS":

find -name "*.h" | xargs grep "PROCESS"

即可。

  下面就来追寻一下,这个hello-world.c里面到底写了什么。

PROCESS(hello_world_process, "Hello world process");

这个宏定义在了  contiki/core/sys/process.h 头文件中:

301 #if PROCESS_CONF_NO_PROCESS_NAMES                    //contiki/platform/cc2530dk/contiki-conf.h:#define PROCESS_CONF_NO_PROCESS_NAMES 1302 #define PROCESS(name, strname)              303   PROCESS_THREAD(name, ev, data);           304   struct process name = { NULL,             305                           process_thread_##name }306 #else307 #define PROCESS(name, strname)              308   PROCESS_THREAD(name, ev, data);           309   struct process name = { NULL, strname,        310                           process_thread_##name }311 #endif

如果上面的行与行之间的连接看起啦不舒服,弄成下面这个模式:

1 #if PROCESS_CONF_NO_PROCESS_NAMES2     #define PROCESS(name, strname)  PROCESS_THREAD(name, ev, data); struct process name = { NULL,process_thread_##name }3 #else4     #define PROCESS(name, strname)  PROCESS_THREAD(name, ev, data); struct process name = { NULL, strname,process_thread_##name }5 #endif

于是可以知道 PROCESS()  被  “ PROCESS_THREAD(name, ev, data); struct process name={//TODO} "给替换掉了。

但是又出现了一个 PROCESS_THREAD()的宏,于是使用命令搜索,发现它依然定义在  contiki/core/sys/process.h 头文件中:

1 273 #define PROCESS_THREAD(name, ev, data)              \                                                                                                                                     2 274 static PT_THREAD(process_thread_##name(struct pt *process_pt,   3 275                        process_event_t ev,  4 276                        process_data_t data))

对于我这样的水货程序员,总是不怎么接纳C语言中宏的行连接符号,于是依然转成下面这个格式:

1 #define PROCESS_THREAD(name, ev, data)  static PT_THREAD(process_thread_##name(struct pt *process_pt, process_event_t ev, process_data_t data))

很明了,PROCESS_THREAD()这个宏  又被后面的东西给替换了,而且出现了另外一个宏-- PT_THREAD(),于是继续用命令搜索,发现它定义在 contiki/core/sys/pt.h 这个头文件中:

1 #define PT_THREAD(name_args) char name_args

嗯哼,终于到头了,PT_THREAD()  其实将被替换成一个 char 变量。好吧,contiki作者有才。那么,回朔前面的宏:

PROCESS_THREAD() 这个宏最终就被替换成这个样:

1 #define PROCESS_THREAD(name, ev, data)  static char process_thread_##name(struct pt *process_pt, process_event_t ev, process_data_t data)

没看错,PROCESS_THREAD()被展开成为了一个静态函数,而这个函数名将来自于上面的PROCESS()展开的时候的处理。那么看看PROCESS()这个宏最后展开成了个什么样子:

1 #define PROCESS(name, strname)  static char process_thread_##name(struct pt *process_pt, process_event_t ev, process_data_t data); struct process name = {//TODO}

特别要注意上面的 static char process_thread_##name 中间的 "##" 符号,这个也是C语法基础,目的是将后面的内容连接到前面的尾巴上。

那么,这行带码的展开是什么呢?

1 PROCESS(hello_world_process, "Hello world process");

按照上面的追寻,应该是下面这行代码:

1 static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data); 2 struct process name = {NULL,process_thread_hello_world_process};

oh my god! 这个宏干了什么?! 不言而喻。

那么,char process_thread_hello_world_process()里面几个参数的数据结构,同理可寻,contiki/core/sys/process.h里,最后他们的定义为:

1 59 typedef unsigned char process_event_t;2 60 typedef void *        process_data_t;3 61 typedef unsigned char process_num_events_t;

不错,其实就是char  void  unsigned char 被typedef了。而struct pt{} 则定义在了 contiki/core/sys/pt.h 文件中:

 53 struct pt { 54   lc_t lc; 55 };

继续追:lc_t 这个东西是什么。当然,它定义在了  sys/lc-switch.h 头文件中:

1 typedef unsigned short lc_t;

这里就没必要再回溯了。它就是那么个玩意。

 

追寻完了第一个宏,接着看第二个宏:AUTOSTART_PROCESSES()。依然采用find 命令进行搜索。它定义在 contiki/core/sys/autostart.h 头文件中:

 46 #if AUTOSTART_ENABLE            //  这个可能在Makefile.include 里面以 -D的方式定义了./Makefile.include:    $(Q)$(CC) $(CFLAGS) -DAUTOSTART_ENABLE -c $< -o $@ 47 #define AUTOSTART_PROCESSES(...)                     48 struct process * const autostart_processes[] = {__VA_ARGS__, NULL} 49 #else /* AUTOSTART_ENABLE */ 50 #define AUTOSTART_PROCESSES(...)                     51 extern int _dummy 52 #endif /* AUTOSTART_ENABLE */

整理下:

1 #if AUTOSTART_ENABLE2     #define AUTOSTART_PROCESSES(...)  struct process * const autostart_processes[] = {__VA_ARGS__, NULL}3 #else4     #define AUTOSTART_PROCESSES(...)    extern int _dummy    5 #endif

     其中的__VA_ARGS__ C99 中的一个宏:(可变参数的宏) 总体来说就是将左边AUTOSTART_PROCESSES中 "..." 的内容原样抄写在右边 "__VA_ARGS__" 所在的位置。它是一个可变参数的宏 并非所有编译器都支持这个宏;  _dummy  : 虽然它也是一个int的变量,更多的表示是一个内存单元--- 这只是一种习惯。

    数据结构 struct process{} 定义在了  contiki/core/sys/process.h 头文件中:

315 struct process {316   struct process *next;                                        //  @1 317 #if PROCESS_CONF_NO_PROCESS_NAMES318 #define PROCESS_NAME_STRING(process) ""                        319 #else320   const char *name;                                            // @2 321 #define PROCESS_NAME_STRING(process) (process)->name322 #endif323   PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t));     // @3324   struct pt pt;                                                    325   unsigned char state, needspoll;                                326 };/*************** struct process ********************/

该结构体中的 next 指针一开始就被初始化成了 NULL。 name 指针被赋予了 process_thread_hello_world_process()这个函数的名字。嗯,前面已经写过了。并且,其中还有一个钩子函数。很不好意思的是,这个钩子函数将是我们自己实现,具体如何,且看下文。

那么,AUTOSTART_PROCESSES(&hello_world_process); 这行代码的展开如下:

1 struct process * const autostart_processes[] = { &hello_world_process, NULL};

注意,上面的 struct process * const autostart_processes[] 是一个数组模式--也即是,不一定是单个对象,而是针对多个对象,换句话说,这是否代表可以填进去多个函数地址?某天,自有分晓。

接下来继续分析  PROCESS_BEGIN()这个宏,根据命令的提示,它被定义在 contiki/./core/sys/process.h 头文件中:

1 #define PROCESS_BEGIN()             PT_BEGIN(process_pt)

而PT_BEGIN()这个宏定义在 contiki/./core/sys/pt.h 这个头文件中:

1 #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((pt)->lc)

再跟一步,LC_RESUME()这个宏定义在 contiki/./core/sys/lc-switch.h 这个头文件中:

1 #define LC_RESUME(s) switch(s) { case 0:

那么,我们回溯  PT_BEGIN() 这个宏的展开:

1  { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG){;}switch((process_pt) -> lc) { case 0:

同时,回溯到 PROCESS_BEGIN()的时候,它依然是这样的:最前面有一个花括号,也就是函数体的开始;后面有一个 case 0 然后以 ":" 冒号结尾。当然,这里还有个小问题,就是在 PROCESS_BEGIN();后面有一个分号,展开的时候如何处理的,留待后面考虑。

 

接着来看hello-world.c里最后一个宏 PROCESS_END(),它定义在 contiki/core/sys/process.h头文件中:

 #define PROCESS_END()               PT_END(process_pt)

而PT_END()则定义在 contiki/./core/sys/pt.h头文件中:

1  #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \                                                                                                                                 2                     PT_INIT(pt); return PT_ENDED; }

其实有点烦,它的宏经过几层的封装,LC_END()定义在了contiki/ ./core/sys/lc-switch.h文件中:

1 #define LC_END(s) }

呵呵,写这个宏的作者卖了个萌吗? 把LC_END()展开成了一个  花括号的右半部分?---这是一盘很大的棋?

不管他,继续看其他几个宏:PT_INIT()定义在 contiki/./core/sys/pt.h头文件中:

1 #define PT_INIT(pt)   LC_INIT((pt)->lc)

而LC_INIT()宏则定义在了 contiki/./core/sys/lc-switch.h头文件中:

1 #define LC_INIT(s) s = 0; 

是的,就是让  s为0. 而 PT_ENDED这个东西也是一个宏,它的值为3,也是定义在了  contiki/./core/sys/pt.h头文件中。

回溯一下,看看 PROCESS_END()展开成了什么样式的:

1 };2 PT_YIELD_FLAG = 0; 3 process_pt->lc = 0; 4 return PT_ENDED;5 }

是的,没错,它的第一行展开成了一个   " } ;"  样式的东西。想想C语言中,几种带花括号的情况:函数体带花括号,但是花括号后面不存在一个 结束符 分号";"    循环体带花括号可以,但是带一个结束符,不常见---虽然它可以存在; 条件分支 if () 和前面情况大致相同,那就只有 switch() 这个玩意儿了。它的体外必须要带一个 分号。那么看看这段代码:

1   PROCESS_BEGIN();2 3   printf("Hello, world\n");4   5   PROCESS_END();

展开为:

1     char PT_YIELD_FLAG = 1;2     if (PT_YIELD_FLAG) {;}3     switch((process_pt) -> lc) {4         case 0:5         printf("Hello world!\n");6     };7     PT_YIELD_FLAG = 0;8     process_pt->lc = 0;9     return PT_ENDED;

三行代码就展开成这个样。那么整个hello-world展开为什么样式呢:

 1 static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);  2 struct process name = {NULL,process_thread_hello_world_process}; 3 struct process * const autostart_processes[] = { &hello_world_process, NULL}; 4  5 static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data) 6 { 7     char PT_YIELD_FLAG = 1; 8     if (PT_YIELD_FLAG) {;} 9     switch((process_pt) -> lc) {10         case 0:11         printf("Hello world!\n");12     };13     PT_YIELD_FLAG = 0;14     process_pt->lc = 0;15     return PT_ENDED;16 }

嗯哼,展开了宏,那么这几行代码的逻辑非常简单--算是C语言老师第一堂课的内容吧。

下面来总结一下这些宏分别会出现在哪些头文件中:

以PROCESS_  开头的,那基本都在   contiki/core/sys/process.h头文件中;

以PT_ 开头的, 那基本都在 contiki/./core/sys/pt.h头文件中;

以 LC_ 开头的, 基本都在contiki/./core/sys/lc-switch.h头文件中。

 

 

这些宏的定义与封装,可以看清contiki的某个东西,或者说某个机制--potothread机制。待后面将学习笔记整理出来。

      哦,这里还有一个重要的东西:那就是我们分析了这么久、这么多的宏,既然工程从Makefile存在的目录开始,那么hello-world.c里面应该存在最重要的main(){}才对。但是这里并没有。

     开篇就说过了,这是C语言编写的OS,应用程序怎么可能没有main()呢? 其实,main() 已经存在,它已经被某个makefile所包含了。代码开始从哪里执行的呢?是从那个main(),还是从这个hello-world.c里面的内容开始的呢? 这个结论不好轻易的下,特别是在有全局变量的情况下。那么"Hello world"又什么时候开始打印呢?在什么样的条件下开始打印呢?

hello-world.c里面已经没有给出任何信息了。那么,就只有看看man()函数了--这也符合我自己的习惯:一个工程,先找main()函数。 

ps: 但contiki OS,我一上来还真没找到main()函数,才导致了自己去分析makefile文件---我坚信,某个地方有一个main()。 

闲话就这么多吧。

 

邮件: newleaves@126.com