现在的位置: 首页 > 综合 > 正文

Contiki 调度内核不完全介绍

2018年04月11日 ⁄ 综合 ⁄ 共 6743字 ⁄ 字号 评论关闭

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_YIELD_FLAG=0;    
        (pt)->lc=0;
        return PT_ENDED;                                                         
}

假设在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.

抱歉!评论已关闭.