码迷,mamicode.com
首页 > 其他好文 > 详细

Protothread 机制

时间:2016-08-22 18:22:26      阅读:240      评论:0      收藏:0      [点我收藏+]

标签:

一、概述

很多传感器操作系统都是基于事件驱动模型的,事件驱动模型不用为每个进程都分配一个进程栈,这对内存资源受限的无线传感器网络嵌入式系统尤为重要。

然而事件驱动模型不支持阻塞等待抽象语句,因此程序员通常用状态机来实现控制流,但这都很复杂。

 

例子:一个假想的MAC层协议

技术分享

 

状态机实现

 技术分享

实现上述代码,需要先提炼出准确特定的状态state,上述代码有三个状态:ON、OFF、WAITING

要提炼出这几个状态并不简单,而且状态机实现后的代码跟系统功能没有相互对应,可阅读性差。

 

Contiki采用一种Protothread机制,来化简这个问题。

Protothread可以看作是事件驱动进程的结合,从进程中继承了“阻塞等待”语义,如Protothread提供PT_WAIT_UNTIL阻塞语句。

Protothread从事件驱动中继承了“低内存开销”和“无栈性(所有进程共用一个栈)”。

 

Protothread实现:

技术分享

二、实现

1、几个概念

这里要先明确几个概念:Process,Protothread,LC(Local Continuation)

Process是进程,包括两个部分。其中Process Control Block是控制进程的数据结构,The Process Thread是进程执行实体函数。

Process Control Block:

struct process {
  struct process *next;
#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));
  struct pt pt;
  unsigned char state, needspoll;
};

The Process Thread:

PROCESS_THREAD(hello_world_process, ev, data)
{
  PROCESS_BEGIN();

  printf("Hello, world\n");
  
  PROCESS_END();
}

Protothread是contiki进程采用的一种机制,结合了事件驱动和进程的特点。

相应数据结构pt

struct pt {
  lc_t lc;
};

LC是local continuation,是Protothread机制的底层支持,用来保存进程运行状态的地方,其实就是保存进程实体函数上次阻塞的位置

lc_t lc

这几个概念对后续理解contiki进程运行过程有很大帮助。

2、LC代码实现

 GCC c 语言拓展实现

lc_t类型如下,是一个指向void的指针:

typedef void * lc_t;

LC_SET(s)采用GCC _label_拓展特性 定义一个标号 resume,然后用 GCC && 拓展特性将标号resume的地址存储在s中,记录阻塞位置s是lc_t类型。

#define LC_SET(s)                                 do { ({ __label__ resume; resume: (s) = &&resume; }); }while(0)

 LC_RESUME(s)采用goto语句来恢复到上次阻塞的位置,与LC_SET(s)相对应。

#define LC_RESUME(s)                              do {                                              if(s != NULL) {                                   goto *s;                                      }                                             } while(0)

  执行前,s初始化为null

#define LC_INIT(s) s = NULL

LC_END(s)为空

#define LC_END(s)

注:这种方法只支持GCC编译器

C Switch 语句实现

lc_t类型如下,是short型

typedef unsigned short lc_t;

LC_SET(s)采用标准__LINE__宏语句,将阻塞时程序执行到的行号记录到s中。

#define LC_SET(s) s = __LINE__; case __LINE__:

LC_RESUME(s)采用switch语句,恢复到上次阻塞的位置,与LC_SET(s)相对应。

#define LC_RESUME(s) switch(s) { case 0:

执行前s初始化为0。

#define LC_INIT(s) s = 0;

和LC_RESUME(s)中的switch() {相对应。

#define LC_END(s) }

注:这种方法不可嵌套switch语句

注:上述两种方法局部变量在阻塞时都不会保存,可加static关键字解决这个问题。

3、pt代码实现

 PT_INIT

#define PT_INIT(pt)   LC_INIT((pt)->lc)

初始化Protothread,初始化必须在执行进程实体前初始化。

pt是指向pt结构体的指针

底层也就是初始化LC

PT_BEGIN、PT_YIELD、PT_END

#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_YIELD(pt)                  do {                            PT_YIELD_FLAG = 0;                    LC_SET((pt)->lc);                    if(PT_YIELD_FLAG == 0) {                  return PT_YIELDED;                }                          } while(0)

PT_BEGIN中,先设置PT_YIELD_FLAG为1,表示已经YIELD过了,配合YIELD命令

然后执行LC_RESUME恢复到上次阻塞的地方,如果是第一次运行,则从头开始运行。

 

PT_END中,只是LC_END,跟PT_BEGIN配合。还有重新做一些初始化工作,并返回PT_ENDED。

 

PT_YIELD中,功能是进程无条件阻塞

第一次运行时,先设置PT_YIELD_FLAG为0,然后保存这次无条件阻塞的位置,进程实体函数返回PT_YIELDED值,退出。

YIELD后,重新执行进程实体时,执行PT_BEGIN后,PT_YIELD_FLAG变为1,跳转到上次阻塞的位置后,这次就不会退出了,接着运行。

PT_WAIT_UNTIL

#define PT_WAIT_UNTIL(pt, condition)              do {                            LC_SET((pt)->lc);                    if(!(condition)) {                      return PT_WAITING;                }                          } while(0)

先用LC_SET保存阻塞时的位置

然后判断条件condition是否成立,如果不成立,进程实体函数返回PT_WAITING值,退出。

一直阻塞,直到condition成立

PT_SPAWN

#define PT_SPAWN(pt, child, thread)          do {                            PT_INIT((child));                    PT_WAIT_THREAD((pt), (thread));          } while(0)

pt,child都是指向结构体pt的指针,pt是父进程的,child是子进程的。

thread是指向子进程的执行实体函数的指针

PT_INIT((child))先初始化子protothread

#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread))
#define PT_WAIT_WHILE(pt, cond)  PT_WAIT_UNTIL((pt), !(cond))
#define PT_SCHEDULE(f) ((f) < PT_EXITED)

PT_WAIT_WHILE是当条件cond成立时,一直阻塞。PT_WAIT_UNTIL是一直阻塞,直到condition成立。

PT_SCHEDULE(f)判断进程执行实体函数f是否已经退出或者执行完毕。

最后展开为:

PT_WAIT_UNTIL((pt), !((thread) < PT_EXITED)

 也就是父进程一直阻塞,直到子进程退出(PT_EXITED)或者执行完毕(PT_ENDED),返回值的相关定义如下

#define PT_WAITING 0
#define PT_YIELDED 1
#define PT_EXITED  2
#define PT_ENDED   3

PT_THREAD

#define PT_THREAD(name_args) char name_args

 

声明或者定义进程实体函数,name_args包括函数名和参数。

PT_RESTART

#define PT_RESTART(pt)                  do {                            PT_INIT(pt);                    return PT_WAITING;              } while(0)

 

重新执行进程实体函数。

PT_EXIT

#define PT_EXIT(pt)                  do {                            PT_INIT(pt);                    return PT_EXITED;              } while(0)

 

强制退出进程实体函数。

PT_YIELD_UNTIL

#define PT_YIELD_UNTIL(pt, cond)          do {                            PT_YIELD_FLAG = 0;                    LC_SET((pt)->lc);                    if((PT_YIELD_FLAG == 0) || !(cond)) {          return PT_YIELDED;                }                          } while(0)

 

YIELD直到条件cond成立为止

三、参考资料

Protothread 机制

标签:

原文地址:http://www.cnblogs.com/songdechiu/p/5793717.html

(1)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!