contiki进程管理

来源:互联网 发布:杭州软件信息技术开发 编辑:程序博客网 时间:2024/06/04 23:32

Contiki进程管理

Contiki 内核是基于事件驱动的,系统运行可以视为不断处理事件的过程。Contiki 整个运行是通过事件触发完成,一个事件绑定相应的进程。当事件被触发,系统把执行权交给事件所绑定的进程。

1、Contiki 内核

进程无疑是一个系统最重要的概述,Contiki 的进程机制是基于 Protothreads线程模型,为确保高优先级任务尽快得到响应,Contiki 采用两级进程调度。

2、Protothreads

Contiki 使用 Protothreads轻量级线程模型,在Protothreads 基础上进行封装。为了适应内存受限的嵌入式系统,瑞典计算机科学研究所设计了Protothreads,实际上是一种轻量级无线结构的线程库。传统的桌面操作系统甚至服务器操作系统,每个进程都拥有自己的栈,进行进程切换时,将进程相关的信息(包括局部变量、断点、寄存器值)存储在栈中。然而,对于嵌入式系统,尤其是内存受限的传感器节点几乎不现实,基于这点考虑,Protothreads巧妙地让所有进程共用一个栈,传统的进程与Protothreads对比示意图如下:


图1:传统进程与protothreads进程对比示意图

从图可以看出,原本需要3个栈的Thread机制,在Protothreads只需要一个栈,当进程数量很多的时候,由栈空间省下来的内存是相当可观的。保存程序断点在传统的Thread 机制很简单,只需要要保存在私有的栈,然而Protothreads不能将断点保存在公有栈中。Protothreads很巧妙地解决了这个问题,即用一个两字节静态变量存储被中断的行,因为静态变量不从栈上分配空间,所以即使有任务切换也不会影响到该变量,从而达到保存断点的。下一次该进程获得执行权的时候,进入函数体后就通过 switch 语句跳转到上一次被中断的地方。

(1)保存断点

保存断点是通过保存行数来完成的,在被中断的地方插入编译器关键字__LINE__,编译器便自动记录所中断的行数。展开那些具有中断功能的宏,可以发现最后保存行数是宏 LC_SET,取宏PROCESS_WAIT_EVENT()为例,将其展开得到如下代码:

#define PROCESS_WAIT_EVENT()PROCESS_YIELD()

#define PROCESS_YIELD()PT_YIELD(process_pt)

#define PT_YIELD(pt) \

do{ \

PT_YIELD_FLAG= 0; \

LC_SET((pt)->lc);\

if(PT_YIELD_FLAG== 0) \

{

returnPT_YIELDED; \

}\

}while(0)

#defineLC_SET(s) s = __LINE__; case __LINE__:  //保存程序断点,下次再运行该进程直接跳到case__LINE__。值得一提的是,宏LC_SET展开还包含语句case__LINE__,用于下次恢复断点,即下次通过 switch 语言便可跳转到 case 的下一语句。

(2)恢复断点

被中断程序再次获得执行权时,便从该进程的函数执行体进入,按照Contiki的编程替换,函数体第一条语句便是PROCESS_BEGIN 宏,该宏包含一条switch语句,用于跳转到上一次被中断的行,从而恢复执行,宏PROCESS_BEGIN 展开的源代码如下:

#definePROCESS_BEGIN() PT_BEGIN(process_pt)

#definePT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)

#defineLC_RESUME(s) switch(s) { case 0:    //switch 语言跳转到被中断的行

3、进程控制块

正如Linux一样,Contiki也用一个结构体来描述整个进程的细节,所不同的是,Contiki进程控制块要简单得多。使用链表将系统所有进程组织起来,如下图所示(将 PT_THREAD 宏展开):


图2:进程链表示意图

Contiki系统定义一个全局变量 process_list作为进程链表的头,定义一个全局变量process_current用于指向当前进程。成员变量next指向下一个进程,最后一进程的next指向空。name是进程的名称,可以将系统配置(定义变量PROCESS_CONF_NO_PROCESS_NAMES 为0)成没有进程名称,此时name为空字符串。变量state 表示进程的状态,共3种,即PROCESS_STATE_RUNNING、PROCESS_STATE_CALLED、PROCESS_STATE_NONE。变量 needspoll 标识进程优先级,只有两个值0和1,needspoll为1意味着进程具有更高的优先级。

