由图示我们可以看到,contiki中包含两种类型的进程,preemptive(可抢占的)和cooperative(合作的,由于只有两种进程,可以理解为非抢占的).
preemptive类型:优先级较高.可以在任何时候直接打断cooperative类的进程执行条件.
prremptive类型的进程可以由中断(interrupt,完合由硬件产生)或者是实时定时器(real-time timer,指的是contiki系统维护的rtimer)来触发.
cooperative类型的进程:优先级相对较低.可以在contiki系统启动时运行,或者是其它事件触发,这里的事件主要是指timer或者是外部的触发条件.
cooperative进程执行时,上一个cooerative进程必须执行完.类似于上图的Process B必须等Process A执行完了之后,才能执行.
cooperativer进程执行的过程中,如果有preemptive类型的进程执行,例如Process B进程.必须要等到preemptive类型的进程执行完之后,Process B进程才能完成它剩下的工作.
contiki中的进程要包括两种类型的数据,
进程控制块(PCB, Process Control Block):管理进程的名称,运行时的状态,而且还有进程中的线程的情况,由于是运行时的情况,理所当然在RAM中存储.
进程中的线程(Process thread):这部分内容放在ROM中
进程的状态,名称. 进程中的线程
代码中关于进程的定义在core/sys/process.h中
316 struct process { 317 struct process *next; 318 #if PROCESS_CONF_NO_PROCESS_NAMES 319 #define PROCESS_NAME_STRING(process) "" 320 #else 321 const char *name; 322 #define PROCESS_NAME_STRING(process) (process)->name 323 #endif 324 PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t)); 325 struct pt pt; 326 unsigned char state, needspoll; 327 }; 328用户状态下的代码是不能直接操作进程控制块的.只有进程管理相关的函数才可以直接操作.
从process这个结构体的定义上,我们可以看到:
*next 维护contiki进程控制块是一个链表
*name 进程的名字,这个名字主要用在调试上,能够向串口去打印当前的活动进程等其它信息
*thread 函数指针,指向process thread的地址
pt 维护这个进程中的protothread
state 记录进程的状态
needpoll 主要是进程轮询时的状态记录有专门的函数process_poll()对它进行操作.
进程用PROCESS宏进行定义.
308 #define PROCESS(name, strname) \ 309 PROCESS_THREAD(name, ev, data); \ 310 struct process name = { NULL, strname, \ 311 process_thread_##name } 312 #endif
再来查看进程控制块中的PT_THREAD宏 ,这个宏与protothread的操作相关.下面会分析protothread的一些信息.
protothread没有栈,属于轻量线的线程.主要是用在内存受限的操作系统或者无线传感网络的节点上.
用在事件驱动类型的系统上开一个阻塞的上下文(不包括每个线程中的任务),可以不用复杂的状态机模型进行
顺序流程控制.在C函数中提供有条件的阻塞.
protothread的所有线程都运行在一个上下文中.
protothread可以去调用其它的函数,但是其它的函数不能去调用protothread.
protothread能够在阻塞线程中提供一个线性的代码结构.一般说来,事件驱动的系统会在阻塞调用前
或者是阻塞调用后这两个点上去中断函数的执行.所以对于哪些函数能够阻塞,哪些函数永远也不会阻塞,
代码编写者是完全清楚的.
但是使用protothread也要特别小心,它没有完整的线程上下文.也就是局部变量的值没有在栈上保存.
千万不要在protothread中随便使用局部变量.除非你知道这个局部变量的作用域.
下面以contiki中的hello world的例子来分析一下protothread的实现及调用.
代码在examples/hello-world/hello-world.c中
40 #include "contiki.h" 41 42 #include <stdio.h> /* For printf() */ 43 /*---------------------------------------------------------------------------*/ 44 PROCESS(hello_world_process, "Hello world process"); 45 AUTOSTART_PROCESSES(&hello_world_process); 46 /*---------------------------------------------------------------------------*/ 47 PROCESS_THREAD(hello_world_process, ev, data) 48 { 49 PROCESS_BEGIN(); 50 51 printf("Hello, world\n"); 52 53 PROCESS_END(); 54 }
static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data); struct process hello_world_process = { NULL, "Hello world process", 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 = 1; if (PT_YIELD_FLAG) {;} switch(process_pt->lc) { case 0: printf("Hello, world\n"); } PT_YIELD_FLAG = 0; process_pt->lc=0; return PT_ENDED; }当然,如果图省事.可以用gcc的预编译参数.得到结果
进入example下的hello-world目录,预编译的命令:
[ycwang@ycwang:hello-world]$ gcc -E -DCONTIKI=1 -DCONTIKI_TARGET_NATIVE=1 -Wall -g -I/usr/local/include -O -I. -I../../platform/native/. -I../../platform/native/dev -I../../cpu/native/. -I../../cpu/native/net -I../../core/dev -I../../core/lib -I../../core/net -I../../core/net/mac -I../../core/net/rime -I../../core/net/rpl -I../../core/sys -I../../core/cfs -I../../core/ctk -I../../core/lib/ctk -I../../core/loader -I../../core/. -I../../platform/native/ -DAUTOSTART_ENABLE -c hello-world.c -o hello-world.co然后打开 -o 后面的文件名.
可以看出,contiki中的process是基于protothread的.至少有一个protothread.
struct pt是用来跳转或者是返回到当前的thread的.pt->lc正是protothread运行的核心.
关于LC的几个宏的定义在文件core/sys/lc-switch.h中
#define LC_INIT(s) s = 0; #define LC_RESUME(s) switch(s) { case 0: #define LC_SET(s) s = __LINE__; case __LINE__: #define LC_END(s) }由于 这几个宏展开的话,就成了switch case的选择语句.一定不能出现LC_SET混用在其它switch语句中的情况.
由于LC_SET直接取的是代码所在的行数.编译器会找到对应的代码段的地址.那么,通过变化__LINE__,就可以切换一个进程中的不同的PT.这是PT线程没有栈的根本原因.
LC相关的这几个宏不能直接在代码中调用.一般是通过PT或者PROCESS的宏来调用.
公共接口是
关于PROCESS相关的几个宏的例子在contiki的源码里倒处都是,例如platform/esb/apps/pinger.c
contiki中有两种事件.异步事件,同步事件.
异步事件主要存放在事件队列中,然后去循环执行.用process_post()把进程加入队列中.
立即执行.实现函数process_pose_synch().
通过process_poll()函数,然后直接执行被poll的进程.轮询是从中断中让一个进程执行的一种方法.
Event Identifier是用来标识事件的,在contiki中用一个8位的数来区分.根据标识符的不同类型做不同的处理,其中用户定义的进程的范围在0~127之间.
其它的进程是contiki的内核去定义的.定义的结果如下
#define PROCESS_EVENT_NONE 128 #define PROCESS_EVENT_INIT 129 #define PROCESS_EVENT_POLL 130 #define PROCESS_EVENT_EXIT 131 #define PROCESS_EVENT_CONTINUE 133 #define PROCESS_EVENT_MSG 134 #define PROCESS_EVENT_EXITED 135 #define PROCESS_EVENT_TIMER 136
进程调度器的目标就是为了使得每个进程在属于它自己的时间段内运行.进程调度器调用执行进程中的线程的函数来完成调度.contiki中的所有进程调用无外乎下面的几种方式.1.响应了进程中的特定的事件. 2. 轮循中请求进程.3. 进程调度器把事件标识符传给了要调用的进程.通过进程描述符,对应的指针也传到了进程中.指针为NULL的情况表示事件还没有传递数据.但是如果是通过轮循方式请求进程,也不会传递数据.
contiki中用process_start()函数来开始一个进程.
1.检查这个进程是否已经在运行(从运行队列中查找是否有这个进程),如果运行,则退出.
2.如果进程没有在运行队列中,则把进程加入运行队列,并全用PT_INIT来初始化进程,并且赋给PROCESS_EVENT_INIT的标识符.如果有数据的话,把指针传到进程中.用INIT类型的标识符,是为了让在进程实际运行前,有一部分代码做初始化工作.
进程退出有两种方式,
1.自己退出,一般是执行到PROCESS_EXIT()所在的宏的位置,或者是有PROCESS_END()状态的位置.进程将结束.
2.被另外一个进程杀死.调用process_exit()函数.
当进程退出时,不管这个进程是怎么退出的.contiki都会发一个消息给其它还没有退出的进程.这样便于释放由退出的进程占用的资源.比如在IP协议栈中,如果一个进程发起来了一个连接,但是这个进程已经退出的话,那么协议栈就会中断连接.从而释放资源.
最后contiki系统再把进程从队列中移除.
这种类型的进程也分两种启动方式.
1. 在contiki开始启动时,启动进程
2. 在一个模块开始装载时,自动启动进程
自启动的状态机可以反应出有哪些模块启动了.如果模块要卸栽的话,也可以随时释放内存.
借用一个最简单的例子hello world来反映进程的实际运行情况。
PROCESS(example_process, "Example process"); AUTOSTART_PROCESSES(&example_process); PROCESS_THREAD(example_process, ev, data) { PROCESS_BEGIN(); while(1) { PROCESS_WAIT_EVENT(); printf("Got event number %d\n", ev); } PROCESS_END(); }
PROCESS_WAIT_EVENT()主要是等待contiki的内核调度完成。真正执行这个进程上的代码。
关键的两个参数。
ev是指事件的参数
data是指传递数据的指针
进程运行分可抢占的,和不可抢占的。其中,可抢占的进程只有一种情况process_poll().