contiki-事件調度


  事件驅動機制廣泛應用於嵌入式系統,類似於中斷機制,當有事件到來時(比如按鍵、數據到達),系統響應並處理該事件。相對於輪詢機制,事件機制優勢很明星,低功耗(系統處於休眠狀態,當有事件到達時才被喚醒)和MCU利用率高。

  Contiki將事件機制融入Protothreads機制,每個事件綁定一個進程(廣播事件例外),進程間的消息也是通過事件來傳遞的。用無符號字符型來標示事件。事件結構體event_data定義如下:

struct event_data {
  process_event_t ev;
  process_data_t data;
  struct process *p;
};
typedef unsigned char process_event_t;
typedef void *        process_data_t;

  用無符號字符型標識一個事件,Contiki定義了10個事件(0x80~0x8A),其它的供用戶使用。

#define PROCESS_EVENT_NONE            0x80
#define PROCESS_EVENT_INIT            0x81
#define PROCESS_EVENT_POLL            0x82
#define PROCESS_EVENT_EXIT            0x83
#define PROCESS_EVENT_SERVICE_REMOVED 0x84
#define PROCESS_EVENT_CONTINUE        0x85
#define PROCESS_EVENT_MSG             0x86
#define PROCESS_EVENT_EXITED          0x87
#define PROCESS_EVENT_TIMER           0x88
#define PROCESS_EVENT_COM             0x89
#define PROCESS_EVENT_MAX             0x8a

  每個事件綁定一個進程,如果p為NULL,表示該事件綁定所有進程(即廣播事件PROCESS_BROADCAST)。除此之外,事件可以攜帶數據data,可以用着電進行進程間的通信(向另一進程傳遞帶數據的事件)。

static process_num_events_t nevents, fevent;
static struct event_data events[PROCESS_CONF_NUMEVENTS];

#define PROCESS_CONF_NUMEVENTS 32

  Contiki用一個全局的靜態數組存放事件,這意味着事件數目在系統運行之前就要指定(用戶可以通過PROCESS_CONF_NUMEVENTS自選配置大小),通過數組下標可以快速訪問事件。系統還定義另兩個全局靜態變量neventsfevent,分別用於記錄未處理事件總數及下一個待處理的位置。事件邏輯組成環形隊列,存儲在數組里。如下圖:

  可見,對於Contiki系統而言,事件並沒有優先級之分,而是先到先服務的策略,全局變量fevent記錄了下一次待處理事件的下標。

事件產生

  Contiki有兩種方式產生事件,即同步和異步。同步事件通過process_post_synch函數產生,事件觸發后直接處理(調用call_process函數)。而異步事件產生是由process_post產生,並沒有及時處理,而是放入事件隊列等待處理,process_post流程圖如下:

 1 int process_post(struct process *p, process_event_t ev, process_data_t data)
 2 {
 3   static process_num_events_t snum;
 4 
 5   if(PROCESS_CURRENT() == NULL) {
 6     PRINTF("process_post: NULL process posts event %d to process '%s', nevents %d\n",
 7        ev,PROCESS_NAME_STRING(p), nevents);
 8   } 
 9   else {
10     PRINTF("process_post: Process '%s' posts event %d to process '%s', nevents %d\n",
11        PROCESS_NAME_STRING(PROCESS_CURRENT()), ev,
12        p == PROCESS_BROADCAST? "<broadcast>": PROCESS_NAME_STRING(p), nevents);
13   }
14   /*nevents未處理事件總數,事件隊列滿,返回PROCESS_ERR_FULL*/
15   if(nevents == PROCESS_CONF_NUMEVENTS) {
16 #if DEBUG
17     if(p == PROCESS_BROADCAST) {
18       printf("soft panic: event queue is full when broadcast event %d was posted from %s\n", ev, PROCESS_NAME_STRING(process_current));
19     } else {
20       printf("soft panic: event queue is full when event %d was posted to %s frpm %s\n", ev, PROCESS_NAME_STRING(p), PROCESS_NAME_STRING(process_current));
21     }
22 #endif /* DEBUG */
23     return PROCESS_ERR_FULL;
24   }
25   //事件隊列未滿,繼續
26   snum = (process_num_events_t)(fevent + nevents) % PROCESS_CONF_NUMEVENTS;//取得循環隊列中下一個空閑位置
27   events[snum].ev = ev;//將事件加入隊列
28   events[snum].data = data;
29   events[snum].p = p;
30   ++nevents;
31 
32 #if PROCESS_CONF_STATS
33   if(nevents > process_maxevents) {
34     process_maxevents = nevents;
35   }
36 #endif /* PROCESS_CONF_STATS */
37   
38   return PROCESS_ERR_OK;
39 }

  process_post首先判斷事件隊列是否已滿,若滿返回錯誤,否則取得下一個空閑位置(因為環形隊列,需要做余操作),而后,設置該事件並將未處理事件總數加1。

