浅探 Event_Driven 模型系列(1-4,整理加上自己的一些思考)
Contiki系统设计的最重要的两个特点就是事件驱动以及Protothread进程模型,我们来现在来探讨以下Contiki的事件驱动原理。
在contiki中,有四种timer,如下:●struct timer: Passive timer, only keeps trackof its expiration time
●struct etimer: Active timer, sends an event whenit expires
●struct ctimer: Active timer, calls a functionwhen it expires, Used by Rime
●(struct rtimer):Real-time timer, calls a functionat an exact time, Not implemented for MSP430 yet
(需要补充对contiki4种定时器的补充说明)
先来看看一段包含了etimer的hello_world程序,代码如下: PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
/* Variables are declared static to ensure their values are kept between kernel calls. */
static struct etimer timer;
static int count = 0;
/* Any process must start with this. */
PROCESS_BEGIN();
/* Set the etimer to generate an event in one second. */
etimer_set(&timer, CLOCK_CONF_SECOND);
while(1) {
/* Wait for an event. */
PROCESS_WAIT_EVENT();
/* Got the timer's event~ */
if (ev == PROCESS_EVENT_TIMER) {
printf("Hello, world #%i\n", count);
count++;
/* Reset the etimer so it will generate another event after the exact same time. */
etimer_reset(&timer);
}
} // while (1)
/* Any process must end with this, even if it is never reached. */
PROCESS_END();
}
static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);
struct process hello_world_process = { ((void *)0), "Hello world process", process_thread_hello_world_process};
struct process * const autostart_processes[] = {&hello_world_process, ((void *)0)};
static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)
{
static struct etimer timer;
static uint8_t leds_state = 0;
{
char PT_YIELD_FLAG = 1;
switch((process_pt)->lc)
{
case 0:
;
etimer_set(&timer, CLOCK_CONF_SECOND);
while(1)
{
/* Wait for an event. */
//PROCESS_WAIT_EVENT(); 与process_yeild() 相同
do
{
PT_YIELD_FLAG = 0;
(process_pt)->lc = 38; case 38:;
if(PT_YIELD_FLAG == 0)
{
return 1;
}
} while(0);
/* Got the timer's event~ */
if (ev == PROCESS_EVENT_TIMER) //由 etimer_process进程传递,意味着有timer 到期 和上面的 set 的timer 对应
{
printf("Hello, world #%i\n", count);
count++;
/* Reset the etimer so it will generate another event after the exact same time. */
etimer_reset(&timer);
}
}
};
PT_YIELD_FLAG = 0;
(process_pt)->lc = 0;;
return 3;
}
}
1.etimer_set
首先process的body定义了一个etimer类型的数据timer,etimer的定义如下:
struct etimer {
struct timer timer;
struct etimer *next;
struct process *p;
};//A timer. This structure is used for declaring a timer. The timer must be set with etimer_set() before it can be used.
struct timer {
clock_time_t start;
clock_time_t interval;
};//A timer. This structure is used for declaring a timer. The timer must be set with timer_set() before it can be used.
复制代码
一个etimer类型的数据,包含了一个timer,以及下一个etimer,同时也包含了一个进程,。当etimer的timer到期时,就会给相应的process发送一个事件从而使这个process启动,其实也就是给这个进程的PROCESS_THREAD宏参数ev传递一个值,可能是PROCESS_EVENT_TIMER。
接着对etimer类型的timer用etimer_set()进行初始化,函数的定义如下:
etimer_set(struct etimer *et, clock_time_t interval)
{
timer_set(&et->timer, interval);
add_timer(et);
}
复制代码
static void
add_timer(struct etimer *timer)
{
struct etimer *t;
etimer_request_poll();
if(timer->p != PROCESS_NONE) {
/* Timer not on list. */
for(t = timerlist; t != NULL; t = t->next) {
if(t == timer) {
/* Timer already on list, bail out. */
update_time();
return;
}
}
}
timer->p = PROCESS_CURRENT();
timer->next = timerlist;
timerlist = timer;
update_time();
}
复制代码
The function is called from timer interrupts, by the system. It the event timer aware that the clock has changed. This function is used to inform the event timer module that the system clock has been updated. Typically, this function would be called from the timer interrupt handler when the clock has ticked.
也就是说用来提醒etimer系统的时钟已经改变。
它的定义如下:
void
etimer_request_poll(void)
{
process_poll(&etimer_process);
}
复制代码
- PROCESS(etimer_process, "Event timer");
- PROCESS_THREAD(etimer_process, ev, data);
void
process_poll(struct process *p)
{
if(p != NULL) {
if(p->state == PROCESS_STATE_RUNNING ||
p->state == PROCESS_STATE_CALLED) {
p->needspoll = 1;
poll_requested = 1;
}
}
}
It is called from device drivers. Request a process to be polled. This function typically is called from an interrupt handler to cause a process to be polled. p is a pointer to the process' process structure.
有两种方法可以让一个进程运行:
1.Post an event, 包含了process_post(process_ptr, eventno, ptr)和process_post_synch(process_ptr, eventno, ptr)这两个函数,前一个函数是即把事件扔进一个事件队列里面去,然后再通过do_event()函数来轮询相应的事件传递给相应的process并让其执行,后一个函数是直接让这个进程执行,相比于前一个来说,具有更高的优先级;
2.一个是Poll the process,它有一个函数就是process_poll(process_ptr)。假如一个process被poll(其实就是process的needspoll变量被设为1)了,在执行的过程中比Post an Event具有更先执行的机会,process就不会参与事件队列的轮询,而被直接率先执行(因为在do_evnevts()中调用了do_poll(),即首先执行needspoll为1的进程)。
比如说上面的etimer_process。
add_timer()的etimer_request_poll()函数,其实也就是 poll 定时器进程etimer_process,让其有优先执行的机会。接下来add_timer()做的工作就是:
把这个etimer加入到etimer链表timerlist里面去,,并且把当前的process注册到这个etimer中。update_time()是遍历所timerlist,更新下一个最近的到期时刻next_expiration。etimer_set(&timer, CLOCK_CONF_SECOND)设置一秒钟产生一个事件。
timer是怎样产生事件的,先看看main函数的初始化:
看看main函数中的这两个语句:
PROCINIT(&etimer_process, &tcpip_process, &sensors_process);
procinit_init();//contiki 2.7 中没有对此函数的调用???
复制代码
- const struct process *procinit[] = {&etimer_process, &tcpip_process, &sensors_process, 0}
void
procinit_init(void)
{
int i;
for(i = 0; procinit[i] != NULL; ++i) {
process_start((struct process *)procinit[i], NULL);
}
}
复制代码
PROCESS_THREAD(etimer_process, ev, data)
{
struct etimer *t, *u;
PROCESS_BEGIN();
timerlist = NULL;
while(1) {
PROCESS_YIELD();
if(ev == PROCESS_EVENT_EXITED) {
struct process *p = data;
while(timerlist != NULL && timerlist->p == p) {
timerlist = timerlist->next;
}
if(timerlist != NULL) {
t = timerlist;
while(t->next != NULL) {
if(t->next->p == p) {
t->next = t->next->next;
} else
t = t->next;
}
}
continue;
} else if(ev != PROCESS_EVENT_POLL) {
continue;
}
again:
u = NULL;
for(t = timerlist; t != NULL; t = t->next) {
if(timer_expired(&t->timer)) {
if(process_post(t->p, PROCESS_EVENT_TIMER, t) == PROCESS_ERR_OK) {
/* Reset the process ID of the event timer, to signal that the etimer has expired. This is later checked in the etimer_expired() function. */
t->p = PROCESS_NONE;
if(u != NULL) {
u->next = t->next;
} else {
timerlist = t->next;
}
t->next = NULL;
update_time();
goto again;
} else {
etimer_request_poll();
}
}
u = t;
}
}
PROCESS_END();
}
复制代码
etimer_process主要干了三件事:
第一件事,第一次调用etimer_process需要做的,初始化:设置etimer队列timerlist为null;
第二件事,接受PROCESS_EVENT_EXITD事件,接着把这个退出的进程所对应的etimer从timerlist中清除;
第三件事,接受PROCESS_EVENT_POLL事件,然后再从事件队列里取出已经到期的etimer,并执行相应的process。
etimer_process只处理上面个两种类型的事件。
此处补充 contiki的事件标识符:
Event Identifier是用来标识事件的,在contiki中用一个8位的数来区分.根据标识符的不同类型做不同的处理,其中用户定义的进程的范围在0~127之间.
其它的进程是contiki的内核去定义的.定义的结果如下
#define PROCESS_EVENT_NONE 128
#define PROCESS_EVENT_INIT 129
#define PROCESS_EVENT_POLL 130
#define PROCESS_EVENT_EXIT 131
#define PROCESS_EVENT_CONTINUE 133
#define PROCESS_EVENT_MSG 134
#define PROCESS_EVENT_EXITED 135
#define PROCESS_EVENT_TIMER 136
#define PROCESS_EVNET_COM 137
#define PROCESS_EVNENT_MAX 138//在事件初始化时 ,让 lastevnet=PROCESS_EVENT_MAX,即新产生的事件从 138开始,函数process_alloc_evnet用于分配一个事件
PROCESS_EVENT_NONE : 用来隔离用户与内核定义的描述符,没有其它作用.
PROCESS_EVENT_INIT : 当初始化的时候,把事件发送到一个新的进程,这种类型只是用于进程初始化时把进程的状态切换到running.可以参考do_event()的实现.用于非广播方式向特定的进程发送信息时,才做切换处理.如果是用广播方式发送,就会对每一个进程做do_poll()操作.
PROCESS_EVENT_POLL : 事件的轮询标记,主要用于do_poll()的实现.
PROCESS_EVENT_EXIT : 用在exit_process()中,用来同步通知contiki的服务,这个进程即将结束,需要释放或解除与这个进程相关的状态.
PROCESS_EVENT_CONTINUE :由内核发给进程,等待PROCESS_YIELD() 的状态.PROCESS_PAUSE()中用这个标识才做短暂的暂停,等到标识符所表示的状态时,继续执行.
PROCESS_EVENT_MSG :主要用在IP协议栈中,但是,也能够用在进程间通信.
PROCESS_EVENT_EXITED : 用在exit_process()中,用来向非进程P的其它所有进程广播,进程P将退出.但是PROCESS_EVENT_EXIT是通知进程P中的线程退出.然后在链表中删除进程.
PROCESS_EVENT_TIMER :当事件时钟(etimer)失效时,发消息给进程
程序为何能一直运行,要看main函数的这段代码:
while(1){
int r;
do {
/* Reset watchdog. */
watchdog_periodic();
r = process_run();
} while(r > 0);
复制代码
关于process_run()函数,contiki中有以下说明:
- /**
- * Run the system once - call poll handlers and process one event.
- *
- * This function should be called repeatedly from the main() program
- * to actually run the Contiki system. It calls the necessary poll
- * handlers, and processes one event. The function returns the number
- * of events that are waiting in the event queue so that the caller
- * may choose to put the CPU to sleep when there are no pending
- * events.
- *
- * \return The number of events that are currently waiting in the
- * event queue.
- */
/*---------------------------------------------------------------------------*/
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()//Call each process' poll handler
- do_event()// Process the next event in the event queue and deliver it to listening processes.
/*---------------------------------------------------------------------------*/
/*
* Call each process' poll handler.
*/
/*---------------------------------------------------------------------------*/
static void
do_poll(void)
{
struct process *p;
poll_requested = 0;
/* Call the processes that needs to be polled. */
for(p = process_list; p != NULL; p = p->next) {
if(p->needspoll) {
p->state = PROCESS_STATE_RUNNING;
p->needspoll = 0;
call_process(p, PROCESS_EVENT_POLL, NULL);
}
}
}
复制代码
do_poll这个函数就是把进程队列里(即needspoll不为零的process)取出来,把它的state改为PROCESS_STATE_RUNNING,并把needspoll变量重置为零,最后在调用call_process使其运行,给PROTOTHREAD传递的是一个PROCESS_EVENT_POLL事件,让这个process执行,典型的是etimer_process。因为hello_world这个process是这样声明的:
- struct process hello_world_process = { 0, "Hello world process", process_thread_hello_world_process};
接下来看看do_event():
/*Process the next event in the event queue and deliver it to listening processes.*/
static void
do_event(void)
{
static process_event_t ev;
static process_data_t data;
static struct process *receiver;
static struct process *p;
/*
* If there are any events in the queue, take the first one and walk
* through the list of processes to see if the event should be
* delivered to any of them. If so, we call the event handler
* function for the process. We only process one event at a time and
* call the poll handlers inbetween.
*/
if(nevents > 0) {
/* There are events that we should deliver. */
ev = events[fevent].ev;
data = events[fevent].data;
receiver = events[fevent].p;
/* Since we have seen the new event, we move pointer upwards
and decrese the number of events. */
fevent = (fevent + 1) % PROCESS_CONF_NUMEVENTS;
--nevents;
/* If this is a broadcast event, we deliver it to all events, in
order of their priority. */
if(receiver == PROCESS_BROADCAST) {//#define PROCESS_BROADCAST NULL
for(p = process_list; p != NULL; p = p->next) {
/* If we have been requested to poll a process, we do this in
between processing the broadcast event. */
if(poll_requested) {
do_poll();
}
call_process(p, ev, data);
}
} else {
/* This is not a broadcast event, so we deliver it to the
specified process. */
/* If the event was an INIT event, we should also update the
state of the process. */
if(ev == PROCESS_EVENT_INIT) {
receiver->state = PROCESS_STATE_RUNNING;
}
/* Make sure that the process actually is running. */
call_process(receiver, ev, data);
}
}
}
复制代码
struct event_data {
process_event_t ev;
process_data_t data;
struct process *p;
};
static process_num_events_t nevents, fevent;
static struct event_data events[PROCESS_CONF_NUMEVENTS];
复制代码
总结:contiki的程序执行三要素:timer、event、process。timer也有对应的系统进程etimer_process,etimer_process维护的是一个timer列表timerlist,并且每个etimer都绑定了一个process。
在程序执行的过程当中,首先通过do_poll函数执行etimer_process,把所有到时的timer取出,然后给相应的进程通过process_post()传递一个事件,把这个事件以及对应的进程放到事件队列里面去;然后再通过do_event()函数依次取出事件队列events里面的事件,执行call_process启动相应的process。
往复执行…………