(1)成员变量 thread

进程的执行体,即进程执行实际上是运行该函数。在实际的进程结构体代码中,该变量由宏PT_THREAD封装,展开即为一个函数指针,关键源代码如下:

PT_THREAD((*thread)(struct pt*, process_event_t, process_data_t));

#define PT_THREAD(name_args)char name_args

/***宏展开***/

char (*thread)(struct pt *,process_event_t, process_data_t);

(2)成员变量 pt

正如上文所述一样,Contiki进程是基于 Protothreads,所以进程控制块需要有个变量记录被中断的行数。结构体pt只有一个成员变量lc(无符号短整型),可以将pt简单理解成保存行数的,相关源代码如下:

struct pt

{

lc_t lc;

};

typedef unsigned short lc_t;

4、 进程调度

Contiki只有两种优先级,用进程控制块中变量needspoll标识,默认情况是0,即普通优先级。想要将某进程设为高优先级,可以在创建之初指定其needspoll为1,或在运行过程中通过设置该变量动态提升其优先级。实际的调度中,会先运行有高优先级的进程,而后再去处理一个事件,随后又运行所有高优先级的进程。通过遍历整个进程链表,将needspoll为1的进程投入运行,关键代码如下:

/***do_poll()关键代码,由process_run 调用***/

for(p = process_list; p !=NULL; p = p->next)    //遍历进程链表

{

if(p->needspoll)

{

p->state =PROCESS_STATE_RUNNING;            //设置进程状态

p->needspoll = 0;

call_process(p,PROCESS_EVENT_POLL, NULL);   //将进程投入运行

}

}

以上是进程的总体调度,具体到单个进程,成员变量state标识着进程的状态,共有 三个状态PROCESS_STATE_RUNNING、PROCESS_STATE_CALLED、PROCESS_STATE_NONE。Contiki 进程状态转换如下图:


图3:contiki进程状态转换图

创建进程(还未投入运行)以及进程退出(但此时还没从进程链表删除),进程状态都为 PROCESS_STATE_NONE。通过进程启动函数 process_start 将新创建的进程投入运行队列 (但未必有执行权),真正获得执行权的进程状态为PROCESS_STATE_CALLED,处在运行队列的进程(包括正在运行和等待运行)可以调用exit_process退出。

(1)进程初始化

系统启动后先将进程初始化,通常由主函数main()调用,进程初始化主要完成事件队列和进程链表初始化。将进程链表头指向为空,当前进程也设为空。process_init源代码如下:

void process_init(void)

{

/***初始化事件队列***/

lastevent = PROCESS_EVENT_MAX;

nevents = fevent = 0;

process_maxevents = 0;

/***初始化进程链表***/

process_current = process_list= NULL;

}

(2)创建进程

创建进程实际上是定义一个进程控制块和定义进程执行体的函数。宏PROCESS 的功能包括声明进程(定义一个结构体),声明进程执行体函数,关键源代码如下(假设进程名称为 Hello world):

PROCESS(hello_world_process,"Hello world");

/***PROCESS 宏展开***/

PROCESS_THREAD(name, ev,data); \     //声明一个进程,进程名称name,事件ev

struct process name = { NULL,strname, process_thread_##name }//最终声明进程处理函数

/***PROCESS_THREAD 宏展开***/

staticPT_THREAD(process_thread_##name(struct pt *process_pt, process_event_t ev,

process_data_t data))

#define PT_THREAD(name_args)char name_args

/***将参数代入,PROCESS宏最后展开结果***/

static  char process_thread_hello_world_process(struct  pt *process_pt,  process_event_t

ev, process_data_t data);

struct processhello_world_process =    \

{NULL, "Helloworld", process_thread_hello_world_process };

