Contiki的Event_Driven 模型的整理与思考

 浅探 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.

timer的定义如下:
    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.

复制代码


timer的start为起始时刻,interval为间隔时间。所以timer只是记录了到期的时间。

一个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);
    }

复制代码


timer_set也就是初始化etimer里面的timer,start用当前的时钟初始化,interval用传递的参数;再来看看add_timer()是怎么实现的。
    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();
    }

复制代码


关于etimer_request_poll()函数,contiki上是这样描述的:
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_poll(&etimer_process), etimer也是一个进程,名字叫etimer_process,它维护的是一个etimer链表叫timerlist,在同文件etimer.c中有定义:
  1. PROCESS(etimer_process, "Event timer");

并且也定义了etimer_process进程的函数,只是没有让它自启动罢了,但是开机的时候会对etimer_process进行初始化:
  1. PROCESS_THREAD(etimer_process, ev, data);

process_poll()的定义如下:
    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;
        }
      }
    }
关于process_poll()函数的说明,如下:
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 中没有对此函数的调用???

复制代码


这是初始化三个系统进程的语句,timer_process,tcp_ip_process,sensor_process。
  1. const struct process *procinit[] = {&etimer_process, &tcpip_process, &sensors_process, 0}
接着来看看procinit_init()函数:

    void
    procinit_init(void)
    {
      int i;
      for(i = 0; procinit[i] != NULL; ++i) {
        process_start((struct process *)procinit[i], NULL);
      }
    }

复制代码

然后对各个进程执行call_process,就像执行一般的user process一样,所以接下来我们得看看etimer的PROCESS_THREAD宏,看看etimer_process的执行过程:
    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);

复制代码


watchdog_periodic()函数是和watchdog有关的,是为了防止程序跑飞(程序跑飞是指系统受到某种干扰后,程序计数器PC的值偏离了给定的唯一变化历程,导致程序运行偏离正常的运行路径.程序跑飞因素及后果往往是不可预计的.在很多情况下,程序跑飞后系统会进入死循环而导致死机,watchdog相当于系统警察,当系统发生严重错误(如程序进入死循环等)不能恢复的时候,WATCHDOG能够让系统重启。WATCHDOG的应用主要是在嵌入式操作系统中,避免了系统在无人干预时长时间挂起的情况)。
关于process_run()函数,contiki中有以下说明:
  1. /**
  2. * Run the system once - call poll handlers and process one event.
  3. *
  4. * This function should be called repeatedly from the main() program
  5. * to actually run the Contiki system. It calls the necessary poll
  6. * handlers, and processes one event. The function returns the number
  7. * of events that are waiting in the event queue so that the caller
  8. * may choose to put the CPU to sleep when there are no pending
  9. * events.
  10. *
  11. * \return The number of events that are currently waiting in the
  12. * event queue.
  13. */

接着来看看process_run(),函数展开如下:
    /*---------------------------------------------------------------------------*/
    int
    process_run(void)
    {
      /* Process poll events. */
      if(poll_requested) {
        do_poll();
      }
      /* Process one event from the queue */
      do_event();
      return nevents + poll_requested;
    }
    /*---------------------------------------------------------------------------*/

复制代码


这其中又包含了两个function,do_poll()和do_event(),看看contiki operating system 关于这两者的描述吧:
  1. do_poll()//Call each process' poll handler
  2. do_event()// Process the next event in the event queue and deliver it to listening processes.

只要poll_requested==1或者nevents>0循环就会一直执行。
    /*---------------------------------------------------------------------------*/
    /*
    * 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);
        }
      }
    }

复制代码


只要存在process的needspoll值为1,那么poll_requestd的值就为1,那么就执行do_poll函数,将process_list中needspoll为1的进程取出来执行,并给这些进程传递一个PROCESS_EVENT_POLL事件。要将一个进程的needspoll标记为1,那么就得执行process_poll()函数。etimer_process在注册到一个process的时候就会执行etimer_request_poll()这个函数,使其needspoll值为 1,。被poll过的进程可能具有更高的优先级,不需要进入事件队列来等待,优先执行。

do_poll这个函数就是把进程队列里(即needspoll不为零的process)取出来,把它的state改为PROCESS_STATE_RUNNING,并把needspoll变量重置为零,最后在调用call_process使其运行,给PROTOTHREAD传递的是一个PROCESS_EVENT_POLL事件,让这个process执行,典型的是etimer_process。因为hello_world这个process是这样声明的:
  1. struct process hello_world_process = { 0, "Hello world process", process_thread_hello_world_process};

如此声明的process的needspoll变量都是默认为0,所以只能靠do_event()来给它传递一个事件之后才能使其执行。
接下来看看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);
        }
      }
    }

复制代码


events是一个事件队列,nevents也就是事件队列里的事件数,fevent就是事件队列里下一个要传递的事件的位置,do_event干的事就是把下一个事件(fevent指向的事件)从事件队列里取出来,然后传递给相应的正在监听的process,进而调用它,使其执行
    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。

往复执行…………

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值