从hello-world 开始 <contiki学习之四>
按照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学习之四>的更多相关文章
- 简单的玩玩etimer <contiki学习笔记之九 补充>
这幅图片是对前面 <<contiki学习笔记之九>> 的一个补充说明. 简单的玩玩etimer <contiki学习笔记之九> 或许,自己正在掀开contiki ...
- 简单的玩玩etimer <contiki学习笔记之九>
好吧,我承认etimer有点小复杂,主要是它似乎和contiki的process搅在一起,到处都在call_process.那就先搜搜contiki下的etimer的example看看,然后再试着写一 ...
- [转]Docker学习之四:使用docker安装mysql
本文转自:https://blog.csdn.net/qq_19348391/article/details/82998391 Docker学习之一:注册Docker Hub账号 Docker学习之二 ...
- jackson学习之四:WRAP_ROOT_VALUE(root对象)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- PROCESS_YIELD()宏和C语言的switch语句< contiki学习笔记之七>
写在前面: 按照main()函数的代码一行一行的分析,该是看到了 etimer_process 这个位置.但是etimer_process实现里的一个宏 PROCESS_YIELD()引出了很多故事 ...
- contiki-main.c 中的process系列函数学习笔记 <contiki学习笔记之六>
说明:本文依然依赖于 contiki/platform/native/contiki-main.c 文件. ---------------------------------------------- ...
- contiki-main.c 一 打印观察 <contiki学习之五>
说明: 本文依赖于 contiki/platform/native/contiki-main.c 文件. 在项目工程目录下的hello-world.c 文件里面,有许多的宏,但没有最关键的main() ...
- cc2530 makefile简略分析 <contiki学习之三>
前面将contiki的makefile框架都理了下,这篇就以cc2530为收篇吧,也即makefile分析就该到此为止了. contiki/examples/cc2530dk 打开Makefile如下 ...
- contiki makefile框架分析 < contiki学习之一 >
在linux下的工程编译,基本都可以使用makefile这个工具来完成.Contiki OS亦如此,下面分析contiki整个Makefile的框架,对makefile的具体内容暂不做分析.本文依赖于 ...
随机推荐
- 【流媒體】live555—VS2010 下live555编译、使用及测试
Ⅰ live555简介 Live555 是一个为流媒体提供解决方案的跨平台的C++开源项目,它实现了对标准流媒体传输协议如RTP/RTCP.RTSP.SIP等的支持.Live555实现了对多种音视频编 ...
- <二>面向对象分析之几个关键的概念
一:建模 --->建模,是指通过对[客观事物]建立一种抽象的方法用以表征事物并获得对事物本身的理解.同时把这种理解概念化,将这些逻辑概念组织起来,构成一种对所观察对象的内部结构和工 ...
- Oracle中如何判断一个字符串是否含有汉字
看到网友问,怎么查询表中某个字段数据是不是包含了全角字符啊? 这个问题涉及到几个函数:to_single_byte.length和lengthb,我之前做开发的时候研究的是如何判断一个字符串中是否包含 ...
- Java应用调优指南之-工具篇
1. 土法调优两大件 先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump. 1.1 Heap Dump jmap -dump:live, ...
- Delphi EVariantTypeCastError错误的解决方法
在执行程序的时候总是提示: ---------------------------Debugger Exception Notification---------------------------P ...
- 在web.config里使用configSource分隔各类配置
转:http://www.yongfa365.com/Item/using-configSource-Split-Configs.html 大型项目中,可能有多个Service,也就是会有一堆配置,而 ...
- User Agent
Android: Mozilla/5.0 (Linux; U; Android 2.3.5; zh-cn; MI-ONE Plus Build/GINGERBREAD) AppleWebKit/533 ...
- private
成员变量私有化的好处在于可以强制加强面向对象和封装的概念,一个面向对象的系统更加关注行为,而不是数据,所以应该通过发送消息来获得数据,也应该实习细节的封装
- 【跟我一起学Python吧】python with statement 进阶理解
由于之前有一个项目老是要打开文件,然后用pickle.load(file),再处理...最后要关闭文件,所以觉得有点繁琐,代码也不简洁.所以向python with statement寻求解决方法.以 ...
- 内核源码分析之进程地址空间(基于3.16-rc4)
所谓进程的地址空间,指的就是进程的虚拟地址空间.当创建一个进程时,内核会为该进程分配一个线性的地址空间(虚拟地址空间),有了虚拟地址空间后,内核就可以通过页表将进程的物理地址地址空间映射到其虚拟地址空 ...