Protothread 機制


一、概述

很多傳感器操作系統都是基於事件驅動模型的,事件驅動模型不用為每個進程都分配一個進程棧,這對內存資源受限的無線傳感器網絡嵌入式系統尤為重要。

然而事件驅動模型不支持阻塞等待抽象語句,因此程序員通常用狀態機來實現控制流,但這都很復雜。

 

例子:一個假想的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代碼實現

 

(1)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編譯器

 

(2)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代碼實現

 

(1)PT_INIT

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

初始化Protothread,初始化必須在執行進程實體前初始化。

pt是指向pt結構體的指針

底層也就是初始化LC

 

(2)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,跳轉到上次阻塞的位置后,這次就不會退出了,接着運行。

 

(3)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成立

 

(4)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

 

(5)PT_THREAD

#define PT_THREAD(name_args) char name_args

聲明或者定義進程實體函數,name_args包括函數名和參數。

 

(6)PT_RESTART

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

重新執行進程實體函數。

 

(7)PT_EXIT

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

強制退出進程實體函數。

 

(8)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成立為止

 

三、參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM