本文给出两个交互进程的实例,先给出程序源代码,运行截图,接着解读该程序,最后深入源码详细分析。

创建两个进程,一个打印Hello,另一个打印World,使其交互打印。

1. 源代码

声明进程print_hello_processprint_world_process,所涉及宏参见博文《实例hello_world剖析》:

//filename:interact_two_process.c
#include "contiki.h"
#include "debug-uart.h"

static process_event_t event_data_ready;

/*声明两个进程*/
PROCESS(print_hello_process, "Hello");
PROCESS(print_world_process, "world");

AUTOSTART_PROCESSES(&print_hello_process, &print_world_process);  //让该两进程自启动

定义进程print_hello_process

/*定义进程print_hello_process*/
PROCESS_THREAD(print_hello_process, ev, data)
{
  PROCESS_BEGIN();
  static struct etimer timer;

  etimer_set(&timer, CLOCK_CONF_SECOND / 10); //#define CLOCK_CONF_SECOND 10将timer的interval设为1 详情见4.1

  usart_puts("***print hello process start***\n");

  event_data_ready = process_alloc_event(); //return lastevent++; 新建一个事件,事实上是用一组unsigned char值来标识不同事件

  while (1)
  {
    PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); //详情见4.2,即etimer到期继续执行下面内容

    usart_puts("Hello\n");

    process_post(&print_world_process, event_data_ready, NULL); //传递异步事件给print_world_process,直到内核调度该进程才处理该事件。详情见4.3

    etimer_reset(&timer); //重置定时器,详情见4.4
  }

  PROCESS_END();
}

定义进程print_world_process

/*定义进程print_world_process*/
PROCESS_THREAD(print_world_process, ev, data)
{
  PROCESS_BEGIN();

  usart_puts("***print world process start***\n");

  while (1)
  {
    PROCESS_WAIT_EVENT_UNTIL(ev == event_data_ready);
    usart_puts("world\n");
  }

  PROCESS_END();
}

2. 运行结果截图

result-two-interactive

3. 程序解读

系统启动,执行一系列初始化(串口、时钟、进程等),接着启动系统进程etimer_process,而后启动进程print_hello_processprint_world_process。那么print_hello_processprint_world_process是怎么交互的呢?

进程print_hello_process一直执行到PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER),此时etimer还没到期,进程被挂起。转去执行print_world_process,待执行到PROCESS_WAIT_EVENT_UNTIL(ev == event_data_ready)被挂起(因为print_hello_process还没post事件)。而后再转去执行系统进程etimer_process,若检测到etimer到期,则继续执行print_hello_process,打印Hello,并传递事件event_data_readyprint_world_process,初始化timer,待执行到PROCESS_WAIT_EVENT_UNTIL(while死循环),再次被挂起。转去执行print_world_process,打印world,待执行到PROCESS_WAIT_EVENT_UNTIL(ev == event_data_ready)又被挂起。再次执行系统进程etimer_process,如此反复执行。

4. 源码详解

4.1 etimer_set函数

//etimer_set(&timer, CLOCK_CONF_SECOND/10);
void etimer_set(struct etimer *et, clock_time_t interval)
{
  timer_set(&et->timer, interval); //设置定时器timer,源码见2.1.1
  add_timer(et);    //将这个etimer加入timerlist
}

4.1.1 timer_set函数

//用当前时间初始化start,并设置间隔interval
void timer_set(struct timer *t, clock_time_t interval)
{
  t->interval = interval;
  t->start = clock_time(); //return current_clock;
}

clock_time_t clock_time(void)
{
  return current_clock;
}

static volatile clock_time_t current_clock = 0;
typedef unsigned int clock_time_t;

4.1.2 add_timer函数

