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

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

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

 #include "contiki.h"

 #include <stdio.h> /* For printf() */
/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
PROCESS_BEGIN(); printf("Hello, world\n"); PROCESS_END();
}
/*---------------------------------------------------------------------------*/

首先说明,整个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 头文件中:

 #if PROCESS_CONF_NO_PROCESS_NAMES                    //contiki/platform/cc2530dk/contiki-conf.h:#define PROCESS_CONF_NO_PROCESS_NAMES 1
#define PROCESS(name, strname) \
PROCESS_THREAD(name, ev, data); \
struct process name = { NULL, \
process_thread_##name }
#else
#define PROCESS(name, strname) \
PROCESS_THREAD(name, ev, data); \
struct process name = { NULL, strname, \
process_thread_##name }
#endif

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

 #if PROCESS_CONF_NO_PROCESS_NAMES
#define PROCESS(name, strname) PROCESS_THREAD(name, ev, data); struct process name = { NULL,process_thread_##name }
#else
#define PROCESS(name, strname) PROCESS_THREAD(name, ev, data); struct process name = { NULL, strname,process_thread_##name }
#endif

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

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

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

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

 #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 这个头文件中:

 #define PT_THREAD(name_args) char name_args

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

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

 #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()这个宏最后展开成了个什么样子:

 #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语法基础,目的是将后面的内容连接到前面的尾巴上。

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

 PROCESS(hello_world_process, "Hello world process");

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

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

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

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

  typedef unsigned char process_event_t;
typedef void * process_data_t;
typedef unsigned char process_num_events_t;

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

  struct pt {
lc_t lc;
};

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

 typedef unsigned short lc_t;

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

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

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

整理下:

 #if AUTOSTART_ENABLE
#define AUTOSTART_PROCESSES(...) struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
#else
#define AUTOSTART_PROCESSES(...) extern int _dummy
#endif

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

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

 struct process {
struct process *next; // @1
#if PROCESS_CONF_NO_PROCESS_NAMES
#define PROCESS_NAME_STRING(process) ""
#else
const char *name; // @2
#define PROCESS_NAME_STRING(process) (process)->name
#endif
PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t)); // @3
struct pt pt;
unsigned char state, needspoll;
};
/*************** struct process ********************/

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

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

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

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

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

 #define PROCESS_BEGIN()             PT_BEGIN(process_pt)

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

 #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 这个头文件中:

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

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

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

同时,回溯到 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头文件中:

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

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

 #define LC_END(s) }

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

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

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

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

 #define LC_INIT(s) s = 0; 

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

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

 };
PT_YIELD_FLAG = ; \
process_pt->lc = ; \
return PT_ENDED;
}

是的,没错,它的第一行展开成了一个   " } ;"  样式的东西。那么看看这段代码:

   PROCESS_BEGIN();

   printf("Hello, world\n");

   PROCESS_END();

展开为:

     char PT_YIELD_FLAG = ;
if (PT_YIELD_FLAG) {;}
switch((process_pt) -> lc) {
case :
printf("Hello world!\n");
};
PT_YIELD_FLAG = ;
process_pt->lc = ;
return PT_ENDED;

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

 static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);
struct process name = {NULL,process_thread_hello_world_process};
struct process * const autostart_processes[] = { &hello_world_process, NULL}; static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)
{
char PT_YIELD_FLAG = ;
if (PT_YIELD_FLAG) {;}
switch((process_pt) -> lc) {
case :
printf("Hello world!\n");
};
PT_YIELD_FLAG = ;
process_pt->lc = ;
return PT_ENDED;
}

嗯哼,展开了宏,那么这几行代码的逻辑非常简单--算是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()。

闲话就这么多吧。

补充说明:在这个文件中,有的宏后面有一个分号  ";" ,而有的宏没有。根据这样,这个文件的代码也可以写成这样:

 #include "contiki.h"

 #include <stdio.h> /* For printf() */
/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
PROCESS_BEGIN()
printf("Hello, world\n");
PROCESS_END()
}
/*---------------------------------------------------------------------------*/

其中,PROCESS()  AUTOSTART_PROCESSES() 这个俩宏定义中,没有 ";"来替换,在编程的时候,就必须显示的在这宏后面写 ";", 而 PROCESS_BEGIN() PROCESS_END()在宏定义中已经写了  ";"  ,编程中可以写";"也可以不写,不过为了编程风格和理解方便,还是应该写 ";"。