事件調度

  事件沒有優先級,采用先到先服務策略,每一次系統輪詢(process_run函數)只處理一個事件,do_event函數用於處理事件,其流程圖如下:

 1 /*
 2  * Process the next event in the event queue and deliver it to
 3  * listening processes.
 4  */
 5 /*---------------------------------------------------------------------------*/
 6 static void
 7 do_event(void)
 8 {
 9   static process_event_t ev;
10   static process_data_t data;
11   static struct process *receiver;
12   static struct process *p;
13   
14   /*
15    * If there are any events in the queue, take the first one and walk
16    * through the list of processes to see if the event should be
17    * delivered to any of them. If so, we call the event handler
18    * function for the process. We only process one event at a time and
19    * call the poll handlers inbetween.
20    */
21 
22   if(nevents > 0) {
23     
24     /* There are events that we should deliver. */
25       /*取出待處理事件*/
26     ev = events[fevent].ev;
27     
28     data = events[fevent].data;
29     receiver = events[fevent].p;
30 
31     /* Since we have seen the new event, we move pointer upwards
32        and decrese the number of events. */
33       /*更新fevent和nevents*/
34     fevent = (fevent + 1) % PROCESS_CONF_NUMEVENTS;
35     --nevents;
36 
37     /* If this is a broadcast event, we deliver it to all events, in
38        order of their priority. */
39       /*是否廣播事件*/
40     if(receiver == PROCESS_BROADCAST) {
41       for(p = process_list; p != NULL; p = p->next) {
42 
43         /* If we have been requested to poll a process, we do this in
44            between processing the broadcast event. */
45          /*有高優先級進程,運行所有高優先級進程*/
46         if(poll_requested) {
47           do_poll();
48         }
49         call_process(p, ev, data);//處理事件
50       }
51     } 
52     else {
53           /* This is not a broadcast event, so we deliver it to the
54          specified process.不是廣播事件,提供給特定進程 */
55           /* If the event was an INIT event, we should also update the
56          state of the process. 如果是初始化事件,設置進程狀態為RUNNING*/
57         if(ev == PROCESS_EVENT_INIT) {
58         receiver->state = PROCESS_STATE_RUNNING;
59       }
60 
61       /* Make sure that the process actually is running. */
62       call_process(receiver, ev, data);//處理事件
63     }
64   }
65 }

  do_event首先取出該事件(即,將事件的值復制到一個新變量),更新總的未處理事件總數及下一個待處理事件的數組下標(環形隊列,需要取余操作)。接着判斷事件是否為廣播事件PROCESS_BROADCAST,若是,考慮到處理廣播事件可能需要更多的時間,為保證系統實時性,先運行高優先級的進程,而后再去處理事件(調用call_process函數)。如果事件是初始化事件PROCESS_EVENT_INIT(創建進程的時候會觸發此事件),需要將進程狀態設為PROCESS_STATE_RUNNING

事件處理

  實際的事件處理是在進程的函數體thread,正如上文所述,call_process會調用thread函數,執行該進程。關鍵代碼如下:

ret = p->thread(&p->pt, ev, data);

參考Jelline大神博客: http://jelline.blog.chinaunix.net


免責聲明!

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



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