• 从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
    ");
    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()在宏定义中已经写了  ";"  ,编程中可以写";"也可以不写,不过为了编程风格和理解方便,还是应该写 ";"。

     

  • 相关阅读:
    python3爬取咪咕音乐榜信息(附源代码)
    python3通过Beautif和XPath分别爬取“小猪短租-北京”租房信息,并对比时间效率(附源代码)
    在windows7 32ibt安装MongoDB数据库的方法及连接失败解决方案
    GoWeb开发_Iris框架讲解(一)
    Web项目开发介绍及实战项目介绍
    DAY7学习笔记
    DAY6课下作业
    DAY6学习笔记
    DAY5课下作业
    DAY5学习笔记
  • 原文地址:https://www.cnblogs.com/chineseboy/p/3856296.html
Copyright © 2020-2023  润新知