从hello-world 开始 <contiki学习之四>的更多相关文章

  1. 简单的玩玩etimer <contiki学习笔记之九 补充>

    这幅图片是对前面  <<contiki学习笔记之九>>  的一个补充说明. 简单的玩玩etimer <contiki学习笔记之九> 或许,自己正在掀开contiki ...

  2. 简单的玩玩etimer <contiki学习笔记之九>

    好吧,我承认etimer有点小复杂,主要是它似乎和contiki的process搅在一起,到处都在call_process.那就先搜搜contiki下的etimer的example看看,然后再试着写一 ...

  3. [转]Docker学习之四:使用docker安装mysql

    本文转自:https://blog.csdn.net/qq_19348391/article/details/82998391 Docker学习之一:注册Docker Hub账号 Docker学习之二 ...

  4. jackson学习之四:WRAP_ROOT_VALUE(root对象)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. PROCESS_YIELD()宏和C语言的switch语句< contiki学习笔记之七>

    写在前面:  按照main()函数的代码一行一行的分析,该是看到了 etimer_process 这个位置.但是etimer_process实现里的一个宏 PROCESS_YIELD()引出了很多故事 ...

  6. contiki-main.c 中的process系列函数学习笔记 <contiki学习笔记之六>

    说明:本文依然依赖于 contiki/platform/native/contiki-main.c 文件. ---------------------------------------------- ...

  7. contiki-main.c 一 打印观察 <contiki学习之五>

    说明: 本文依赖于 contiki/platform/native/contiki-main.c 文件. 在项目工程目录下的hello-world.c 文件里面,有许多的宏,但没有最关键的main() ...

  8. cc2530 makefile简略分析 <contiki学习之三>

    前面将contiki的makefile框架都理了下,这篇就以cc2530为收篇吧,也即makefile分析就该到此为止了. contiki/examples/cc2530dk 打开Makefile如下 ...

  9. contiki makefile框架分析 < contiki学习之一 >

    在linux下的工程编译,基本都可以使用makefile这个工具来完成.Contiki OS亦如此,下面分析contiki整个Makefile的框架,对makefile的具体内容暂不做分析.本文依赖于 ...

随机推荐

  1. Nosql释义

    NoSQL不是产品,是一项运动        ---->NoSQL(NoSQL = Not Only SQL ),意即反SQL运动,是一项全新的数据库革命性运动,早期就有人提出,发展至2009年 ...

  2. Mybatis学习——一对多关联表查询

    1.实体类 public class Student { private int id; private String name; } public class Classes { private i ...

  3. redis学习笔记之pipeline

    redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常 会阻塞并等待redi ...

  4. Cadence原理图与Allegro交互

    1:激活orCAD与Allegro的交互程序 打开原理图,Options->Preference在Miscellaneous里勾选 2:打开用到的工程 原理图,还有Allegro PCB Des ...

  5. Android-day02_广播

    1.什么是广播 貌似一个人大声喊一句话,别人听到了这就是广播 2.在android中广播有标准广播和有序广播 标准广播也就是发送一个广播,所有人都能同一时间接收到 有序广播则是有顺序的广播,发送的时候 ...

  6. Backbone.js developer 武汉 年薪8w-10w

    1.   精通Backbone.js 2.   熟练Ajax.NoSQL.RESTful APIs 3.   了解Pusher.com和 Parse.com 4.   具有良好的沟通能力,学习能力,敬 ...

  7. Tsinsen A1303. tree(伍一鸣) (LCT+处理标记)

    [题目链接] http://www.tsinsen.com/A1303 [题意] 给定一棵树,提供树上路径乘/加一个数,加边断边,查询路径和的操作. [思路] LCT+传标 一次dfs构造LCT. L ...

  8. 多元线性回归(Linear Regression with multiple variables)与最小二乘(least squat)

    1.线性回归介绍 X指训练数据的feature,beta指待估计得参数. 详细见http://zh.wikipedia.org/wiki/%E4%B8%80%E8%88%AC%E7%BA%BF%E6% ...

  9. [算法] 插入排序 Insertion Sort

    插入排序(Insertion Sort)是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入.插入排序在实现上,通常采用in-pla ...

  10. 集群——LVS理论(转)

    原文:http://caduke.blog.51cto.com/3365689/1544229 当单个服务器性能 不能满足日益增多访问流量时,服务器的扩展策略: Scale Up :向上扩展,提升单个 ...