按照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 "); 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 1 302 #define PROCESS(name, strname) 303 PROCESS_THREAD(name, ev, data); 304 struct process name = { NULL, 305 process_thread_##name } 306 #else 307 #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_NAMES 2 #define PROCESS(name, strname) PROCESS_THREAD(name, ev, data); struct process name = { NULL,process_thread_##name } 3 #else 4 #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_ENABLE 2 #define AUTOSTART_PROCESSES(...) struct process * const autostart_processes[] = {__VA_ARGS__, NULL} 3 #else 4 #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_NAMES 318 #define PROCESS_NAME_STRING(process) "" 319 #else 320 const char *name; // @2 321 #define PROCESS_NAME_STRING(process) (process)->name 322 #endif 323 PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t)); // @3 324 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 }
是的,没错,它的第一行展开成了一个 " } ;" 样式的东西。那么看看这段代码:
1 PROCESS_BEGIN(); 2 3 printf("Hello, world "); 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! "); 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! "); 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()。
闲话就这么多吧。
补充说明:在这个文件中,有的宏后面有一个分号 ";" ,而有的宏没有。根据这样,这个文件的代码也可以写成这样:
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 printf("Hello, world "); 12 PROCESS_END() 13 } 14 /*---------------------------------------------------------------------------*/
其中,PROCESS() AUTOSTART_PROCESSES() 这个俩宏定义中,没有 ";"来替换,在编程的时候,就必须显示的在这宏后面写 ";", 而 PROCESS_BEGIN() PROCESS_END()在宏定义中已经写了 ";" ,编程中可以写";"也可以不写,不过为了编程风格和理解方便,还是应该写 ";"。