可见,PROCESS宏实际上声明一个函数并定义一个进程控制块,新创建的进程next指针指向空,进程名称为“Hello  world”,进程执行体函数指针为process_thread_hello_world_process,保存行数的pt为0,状态为0(即PROCESS_STATE_NONE),优先级标记位needspoll也为 0(即普通优先级)。PROCESS 定义了结构体并声明了函数,还需要实现该函数,通过宏PROCESS_THREAD实现。值得注意的是,尽管PROCESS宏展开包含了宏PROCESS_THREAD,用于声明函数,而这里是定义函数,区别在于前者宏展开后面加了个分号。定义函数框架代码如下:

PROCESS_THREAD(hello_world_process,ev, data)

//static charprocess_thread_hello_world_process(struct pt *process_pt, process_event_t

ev, process_data_t data)

{

PROCESS_BEGIN(); //函数开头必须有

/***代码放在这***/

PROCESS_END(); //函数末尾必须有

}

把要实现的代码必须放在宏PROCESS_BEGIN与PROCESS_END之间,这是因为这两个宏用于辅助保存断点信息(即行数),宏PROCESS_BEGIN包含switch(process_pt->lc)语句,这样被中断的进程再次获利执行便可通过switch语句跳转到相应的case,即被中断的行。

(3)启动进程

附上调用过程:

Process_start开始一个进程,初始化要启动进程状态,调用:

process_post_synch(p, PROCESS_EVENT_INIT,(process_data_t)arg)保存当前进程,传递PROCESS_EVENT_INIT事件让进程开始执行,调用:

call_process (p, ev, data),调用:

p->thread开始执行进程主函数,返回值表示退出、结尾或者遇到PROCESS_EVENT_EXIT,进程退出,否则进程被挂起,等待事件。调用:

exit _ process (),进行验证,然后发出PROCESS_EVENT_EXITED事件,并从进程链表总删除该进程,处理与改进成有关的进程,恢复现场。

 

函数process_start用于启动一个进程,首先进行参数验证,即判断该进程是否已经在进程链表中,而后将进程加到链表,给该进程发一个初始化事件PROCESS_EVENT_INIT。函数 process_start 流程图如下:


图4:函数process_start()流程图

process_start将进程状态设为PROCESS_STATE_RUNNING,并调用PT_INIT宏将保存断点的变量设为0(即行数为0)。调用process_post_synch给进程触发一个同步事件,事件为PROCESS_EVENT_INIT。考虑到进程运行过程中可能被中断(比如中断),在进程运行前将当前进程指针保存起来,执行完再恢复。进程运行是由call_process函数实现。


图5:call_process()流程图

call_process 首先进行参数验证,即进程处于运行状态(退出尚未删除的进程状态为 PROCESS_STATE_NONE)并且进程的函数体不为空,接着将进程状态设为PROCESS_STATE_CALLED,表示该进程拥有执行权。接下来,运行进程函数体,根据返回值判断进程是否结束(主动的)或者退出(被动的),若是调用 exit_process将进程退出,否则将进程状态设为 PROCESS_STATE_RUNNING,继续放在进程链表。

(4)  进程退出

进程运行完或者收到退出的事件都会导致进程退出。根据Contiki编程风格,进程函数体最后一条语句是 PROCESS_END(),该宏包含语句return PT_ENDED,表示进程运行完毕。系统处理事件时(事件绑定进程,事实上执行进程函数体),倘若该进程恰好收到退出事件,thread 便返回PT_EXITED,进程被动退出。还有就是给该进程传递退出事件 PROCESS_EVENT_EXIT 也会导致进程退出。进程退出函数exit_process 流程图如下:


图6:函数 exit_process 流程图

进程退出函数exit_process首先对传进来的进程p进行参数验证,确保该进程在进程链表中并且进程状态为PROCESS_STATE_CALLED/RUNNING(即不能是NONE),接着将进程状态设为NONE。随后,向进程链表的所有其他进程触发退出事件 PROCESS_EVENT_EXITED,通知他们将要退出,让与该进程相关的进程进行相应处理。此时其他进程依次执行处理该事件,其中很重要一部分是取消与该进程的关联。进程执行函数体thread 进行善后工作,最后将该进程从进程链表删除。

PROCESS_EVENT_EXITED事件是由要退出的进程发出,来告诉其他有关联的进程进行相应的处理。

 

 

2 0