窥探contiki的event-driven模型

event和process的关系是怎样体现出来的?

在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
先来看看一段包含了timer的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();
}

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为间隔时间。所以说struct timer is a passive timer, only keeps trackof its expiration time.

而struct etimer is an active timer, sends an event whenit expires.

一个etimer类型的数据,包含了一个timer,以及下一个etimer,同时也包含了一个进程, 根据介绍,初步理解,当etimer的timer到期时,就会发送一个事件从而invoke这个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的函数定义如下:

void
timer_set(struct timer *t, clock_time_t interval)
{
  t->interval = interval;
  t->start = clock_time();
}
clock_time()是返回一个当前的时刻,开机初始化为0,会随着系统的运行而增加。在hello_world这个进程中,是吧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中有定义:

PROCESS(etimer_process, "Event timer");

并且也定义了etimer_process进程的函数,只是没有让它自启动罢了:

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.

在一个题目为“ProgrammingContiki Crash Course”作者是“Kista,Sweden”的ppt上介绍说,有两种方法可以让一个进程运行,一个是Post an event, 即传递一个事件,包含了process_post(process_ptr, eventno, ptr)和process_post_synch(process_ptr, eventno, ptr)这两个函数;一个是Poll the process,这个至今没怎么想通,它有一个函数就是process_poll(process_ptr)。猜测是前一个是app所为,后一个是system所为。

暂不知何意,先过之。add_timer()就是把这个etimer加入到etimer链表timerlist里面去,,并且把当前的process注册到这个etimer中。update_time()是遍历所timerlist,更新下一个最近的到期时刻next_expiration,etimer_set就先到此。etimer_set(&timer, CLOCK_CONF_SECOND);一秒钟产生一个事件。

2.PROCESS_WAIT_EVENT 

这个宏是用来等待一个传递给进程的事件,它会使进程阻塞直到process收到了一个event。
看看这个宏展开:
#define PROCESS_WAIT_EVENT()        PROCESS_YIELD()
#define PROCESS_YIELD()             PT_YIELD(process_pt) //Yield the currently running process.
#define PT_YIELD(pt)				\
  do {						\
    PT_YIELD_FLAG = 0;				\
    LC_SET((pt)->lc);				\
    if(PT_YIELD_FLAG == 0) {			\
      return PT_YIELDED;			\
    }						\
  } while(0)
所以最后PROCESS_WAIT_EVENT宏展开来如下:
do {						\
    PT_YIELD_FLAG = 0;				\
    (process_pt)->lc = 38; case 38:;				\
    if(PT_YIELD_FLAG == 0) {			\
      return 1;			\
    }						\
  } while(0)
这其实就是protothread的精华,当Protothread程序运行到PT_WAIT_UNTIL时,判断其运行条件是否满足,若不满足,则阻塞。通过PROCESS_WAIT_EVENT宏展开的程序代码可以得知,Protothread的阻塞其实质就是函数返回,只不过在返回前保存了当前的阻塞位置,待下一次Protothread被调用时,直接跳到阻塞位置执行,再次判断运行条件是否满足,并执行后续程序或继续阻塞。 

3.Got a timer's event

接着看下一段代码,如下:
/* Got the timer's event~ */
    if (ev == PROCESS_EVENT_TIMER) {
      printf("Hello, world #%i\n", count);
      count++;
#define PROCESS_EVENT_TIMER           0x88
如果protothread收到了一个PROCESS_EVENT_TIMER事件,就打印hello,world。

4.整段代码展开如下:

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();
                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)
                {
                    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;
    }
}
程序的大致执行过程已经比较明了了,当程序执行到PROCESS_WAIT_EVENT的时候,基于protothread的机制程序会阻塞,即return 1,return的值有3种情况,这里要注意:
#define PT_WAITING 0
#define PT_YIELDED 1
#define PT_EXITED  2
#define PT_ENDED   3
然后退出process_thread_hello_world_process这个函数,然后这一点搞不明白了,下一次再进入到这个函数时就会直接跳到(process_pt)->lc = 38; case 38:;这一行语句,然后可以打印出hello world,假如没有while语句程序将return PT_ENDED接着退出。所以这里将继续循环,进入下一个等待周期,继续等待etimer给它传送PROCESS_EVENT_TIMER事件。让程序一直运行下去就要归功于main函数里面的while循环了。

5.看看main函数

程序为何能一直运行,要看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中有以下说明:
/**
 * 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.
 */

接着来看看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 关于这两者的描述吧:
do_poll()//Call each process' poll handler//调用轮询处理程序?
do_event()// Process the next event in the event queue and deliver it to listening processes.
只要poll_requested==1或者nevents>0循环就会一直执行。第二个好理解一点,从事件队列里取出一个事件然后传送给一个监听的进程,第一个不清楚,暂搁置之。看看do_poll()函数的代码。
/*---------------------------------------------------------------------------*/
/*
 * 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_set函数中就有将etimer_process的needspoll标记为1。这样的进程可能具有更高的优先级,先取出来优先执行。

观看do_poll的代码,poll应该就是“轮询”之意,process结构体中有unsigned char型的needspoll变量,如果needspoll不为零的话,就参加这个轮询。do_poll这个函数就是把进程队列里要参加轮询的process(即needspoll不为零的process)取出来,把它的state改为PROCESS_STATE_RUNNING,并把needspoll变量置为零,最后在调用call_process使其运行,给PROTOTHREAD传递的是一个PROCESS_EVENT_POLL事件,但这样有什么意义,还是没明白。继续看下面。因为hello_world这个process是这样声明的:
struct process hello_world_process = { 0, "Hello world process", process_thread_hello_world_process};
如此声明的process的needspoll变量都是默认为0。
接下来看看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];

然而这里就有一个疑惑了,在main函数的autostart_start()函数以及process_run()函数中都没有看到对事件队列的操作,它是怎么初始化的呢?那么还得往前看main函数。
看看main函数中的这两个语句:
PROCINIT(&etimer_process, &tcpip_process, &sensors_process);

procinit_init();
这是初始化三个系统进程的语句,ttimer_process,tcp_ip_process,sensor_process。
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_EXIT事件,接着把这个退出的进程所对应的etimer从timerlist中清除;
第三件事,接受PROCESS_EVENT_POLL事件,然后再从事件队列里取出已经到期的etimer,并执行相应的process。
etimer_process只处理上面个两种类型的数据。












评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值