Protothread:
Lightweight, Stackless Threads in C
C协程
利用C语言的语法特性或者利用编译器特性来完成上行文的切换,所有的thread共用一个堆栈,只是用2byte保存上下文。类似于协作式操作系统,由thread主动释放CPU。设计原理可参照http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html 文件代码可参照http://dunkels.com/adam/pt/index.html
一个典型的Protothread(以下简称pt)如下
PT_INIT(&ptex);
PT_THREAD(pt_name)(struct *pt, void *param)
{
PT_BEGIN(pt);
while(1)
{
PT_WAIT_UNTIL(pt, condition);
}
PT_END(pt);
}
按照lc.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) }
和pt.h的宏定义:
#define PT_INIT(pt) LC_INIT((pt)->lc)
#define PT_THREAD(name_args) char name_args
#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((pt)->lc)
#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
PT_INIT(pt); return PT_ENDED; }
#define PT_WAIT_UNTIL(pt, condition) do {LC_SET((pt)->lc); if(!(condition)) {return PT_WAITING;}\
} while(0)
将上面代码展开:
&ptex->lc = 0;
char pt_name(struct *pt, void *param)
{
char PT_YIELD_FLAG = 1;
if(PT_TIELD_FLAG){;};
switch(pt->lc)
{
case 0: PT_BEGIN
While(1)
{
do
{
Pt->lc = __LINE__;
case __LINE__:
If(! condition)
{
return PT_WAITING;
}
}while(0); PT_WAIT_UNTIL
}
} PT_END
}
假设在main函数中一直call这个函数
main()
{
while(1)
{
pt_name(&ptex,NULL);
pt_name1(&ptex1,NULL);
}
}
那么即使,每次从__LINE__处退出了,下一次进入函数会再到__LINE__处执行,这个流程巧妙的利用__LINE__保存上下文,利用循环调用函数来完成调度。
基于上面的分析可知(基于switch-case):
1. pt 中使用的局部变量,在发生调度后无效。因为调度是通过调用函数来完成。所以pt内要注意局部变量的使用
2. 因为上下文切换依赖的是switch-case,因此在pt内使用单独的case语句会导致流程混乱,所以尽量避免在pt中使用case。
3. pt的依赖于case的跌落特性,在展开后的代码明显违反代码规范,而且会导致编译器warning.
4. 发生上下文切换的条件是一个pt主动释放CPU(从函数返回),然后另一个pt开始执行(进入另一个函数执行)
Contiki process:
Contiki是专门为物联网开发的操作系统,其core部分是基于pt来开发的,引入了基于event的调度机制,做成基于事件驱动的OS,由于占用资源极少,采用纯C语言。因此可以将其core方便的移植到低资源的嵌入式平台上。代码参见http://www.contiki-os.org/
Contiki core基本结构分析:
1. Process
Process 结构
Process基于pt扩展而来,process以单链表形式管理所有process
struct process {
struct process *next;
PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t));
struct pt pt;
unsigned char state, needspoll;
};
next: 指向下一个process
thread:实际的process pt函数
pt: pt上下文
state: process状态
PROCESS_STATE_NONE process退出后的状态
PROCESS_STATE_RUNNING process 就绪,等待运行
PROCESS_STATE_CALLED process 本次已运行,等待下次调度
needspoll: poll标志
process本身是基于event的,当有event触发时一个process才会工作,设置needspoll标志,在无event触发的情况下也要执行process,执行一次process后该标志会被清掉
对于51内核来说一个process占用了3+3+2+1+1 = 10个字节
Process的形式
PROCESS(name, strname);
PROCESS_THREAD(name, ev, data)
{
PROCESS_BEGIN()
While(1)
{
PROCESS_WAIT_EVENT_UNTIL(c)
}
PROCESS_END()
}
Process的操作
void process_init(void)
初始化process管理数据和链表
process_start(struct process *p, const char *arg)
将process加入到process链表中,并调度一次
void process_exit(struct process *p)
退出process
void process_poll(struct process *p)
触发一个指定process去poll一次
2. Event
contiki event默认支持32个event,以数组的形式管理,先进后出,因此有可能会出现最开始发生的event最后处理。对于同步的event,直接调用process的处理。对于异步event,先将event放到event数组内,在以后的调度中处理event。
Event的结构
struct event_data {
process_event_t ev;
process_data_t data;
struct process *p;
};
一个event占用1+3+3=7个字节
ev:事件类型标识
PROCESS_EVENT_NONE
PROCESS_EVENT_INIT
PROCESS_EVENT_POLL
PROCESS_EVENT_EXIT
PROCESS_EVENT_SERVICE_REMOVED
PROCESS_EVENT_CONTINUE
PROCESS_EVENT_MSG
PROCESS_EVENT_EXITED
PROCESS_EVENT_TIMER
PROCESS_EVENT_COM
PROCESS_EVENT_MAX 自定义事件的起始值
data: event自带数据
p: 事件发向那个process,process为NULL的话就是广播事件
Event的操作
process_event_t process_event_t process_alloc_event(void)
分配一个event
int process_post(struct process *p, process_event_t ev, process_data_t data)
异步post event,发送后立即返回,在以后的调度中处理event
void process_post_synch(struct process *p, process_event_t ev, process_data_t data)
同步post event,发送阻塞,直到event被process处理完成为止
int process_nevents(void);
查询还有多少个event未处理, process poll被看做是一个优先级高于其它event的特殊event。
3. Schedule
调度分为两个步骤,一是处理poll,二是处理event。
int
process_run(void)
{
/* Process poll events. */
if(poll_requested) {
do_poll();
}
/* Process one event from the queue */
do_event();
return nevents + poll_requested;
}
do_poll()
遍历process list,将其中是poll的process都执行一遍,一次调度执行一次这个动作。
do_event()
去除event数组的最末尾的event,并交由对应的process处理,一次调度只能处理一个event。
call_process(struct process *p, process_event_t ev, process_data_t data)
process处理event的函数,当一个process处理了event后,将被设置为PROCESS_STATE_CALLED状态,那么下次调度就不会被处理,将CPU时间可以让给其它的process。如果一个process的状态时PROCESS_STATE_CALLED那么在这次调度后会将状态再设置为PROCESS_STATE_RUNNING以重新得到调度的权利。
4. Timer
contiki core包含有5中类型的timer,所有timer的根基是clock,都是从clock得到时钟,所有contiki core的移植就是实现clock为系统提供基准即可。
timer
系统时钟timer,以tick为单位提供时间到期检查功能
timer结构
struct timer {
clock_time_t start;
clock_time_t interval;
};
start :timer的开始时间
interval:timer持续时间
一个timer占用内存的大小取决于clock_time_t的粒度
timer的操作
void timer_set(struct timer *t, clock_time_t interval)
设置一个timer
void timer_reset(struct timer *t)
reset timer,timer的启动时间将被设置到timer到期的时间
void timer_restart(struct timer *t)
从当前时间开始重启一个timer
int timer_expired(struct timer *t)
检查timer是否到期
clock_time_t timer_remaining(struct timer *t)
检查timer还有多久到期
stimer
秒 timer,以s为单位提供时间到其他检查功能,功能类似于timer,只是以s为单位
etimer
event timer,一个etimer必须绑定到一个process上,etimer到期后就发PROCESS_EVENT_TIMER到绑定的process上。etimer基于etimer_process和timer来实现的,etimer_process只响应两类event
PROCESS_EVENT_EXITED和PROCESS_EVENT_POLL。PROCESS_EVENT_EXITED是通知一个绑定了etimer的process exit,对应的etimer需要被删除。PROCESS_EVENT_POLL是让etimer_process检查是否有etimer到期。PROCESS_EVENT_POLL是由etimer_request_poll()产生,而在正常的etimer检查情况下etimer_process是不会自己产生PROCESS_EVENT_POLL的,所以需要调度call etimer_request_poll()来触发etimer process检查。
当etimer检查到timer到期,会发生PROCESS_EVENT_TIMER到绑定的process,如果发送失败,etimer_process会再自己call etimer_request_poll()来触发一次检查。如果发生成功了etimer就会从timer list中删除,因此一个etimer启动后到期一次就失效了。
etimer结构
etimer以链表的形式管理,并使用timer来实现
struct etimer {
struct timer timer;
struct etimer *next;
struct process *p;
};
timer: 到期检查timer
next:指向下一个timer
p:指向绑定的process
因此一个etimer占用的内存为3+3+timer size
etimer操作
void etimer_set(struct etimer *et, clock_time_t interval)
设置并启动一个etimer
void etimer_reset(struct etimer *et)
reset etimer,可以触发一个失效etimer重启
void etimer_restart(struct etimer *et)
restart etimer,可以触发一个失效etimer重启
void etimer_adjust(struct etimer *et, int td)
调整etimer时间
clock_time_t etimer_expiration_time(struct etimer *et)
获取etimer到期的时间
clock_time_t etimer_start_time(struct etimer *et)
获取etimer开始的时间
int etimer_expired(struct etimer *et)
检查etimer是否到期
void etimer_stop(struct etimer *et)
停止一个etimer
void etimer_request_poll(void)
触发etimer_process poll
int etimer_pending(void)
检查是否还有etimer在工作
clock_time_t etimer_next_expiration_time(void)
获取所有etimer的到期时间总和,可以以此为判断在调度器内来决定是否要触发etimer_process poll
ctimer
ctimer是以etimer为基础,以list形式管理。创建ctimer_process,当有etimer 到期后, ctimer_process将收到,PROCESS_EVENT_TIMER,process根据event data找到时那个ctimer,再执行ctimer的创建时的callback.
Ctimer也是和一个process绑定的,当执行callback时,current的process是这个callback绑定的process。
ctimer结构
struct ctimer {
struct ctimer *next;
struct etimer etimer;
struct process *p;
void (*f)(void *);
void *ptr;
};
next: 指向下一个ctimer
etimer: ctimer使用的etimer
p: 与ctimer绑定的process
f: ctimer的callback
ptr: ctimer callback的参数
ctimer的操作
void ctimer_init(void)
初始化ctimer process和链表
void ctimer_set(struct ctimer *c, clock_time_t t,void (*f)(void *), void *ptr)
创建并启动一个ctimer
void ctimer_reset(struct ctimer *c)
void ctimer_restart(struct ctimer *c)
void ctimer_stop(struct ctimer *c)
int ctimer_expired(struct ctimer *c)
rtimer
需要平台支持,在硬件timer里面实现这个实时timer.