/*将etimer加入timerlist,并update_time(),即求出下一个到期时间next_expiration*/
static void add_timer(struct etimer *timer)
{
  struct etimer *t;

  etimer_request_poll(); //事实上执行process_poll(&etimer_process),即将进程的needspoll设为1,使其获得更高优先级,详情见下方

  /*参数验证,确保该etimer不是已处理过的*/
  if (timer->p != PROCESS_NONE) //如果是PROCESS_NONE,则表示之前处理过,该etimer已到期(注1)
  {
    for (t = timerlist; t != NULL; t = t->next)
    {
      if (t == timer) /* Timer already on list, bail out. */
      {
        update_time(); //即求出下一个到期时间next_expiration(全局静态变量),即还有next_expiration时间,就有timer到期
        return ;
      }
    }
  }

  /*将timer加入timerlist*/
  timer->p = PROCESS_CURRENT();
  timer->next = timerlist;
  timerlist = timer;

  update_time(); //即求出下一个到期时间next_expiration(全局静态变量),即还有next_expiration时间,就有timer到期
}

注1:关于PROCESS_NONE可以参考博文《系统进程etimer_process》。

函数etimer_request_poll直接调用process_poll,一步步展开如下:

void etimer_request_poll(void)
{
  process_poll(&etimer_process);
}

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; //全局静态变量,标识是否有进程的needspoll为1
    }
  }
}

4.2 PROCESS_WAIT_EVENT_UNTIL宏

//PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER);

/*宏PROCESS_WAIT_EVENT_UNTIL,直到条件c为真时,进程才得以继续向前推进*/
#define PROCESS_WAIT_EVENT_UNTIL(c) PROCESS_YIELD_UNTIL(c)

#define PROCESS_YIELD_UNTIL(c) PT_YIELD_UNTIL(process_pt, c) //Yield the currently running process until a condition occurs

#define PT_YIELD_UNTIL(pt, cond)        \
  do \
  {                        \
    PT_YIELD_FLAG = 0;                \
    LC_SET((pt)->lc);                \         //注:保存断点,下次从这里开始执行!!!
    if((PT_YIELD_FLAG == 0) || !(cond)) {    \
      return PT_YIELDED;            \
    }                        \
  } while(0)


#define LC_SET(s) s = __LINE__; case __LINE__: //保存行数,即lc被设置成该LC_SET所在的行

从源代码可以看出,第一次执行PROCESS_WAIT_EVENT_UNTIL总是被挂起(因为PT_YIELD_FLAG==0为真,会直接执行return PT_YIELDED)。

该进程下一次被调试的时候,总是从PROCESS_BEGIN()开始,而PROCESS_BEGIN宏包含两条语句:其一,将PT_YIELD_FLAG置1;再者,switch(pt->lc),从而跳转到case __LINE__(见上述源码的注)。接着判断if条件,此时PT_YIELD_FLAG=1,若条件为真,则不执行return,继续后续的内容,从而进程接着执行。

4.3 process_post函数

精简后(去除一些用于调试的代码)源码如下,详情参考博文《系统进程etimer_process》2.3节:

//process_post(&print_world_process, event_data_ready, NULL); 即把事件event_data_ready加入事件队列
int process_post(struct process *p, process_event_t ev, process_data_t data)
{
  static process_num_events_t snum;

  if (nevents == PROCESS_CONF_NUMEVENTS)
  {
    return PROCESS_ERR_FULL;
  }

  snum = (process_num_events_t)(fevent + nevents) % PROCESS_CONF_NUMEVENTS;
  events[snum].ev = ev;
  events[snum].data = data;
  events[snum].p = p;
  ++nevents;

  #if PROCESS_CONF_STATS
    if (nevents > process_maxevents)
    {
      process_maxevents = nevents;
    }
  #endif

  return PROCESS_ERR_OK;
}

4.4 etimer_reset函数

/*Reset the timer with the same interval.*/
void etimer_reset(struct etimer *et)
{
  timer_reset(&et->timer);
  add_timer(et); //详情见4.1.2
}

void timer_reset(struct timer *t)
{
  t->start += t->interval;  //为什么不t->start = clock_time()?为了程序可预测性?
}
本文系Spark & Shine原创,转载需注明出处本文最近一次修改时间 2022-03-20 17:06

results matching ""

    No results matching ""