Contiki学习笔记 第一个程序:Hello World
2016-01-18 12:53
204 查看
想来想去,还是得先写一个程序,找下感觉,增强一下自信心,那就国际惯例Hello World吧。
先到这个网址下一个Instant Contiki 2.7。之所以没用3.0的,是因为有些问题,我源码是下的3.0的。
http://sourceforge.net/projects/contiki/files/Instant%20Contiki/
下完后装个VMWear,载入Instant Contiki 2.7虚拟机,就可以在Ubuntu上用contiki了。
打开终端,默认是用user用户名登录,密码也是user。ls一下,看见有contiki目录就对了。接下来在user根目录下建一个demo目录用来存放自己的工程,然后在demo目录下建一个helloworld目录,然后进去。
建一个hello-world.c文件,输入如下代码:
接下来回到user根目录,然后进入contiki目录,敲pwd命令,记下当前路径,等下要用。重新进入helloworld目录,新建一个Makefile文件,输入如下代码:
准备工作完成,敲入命令make,编译、生成可执行文件。此处相当坑爹,代码写错几处,编译不过,要删除生成的文件再编译,折磨死我了。先将就着,以后要换个工具写代码。生成完后,如图所示,生成很多文件。
![](http://img2.ph.126.net/CvzkfZPCZLrKGGaHjD3gtg==/6630914933585476454.jpg)
看到绿色文件没?执行它,结果如图所示:
![](http://img2.ph.126.net/5lBPqcZAURRykWHjI--LRQ==/662873570171754476.jpg)
出现Hello World!后程序不会自动退出,这跟在Linux下写C程序可是不一样的。按Ctrl+C退出程序。
好,举杯庆祝迈出了关键一步。下面的大段分析就以此展开。
hello-world.c的代码真正属于自己的代码只有printf语句,其他都是固定格式。也就是说将来写程序是在PROCESS_BEGIN();和PROCESS_END();之间写自己的代码。main()方法呢?main方法是有,不在这里,不用我们自己写,习惯就好。
好,先分析第一句代码
PROCESS(HW, "HWP");
先看看PROCESS源码,就在前一篇process结构体上面:
和之前一样,只考虑有名字的情况,代入实参PROCESS变为:
PROCESS(HW, "HWP");
再代入下面公式,变为:
PROCESS_THREAD(HW, ev, data); \
struct process HW= { NULL, "HWP", process_thread_HW }
接下来看看PROCESS_THREAD的声明,
越来越复杂了,继续代入吧 PROCESS_THREAD(HW, ev, data); 变为:
还没完,还得跟踪PT_THREAD,在Pt.h头文件中,先看看定义:
这个……这个上一篇日志中刚接触过,用于把一个东西变成函数指针,先代入看看:
这回没变成函数指针,而是一个方法,看来PT_THREAD这个宏定义专门用来生成函数,它有注释,看看怎么说:
* Declaration of a protothread.
*声明一个线程原型
* This macro is used to declare a protothread. All protothreads must
* be declared with this macro.
*此宏用于声明线程原型,所有的线程原型必须通过此宏声明
* \param name_args The name and arguments of the C function
*参数name_args:C函数的名称和参数
* implementing the protothread.
小结:一系列动作下来观察到,PROCESS宏实际上就是给定一个name参数,此处可看做函数名称的一部分,生成一个静态函数,函数返回值为char,名称为process_thread_+name,函数里面有三个参数。结构体pt也是第二次碰到了(参考上一篇日志),就是一个数字。剩余两参数后面用到再回来讨论。
还没完,下面来分析第二句 struct process HW= { NULL, "HWP", process_thread_HW }
这里初始化了一个process结构体变量HW,上一篇日志我们已经分析了process结构,再贴上来看看
结构体HW变量的第一个成员是指向下一元素指针,设为NULL,还未加入链表,只是NULL了。
第二个成员表示进程的名称,这里为"HWP",这是我们起的名字。
第三个成员,表示一个函数指针,每个process都有一个函数,process执行的就是这个函数,我们看看它的名字:process_thread_HW ,这不正是我们之前通过PT_THREAD展开的函数嘛。
还有三个参数没赋初值。
好,现在可以做一个总结了:
helloworld的第一句代码PROCESS(HW, "HWP");声明了此进程所对应的函数原型process_thread_HW ,还声明了此进程所对应的process结构体HW。并将函数原型作为HW的成员。
天啊,太复杂了,这才是第一句代码。
越来越有意思了,有些东西,外面看很抗拒,一旦钻进去,却又爱不释手。C语言一些简单的语法,能实现C#要非常复杂才能实现的功能。真是不同的世界观,准确地说,应该是让在不同的角度去看世界。继续分析第二句代码
先找到AUTOSTART_PROCESSES定义,在Autostart.h头文件中
先上网查查__VA_ARGS__是什么:
__VA_ARGS__ 是一个可变参数的宏(gcc支持)。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,替换省略号所代表的字符串。
经过替换,语句变成如下形式:
这里声明了一个指针数组,数组里的每一个指针都指向了一个process,数组声明的同时初始化了两个元素,第一个元素是指向HW的指针。回头看上一篇日志,HW正是表示当前process的结构体变量。这个数组用来做什么呢?后面用到再讲解。
下面继续讲解第三条语句:PROCESS_THREAD(HW, ev, data)
PROCESS_THREAD宏是老朋友了(请阅上一篇日志),专门用于生成process所要执行的函数原型。代入后的语句如下:
和上一篇日志展开后一样,上一次用于声明函数原型,这次声明的是函数主体。
继续第四条语句:PROCESS_BEGIN();
这个是关键,先找找宏定义,Process.h头文件中:
继续代入吧,有啥可说的呢,语句变为:
接下来找PT_BEGIN宏,Pt.h头文件中,原型如下:
代入,语句变为:
整下容,变为
继续追踪LC_RESUME宏:
代入上式,最终PROCESS_BEGIN();变成:
这……这……这是什么?半条语句,if (PT_YIELD_FLAG) {;} 意义何在?先不管了,将来有机会弄明白再说吧。
最后,PROCESS_END() 找宏
再找PT_END
最终,语句变为:
LC_END定义为:
PT_INIT定义为:
LC_INIT定义为:
PT_ENDED定义为:
一层层代入,最终PROCESS_END()变成:
凌乱了,整理下思绪,休息一下把Helloworld.c全部展开看看
脑袋有点不够用了,慢慢展开吧,看看庐山真面目:
下面给代码加上我自己的理解
有点凌乱,但也只能如此理解。这个程序只打印一句话,没用到事件,所以产生了一些无用语句。只能等下次代入事件,看看会不会有什么新的理解。
先到这个网址下一个Instant Contiki 2.7。之所以没用3.0的,是因为有些问题,我源码是下的3.0的。
http://sourceforge.net/projects/contiki/files/Instant%20Contiki/
下完后装个VMWear,载入Instant Contiki 2.7虚拟机,就可以在Ubuntu上用contiki了。
打开终端,默认是用user用户名登录,密码也是user。ls一下,看见有contiki目录就对了。接下来在user根目录下建一个demo目录用来存放自己的工程,然后在demo目录下建一个helloworld目录,然后进去。
建一个hello-world.c文件,输入如下代码:
#include "contiki.h" #include <stdio.h> PROCESS(HW, "HWP"); AUTOSTART_PROCESSES(&HW); PROCESS_THREAD(HW, ev, data) { PROCESS_BEGIN(); printf("Hello world!\n"); //此处放自己的代码 PROCESS_END(); }
接下来回到user根目录,然后进入contiki目录,敲pwd命令,记下当前路径,等下要用。重新进入helloworld目录,新建一个Makefile文件,输入如下代码:
CONTIKI_PROJECT = hello-world all: $(CONTIKI_PROJECT) /* Contiki源文件根目录,使用前面记下的路径 */ CONTIKI = /home/user/contiki include $(CONTIKI)/Makefile.include
准备工作完成,敲入命令make,编译、生成可执行文件。此处相当坑爹,代码写错几处,编译不过,要删除生成的文件再编译,折磨死我了。先将就着,以后要换个工具写代码。生成完后,如图所示,生成很多文件。
![](http://img2.ph.126.net/CvzkfZPCZLrKGGaHjD3gtg==/6630914933585476454.jpg)
看到绿色文件没?执行它,结果如图所示:
![](http://img2.ph.126.net/5lBPqcZAURRykWHjI--LRQ==/662873570171754476.jpg)
出现Hello World!后程序不会自动退出,这跟在Linux下写C程序可是不一样的。按Ctrl+C退出程序。
好,举杯庆祝迈出了关键一步。下面的大段分析就以此展开。
hello-world.c的代码真正属于自己的代码只有printf语句,其他都是固定格式。也就是说将来写程序是在PROCESS_BEGIN();和PROCESS_END();之间写自己的代码。main()方法呢?main方法是有,不在这里,不用我们自己写,习惯就好。
好,先分析第一句代码
PROCESS(HW, "HWP");
先看看PROCESS源码,就在前一篇process结构体上面:
#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 }
和之前一样,只考虑有名字的情况,代入实参PROCESS变为:
PROCESS(HW, "HWP");
再代入下面公式,变为:
PROCESS_THREAD(HW, ev, data); \
struct process HW= { NULL, "HWP", process_thread_HW }
接下来看看PROCESS_THREAD的声明,
/** * Define the body of a process. *定义process主体 * This macro is used to define the body (protothread) of a * process. The process is called whenever an event occurs in the * system, A process always start with the PROCESS_BEGIN() macro and * end with the PROCESS_END() macro. *此宏用于定义一个process的主体,当某事件发生时,process被调用。process总是从PROCESS_BEGIN()宏开始,并结束于 *PROCESS_END() 宏 */ #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(HW, ev, data); 变为:
static PT_THREAD(process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data))
还没完,还得跟踪PT_THREAD,在Pt.h头文件中,先看看定义:
#define PT_THREAD(name_args) char name_args
这个……这个上一篇日志中刚接触过,用于把一个东西变成函数指针,先代入看看:
static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data)
这回没变成函数指针,而是一个方法,看来PT_THREAD这个宏定义专门用来生成函数,它有注释,看看怎么说:
* Declaration of a protothread.
*声明一个线程原型
* This macro is used to declare a protothread. All protothreads must
* be declared with this macro.
*此宏用于声明线程原型,所有的线程原型必须通过此宏声明
* \param name_args The name and arguments of the C function
*参数name_args:C函数的名称和参数
* implementing the protothread.
小结:一系列动作下来观察到,PROCESS宏实际上就是给定一个name参数,此处可看做函数名称的一部分,生成一个静态函数,函数返回值为char,名称为process_thread_+name,函数里面有三个参数。结构体pt也是第二次碰到了(参考上一篇日志),就是一个数字。剩余两参数后面用到再回来讨论。
还没完,下面来分析第二句 struct process HW= { NULL, "HWP", process_thread_HW }
这里初始化了一个process结构体变量HW,上一篇日志我们已经分析了process结构,再贴上来看看
struct process { struct process *next; const char *name; char (* thread)(struct pt *, process_event_t, process_data_t) struct pt pt; unsigned char state; unsigned char needspoll; };
结构体HW变量的第一个成员是指向下一元素指针,设为NULL,还未加入链表,只是NULL了。
第二个成员表示进程的名称,这里为"HWP",这是我们起的名字。
第三个成员,表示一个函数指针,每个process都有一个函数,process执行的就是这个函数,我们看看它的名字:process_thread_HW ,这不正是我们之前通过PT_THREAD展开的函数嘛。
还有三个参数没赋初值。
好,现在可以做一个总结了:
helloworld的第一句代码PROCESS(HW, "HWP");声明了此进程所对应的函数原型process_thread_HW ,还声明了此进程所对应的process结构体HW。并将函数原型作为HW的成员。
天啊,太复杂了,这才是第一句代码。
越来越有意思了,有些东西,外面看很抗拒,一旦钻进去,却又爱不释手。C语言一些简单的语法,能实现C#要非常复杂才能实现的功能。真是不同的世界观,准确地说,应该是让在不同的角度去看世界。继续分析第二句代码
AUTOSTART_PROCESSES(&HW);
先找到AUTOSTART_PROCESSES定义,在Autostart.h头文件中
#define AUTOSTART_PROCESSES(...) \ struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
先上网查查__VA_ARGS__是什么:
__VA_ARGS__ 是一个可变参数的宏(gcc支持)。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,替换省略号所代表的字符串。
经过替换,语句变成如下形式:
struct process * const autostart_processes[] = {&HW, NULL}
这里声明了一个指针数组,数组里的每一个指针都指向了一个process,数组声明的同时初始化了两个元素,第一个元素是指向HW的指针。回头看上一篇日志,HW正是表示当前process的结构体变量。这个数组用来做什么呢?后面用到再讲解。
下面继续讲解第三条语句:PROCESS_THREAD(HW, ev, data)
PROCESS_THREAD宏是老朋友了(请阅上一篇日志),专门用于生成process所要执行的函数原型。代入后的语句如下:
static char process_thread_HW(struct pt *process_pt, \ process_event_t ev, \ process_data_t data)
和上一篇日志展开后一样,上一次用于声明函数原型,这次声明的是函数主体。
继续第四条语句:PROCESS_BEGIN();
这个是关键,先找找宏定义,Process.h头文件中:
/** * Define the beginning of a process. *定义process的开始部分 * This macro defines the beginning of a process, and must always * appear in a PROCESS_THREAD() definition. The PROCESS_END() macro * must come at the end of the process. *此宏用于定义一个process的开始部分,并只能在PROCESS_THREAD() 函数体中定义。在process结尾处必须紧接着定义 *PROCESS_END() 宏。 */ #define PROCESS_BEGIN() PT_BEGIN(process_pt)
继续代入吧,有啥可说的呢,语句变为:
PT_BEGIN(process_pt);
接下来找PT_BEGIN宏,Pt.h头文件中,原型如下:
/** * Declare the start of a protothread inside the C function * implementing the protothread. *用于在线程原型函数主体中声明一个线程的开始部分 * This macro is used to declare the starting point of a * protothread. It should be placed at the start of the function in * which the protothread runs. All C statements above the PT_BEGIN() * invokation will be executed each time the protothread is scheduled. *此宏放在线程运行的开始部分。线程将会根据执行在PT_BEGIN()中声明的调用。 * \param pt A pointer to the protothread control structure. * \hideinitializer */ #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((pt)->lc);
代入,语句变为:
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((process_pt)->lc)
整下容,变为
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((process_pt)->lc);
继续追踪LC_RESUME宏:
#define LC_RESUME(s) switch(s) { case 0:
代入上式,最终PROCESS_BEGIN();变成:
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((process_pt)->lc) { case 0:;
这……这……这是什么?半条语句,if (PT_YIELD_FLAG) {;} 意义何在?先不管了,将来有机会弄明白再说吧。
最后,PROCESS_END() 找宏
#define PROCESS_END() PT_END(process_pt)
再找PT_END
#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(pt); return PT_ENDED; }
最终,语句变为:
LC_END((process_pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(process_pt); return PT_ENDED; } 整下容变成: LC_END((process_pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(process_pt); return PT_ENDED; }
LC_END定义为:
#define LC_END(s) }
PT_INIT定义为:
#define PT_INIT(pt) LC_INIT((pt)->lc)
LC_INIT定义为:
#define LC_INIT(s) s = 0;
PT_ENDED定义为:
#define PT_ENDED 3
一层层代入,最终PROCESS_END()变成:
} PT_YIELD_FLAG = 0; \ (process_pt)->lc = 0; return 3; }
凌乱了,整理下思绪,休息一下把Helloworld.c全部展开看看
脑袋有点不够用了,慢慢展开吧,看看庐山真面目:
static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) struct process HW= { NULL, "HWP", process_thread_HW } struct process * const autostart_processes[] = {&HW, NULL} static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((process_pt)->lc) { case 0: printf("Hello world!\n"); }; PT_YIELD_FLAG = 0; \ (process_pt)->lc = 0; return 3; }
下面给代码加上我自己的理解
//声明一个函数原型,用于process所执行的方法 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) //声明代表进程的结构体,并把之前的函数原型做为其参数代入 struct process HW= { NULL, "HWP", process_thread_HW } //声明一个process的指针数组,用于存放多个process(此程序只有一个),最后放入NULL只是为了方便查找到数组结尾。这 //里没有用链表,说明不需要删除process(个人猜测) struct process * const autostart_processes[] = {&HW, NULL} //函数主体,对应上面的函数原型 static char process_thread_HW(struct pt *process_pt, process_event_t ev, process_data_t data) { //由于这个程序没用到事件,此参数无用,所以下面三句都是废话 char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} //process_pt为函数第一个参数,并无赋值,此时值为0 switch((process_pt)->lc) { case 0: printf("Hello world!\n"); //由于process_pt的值为0,所以执行此句 }; PT_YIELD_FLAG = 0; //此处无用 (process_pt)->lc = 0; //此处无用 return 3; //返回PT_ENDED,从字面意义上理解protothread_ended,指示此process已经game over。 }
有点凌乱,但也只能如此理解。这个程序只打印一句话,没用到事件,所以产生了一些无用语句。只能等下次代入事件,看看会不会有什么新的理解。
相关文章推荐
- 多线程
- 用FFmpeg命令处理音视频总结
- 相对分子质量
- Restfull API 示例
- POJ 2251 Dungeon Master
- 如何成为一名优秀的前端工程师
- iOS 可延展视图(点击前显示部分文字,点击后显示全部)
- 在线安装maven插件问题:Cannot complete the install because one or more required items could not be found.
- iOS裁剪图片成圆形
- LeetCode 328 Odd Even Linked List(奇偶链表)(*)
- 查看当前web服务器的并发连接数
- Centos7上安装OpenLDAP
- 前缀和 Codeforces509E Pretty Song
- C#OOP基础之类,字段,属性和方法。
- CSS代码检查工具推荐:CSS Lint
- linux下安装软件
- java swing写的双色球摇奖机
- CSS代码检查工具推荐:CSS Lint
- 2016.01.18-2016.01.21盲审通关修改
- 游戏数学中的数学思想