contiki系统分析三:进程分析


1. contiki中进程的类型




    由图示我们可以看到,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进程才能完成它剩下的工作.

   

2. contiki中进程的结构

   contiki中的进程要包括两种类型的数据, 

   进程控制块(PCB, Process Control Block):管理进程的名称,运行时的状态,而且还有进程中的线程的情况,由于是运行时的情况,理所当然在RAM中存储.

  进程中的线程(Process thread):这部分内容放在ROM中


2.1 进程控制块

   进程的状态,名称.  进程中的线程    

  代码中关于进程的定义在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

其中的ev表示process_event_t类型的参数.用在event_data结构体中,用于活动的事件的维护.

再来查看进程控制块中的PT_THREAD宏 ,这个宏与protothread的操作相关.下面会分析protothread的一些信息.

2.2 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_BEGIN() : 初始化定义PT PROCESS_END() : 用在彻底结束PT之前 PROCESS_EXIT() : 完全退出PT PROCESS_WAIT_EVENT() : 件等待,两种事件,同步或异步事件 PROCESS_WAIT_EVENT_UNTIL() : 当条件触发时,运行PT,等待条件 PROCESS_YIELD() : 等待触发条件,与 PROCESS_WAIT_EVENT()等价 PROCESS_WAIT_UNTIL() : 等待触发条件,也许下可能让出PT PROCESS_PAUSE()  : 临时出让PT.

关于PROCESS相关的几个宏的例子在contiki的源码里倒处都是,例如platform/esb/apps/pinger.c


2.3事件

contiki中有两种事件.异步事件,同步事件.


2.3.1异步事件



异步事件主要存放在事件队列中,然后去循环执行.用process_post()把进程加入队列中.


2.3.2 同步事件



立即执行.实现函数process_pose_synch().

2.3.3 轮询方式

通过process_poll()函数,然后直接执行被poll的进程.轮询是从中断中让一个进程执行的一种方法.

2.3.4 事件标识符

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

PROCESS_EVENT_NONE : 用来隔离用户与内核定义的描述符,没有其它作用. PROCESS_EVENT_INIT : 当初始化的时候,把事件发送到一个新的进程,这种类型只是用于进程初始化时把进程的状态切换到running.可以参考do_event()的实现.用于非广播方式向特定的进程发送信息时,才做切换处理.如果是用广播方式发送,就会对每一个进程做do_poll()操作. PROCESS_EVENT_POLL : 事件的轮询标记,主要用于do_poll()的实现. PROCESS_EVENT_EXIT : 用在exit_process()中,用来同步通知contiki的服务,这个进程即将结束,需要释放或解除与这个进程相关的状态. PROCESS_EVENT_CONTINUE :由内核发给进程,等待 PROCESS_YIELD() 的状态.PROCESS_PAUSE()中用这个标识才做短暂的暂停,等到标识符所表示的状态时,继续执行. PROCESS_EVENT_MSG :主要用在IP协议栈中,但是,也能够用在进程间通信. PROCESS_EVENT_EXITED : 用在exit_process()中,用来向非进程P的其它所有进程广播,进程P将退出.但是 PROCESS_EVENT_EXIT是通知进程P中的线程退出.然后在链表中删除进程. PROCESS_EVENT_TIMER :当事件时钟(etimer)失效时,发消息给进程

2.4 进程调度器

进程调度器的目标就是为了使得每个进程在属于它自己的时间段内运行.进程调度器调用执行进程中的线程的函数来完成调度.contiki中的所有进程调用无外乎下面的几种方式.1.响应了进程中的特定的事件. 2. 轮循中请求进程.3. 进程调度器把事件标识符传给了要调用的进程.通过进程描述符,对应的指针也传到了进程中.指针为NULL的情况表示事件还没有传递数据.但是如果是通过轮循方式请求进程,也不会传递数据.

2.4.1 进程开始

contiki中用process_start()函数来开始一个进程.

1.检查这个进程是否已经在运行(从运行队列中查找是否有这个进程),如果运行,则退出.

2.如果进程没有在运行队列中,则把进程加入运行队列,并全用PT_INIT来初始化进程,并且赋给PROCESS_EVENT_INIT的标识符.如果有数据的话,把指针传到进程中.用INIT类型的标识符,是为了让在进程实际运行前,有一部分代码做初始化工作.


2.4.2 退出并杀死进程

进程退出有两种方式,

1.自己退出,一般是执行到PROCESS_EXIT()所在的宏的位置,或者是有PROCESS_END()状态的位置.进程将结束.

2.被另外一个进程杀死.调用process_exit()函数.

当进程退出时,不管这个进程是怎么退出的.contiki都会发一个消息给其它还没有退出的进程.这样便于释放由退出的进程占用的资源.比如在IP协议栈中,如果一个进程发起来了一个连接,但是这个进程已经退出的话,那么协议栈就会中断连接.从而释放资源.

最后contiki系统再把进程从队列中移除.


2.4.3 自动启动的进程

这种类型的进程也分两种启动方式.

1. 在contiki开始启动时,启动进程

2. 在一个模块开始装载时,自动启动进程

自启动的状态机可以反应出有哪些模块启动了.如果模块要卸栽的话,也可以随时释放内存.


3 示例分析

借用一个最简单的例子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和PROCESS_THREAD宏的展开,上面已经分析过了。 AUTOSTART_PROCESSES这个宏主要是用来在contiki启动时,就启动进程。还有种情况是当装载模块时,开始运行。所以要传入example_process的地址进来。

PROCESS_WAIT_EVENT()主要是等待contiki的内核调度完成。真正执行这个进程上的代码。

关键的两个参数。

ev是指事件的参数

data是指传递数据的指针


4 总结


进和是contiki中比较重要应用。进程中包括进程控制块和线程两部分。进程控制块用来提供进程运行时的信息,而进程中的线程包括进程的代码。线程也就是轻量级的protothread。

进程运行分可抢占的,和不可抢占的。其中,可抢占的进程只有一种情况process_poll().

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值