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自选配置大小),通过数组下标可以快速访问事件。系统还定义另两个全局静态变量nevents和fevent,分别用于记录未处理事件总数及下一个待处理的位置。事件逻辑组成环形队列,存储在数组里。如下图:
可见,对于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
本文来自博客园,作者:一只奋斗的考拉,转载请注明原文链接:https://www.cnblogs.com/liu13526825661/p/6122589.html