本文给出两个交互进程的实例,先给出程序源代码,运行截图,接着解读该程序,最后深入源码详细分析。
创建两个进程,一个打印Hello,另一个打印World,使其交互打印。
1. 源代码
声明进程print_hello_process
和print_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. 运行结果截图
3. 程序解读
系统启动,执行一系列初始化(串口、时钟、进程等),接着启动系统进程etimer_process
,而后启动进程print_hello_process
和print_world_process
。那么print_hello_process
与print_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_ready
给print_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()?为了程序可预测性?
}