本文介绍Contiki主要数据结构之进程,深入源码分析,并用图示直观表示进程链表。
1. 进程
进程结构体源码如下:
struct process
{
struct process *next; //指向下一个进程
/******见1.1 进程名称***********/
#if PROCESS_CONF_NO_PROCESS_NAMES
#define PROCESS_NAME_STRING(process) ""
#else
const char *name;
#define PROCESS_NAME_STRING(process) (process)->name
#endif
PT_THREAD((*thread)(struct pt *, process_event_t, process_data_t)); //见1.2
struct pt pt; //见1.3
unsigned char state; //见1.4
unsigned char needspoll; //见1.5
};
1.1 进程名称
运用C语言预编译指令,可以配置进程名称,宏PROCESS_NAME_STRING(process)
用于返回进程process名称,若系统无配置进程名称,则返回空字符串。在以后讨论中,均假设配有进程名称。
1.2 PT_THREAD宏
PT_THREAD宏定义如下:
#define PT_THREAD(name_args) char name_args
故该语句展开如下:
char (*thread)(struct pt *, process_event_t, process_data_t);
声明一个函数指针thread,指向的是一个含有3个参数,返回值为char类型的函数。这是进程的主体,当进程执行时,主要是执行这个函数的内容,详情请参考博文《Contiki学习笔记:实例hello_world剖析》。另,声明一个进程包含在宏PROCESS(name, strname)
里,通过宏AUTOSTART_PROCESSES(...)
将进程加入自启动数组中。
1.3 pt
pt结构体一步步展开如下:
struct pt
{
lc_t lc;
};
typedef unsigned short lc_t;
如此,可以把struct pt pt
直接理解成unsigned short lc
,以后如无特殊说明,pt
就直接理解成lc
。lc
(local continuations)用于保存程序被中断的行数(只需两个字节,这恰是protothread轻量级的集中体现),被中断的地方,保存行数(s=__LINE__
)接着是语句case __LINE__
。当该进程再次被调度时,从PROCESS_BEGIN()
开始执行,而该宏展开含有这条语句switch(process_pt->pt)
,从而跳到上一次被中断的地方(即case __LINE__
),继续执行。
1.4 进程状态
进程共3个状态,宏定义如下:
#define PROCESS_STATE_NONE 0 /*类似于Linux系统的僵尸状态,进程已退出,只是还没从进程链表删除*/
#define PROCESS_STATE_RUNNING 1 /*进程正在执行*/
#define PROCESS_STATE_CALLED 2 /*实际上是返回,并保存lc值*/
1.5 needspoll
简而言之,needspoll为1的进程有更高的优先级。具体表现为,当系统调用process_run()函数时,把所有needspoll标志为1的进程投入运行,而后才从事件队列取出下一个事件传递给相应的监听进程。
与needspoll相关的另一个变量poll_requested,用于标识系统是否存在高优先级进程,即标记系统是否有进程的needspoll为1。
static volatile unsigned char poll_requested;
2. 进程链表
基于上述分析,将代码展开或简化,得到如下进程链表process_list:
Contiki进程链表visio源文件:Contiki进程链表.vsd。
3. 创建进程及启动进程
创建进程主要由PROCESS宏(声明进程)和PROCESS_THREAD宏(定义进程执行主体)完成,详情可参考博文《实例hello_world剖析》,启动进程由process_start函数完成,总是把进程加入到进程链表的头部,详情可参考博文《启动一个进程process_start》。