contiki学习笔记之hello_world

hello_world是当中最简单的程序了。程序代码如下:

/* Declare the process */
PROCESS(hello_world_process, “Hello world”);
/* Make the process start when the module is loaded */
AUTOSTART_PROCESSES(&hello_world);

/* Define the process code */
PROCESS_THREAD(hello_world_process, ev, data) {
  PROCESS_BEGIN();            /* Must always come first */
  printf(“Hello, world!\n”);  /* Initialization code goes here */
  while(1) {                  /* Loop for ever */
    PROCESS_WAIT_EVENT();     /* Wait for something to happen */
  }
  PROCESS_END();              /* Must always come last */
}

A process in Contiki consists of a single protothread.

protothread是contiki的进程控制模型,是contiki关于进程的精华知识,详情可参考http://dak664.github.com/contiki-doxygen/a01676.html

其它关于contiki的资料也可参考http://dak664.github.com/contiki-doxygen/,里面介绍了contiki的关于process、protothread、timer、以及communication stack的一些内容,归纳和总结了里面的一些库函数的用法,其实在contiki的源码中都可以找到。

contiki程序有着非常规范的程序步骤,PROCESS宏是用来声明一个进程;AUTOSTART宏是使这个进程开机自启动,PROCESS_THREAD里面定义的是程序的主体,并且主体内部要以PROCESS_BEGIN()开头,以PROCESS_END()来结束。

接下来一个一个宏展开来看清它们的真面目:

1.PROCESS

PROCESS宏定义在core\sys的process.h文件内,如下:

#define PROCESS(name, strname)				\
  PROCESS_THREAD(name, ev, data);			\
  struct process name = { NULL, strname,		\
                          process_thread_##name }
PROCESS_THREAD的宏定义如下,它用来定义一个process的body:

#define PROCESS_THREAD(name, ev, data) 				\
static PT_THREAD(process_thread_##name(struct pt *process_pt,	\
				       process_event_t ev,	\
				       process_data_t data))

最终又回归到PT_THREAD,所以说A process in Contiki consists of a single protothread。

看看PT_THREAD宏中参数的定义:

struct pt {
  lc_t lc;
};//typedef unsigned short lc_t;

typedef unsigned char process_event_t;

typedef void *        process_data_t;

第一个参数lc是英文全称是local continuation(本地延续?,这个不好翻译),它可以说就是protothread的控制参数,因为protothread的精华在C的switch语句,这个lc就是switch里面的参数;

第二个参数就是timer给这个进程传递的事件了,其实就是一个unsigned char类型的参数,具体的参数值在process .h中定义;

第三个参数是给进程传递的一个参数,具体还没涉及到,先过之。


PT_THREAD是用来定义一个protothread,每个protothread都要用到这个宏,其宏定义如下:

#define PT_THREAD(name_args) char name_args
所以PROCESS(hello_world_process, "Hello world process");的展开代码如下:

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}

第一个语句是声明了一个process_thread_hello_world_process的函数,返回值是char型,其实就是声明了hello_world这个process的body函数,接下来的PROCESS_THREAD其实就是这个函数的定义。

第二个语句是声明了一个process类型的数据,是hello_world_process。下面看看struct process是怎么定义的。

struct process {
  struct process *next;
  const char *name;
  PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t));
  struct pt pt;
  unsigned char state, needspoll;
};
因为要维持一个进程的链表,struct process *next是指向它的下一个进程,声明一个process时初始化为0;

const char *name指的是这个进程的名字;

第三个展开来就是 char (* thread)(struct pt *, process_event_t, process_data_t),thread是一个函数指针,也就是说process包含了一个函数,每个process都包含了一个函数,process的执行其实就是这个函数体的执行。

再来看看pt的定义:

struct pt {
  lc_t lc;  //typedef unsigned short lc_t;
};

struct pt pt在这有何用,这正是protothread的精髓所在,看contiki内在ls-switch.h文件里的一段说明:

This implementation of local continuations uses the C switch()statement to resume execution of a function somewhere inside the function's body. The implementation is based on the fact that switch() statements are able to jump directly into the bodies of control structures such as if() or while() statements.

上面已经提到过lc表示local continuation,它是为switch()语句服务的,从而可以恢复process的执行,实现对process的控制。

还有unsigned char state, needspoll,state表示process的状态,needspoll先不管。

2.AUTOSTART_PROCESSES

先看看AUTOSTART_PROCESS的宏定义:
#define AUTOSTART_PROCESSES(...)					\
struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
autostart_process是一个指针数组,每个指针指向的都是一个process。
所以展开的代码就是:
struct process * const autostart_processes[] = {&hello_world_process, ((void *)0)}
定义了一个process类型的指针&hello_world_process,下一个为空,hello_world_process在PROCESS宏中已经声明过了。

3.PROCESS_THREAD

process的body就是定义在PROCESS_THREAD中,可见PROCESS_THREAD的重要性,其实展开来它就是一个函数,上面已经提到了,展开如下:

static char process_thread_hello_world_process(struct pt *process_pt,	\
				       process_event_t ev,	\
				       process_data_t data)
{
}
process的body就定义在{ }中。

4.PROCESS_BEGIN和PROCESS_END

process的body必须要以这两个宏来作为开始和结尾,看看这连个宏定义:
#define PROCESS_BEGIN()             PT_BEGIN(process_pt)

#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)

#define LC_RESUME(s) switch(s) { case 0:
所以展开来就是
{ char PT_YIELD_FLAG = 1; switch((process_pt)->lc) { case 0:

再看看PROCESS_END的宏定义:
#define PROCESS_END()               PT_END(process_pt)

#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
                   PT_INIT(pt); return PT_ENDED; }

#define LC_END(s) }

#define PT_INIT(pt)   LC_INIT((pt)->lc)

#define LC_INIT(s) s = 0;

#define PT_ENDED   3
展开来就是:
}; PT_YIELD_FLAG = 0; \
                   (process_pt)->lc = 0;; return 3; }

5.总体展开的代码如下

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)
{
    {
        char PT_YIELD_FLAG = 1;
        switch((process_pt)->lc)
        {
            case 0:
            ;
            printf(“Hello, world!\n”);  /* Initialization code goes here */
            while(1)
            {                  /* Loop for ever */
                PROCESS_WAIT_EVENT();     /* Wait for something to happen */
            }
        };
        PT_YIELD_FLAG = 0;
        (process_pt)->lc = 0;;
        return 3;
    }
}
这段代码有的细节实在是让人困惑,原来代码段{  }后可以加分号,所以switch后也可加分号,而且一条语句之后也可以用多个分号。

6.main()函数

main函数在platform/mb851的contiki-main.c文件。要执行hello_world.c的process,主要是用到main函数的这个语句:
autostart_start(autostart_processes);

autostart_start()是一个函数,定义在autostart_start.c文件里面,函数定义如下:

void
autostart_start(struct process * const processes[])
{
  int i;
  
  for(i = 0; processes[i] != NULL; ++i) {
    process_start(processes[i], NULL);
    PRINTF("autostart_start: starting process '%s'\n", processes[i]->name);
  }
}
struct process * const process[]维护的是一个进程队列,然后轮流执行,此例中只有一个hello_world进程。要启动一个进程,必须要用到process_start函数,此函数的定义如下:

void
process_start(struct process *p, const char *arg)
{
  struct process *q;

  /* First make sure that we don't try to start a process that is
     already running. */
  for(q = process_list; q != p && q != NULL; q = q->next);

  /* If we found the process on the process list, we bail out. */
  if(q == p) {
    return;
  }
  /* Put on the procs list.*/
  p->next = process_list;
  process_list = p;
  p->state = PROCESS_STATE_RUNNING;
  PT_INIT(&p->pt);

  PRINTF("process: starting '%s'\n", p->name);

  /* Post a synchronous initialization event to the process. */
  process_post_synch(p, PROCESS_EVENT_INIT, (process_data_t)arg);
}
process_start所干的工作就是先把将要执行的进程加入到进程队列process_list的首部,如果这个进程已经在process_list中,就return;接下来就把state设置为PROCESS_STATE_RUNNING并且初始化pt。最后通过函数process_post_synch()执行这个进程,并给这个进程传递一个PROCESS_EVENT_INIT(事件),看看这个函数的定义:

void
process_post_synch(struct process *p, process_event_t ev, process_data_t data)
{
  struct process *caller = process_current;

  call_process(p, ev, data);
  process_current = caller;
}

为什么process_post_synch()中要把process_current保存起来呢,process_current指向的是一个正在运行的process,当调用call_process执行这个hello_world这个进程时,process_current就会指向当前的process也就是hello_world这个进程,而hello_world这个进程它可能会退出或者正在被挂起等待一个事件,这时process_current = caller 语句正是要恢复先前的那个正在运行的process。

接下来展开call_process(),开始真正的执行这个process了:

static void
call_process(struct process *p, process_event_t ev, process_data_t data)
{
  int ret;

#if DEBUG
  if(p->state == PROCESS_STATE_CALLED) {
    printf("process: process '%s' called again with event %d\n", p->name, ev);
  }
#endif /* DEBUG */
  
  if((p->state & PROCESS_STATE_RUNNING) &&
     p->thread != NULL) {
    PRINTF("process: calling process '%s' with event %d\n", p->name, ev);
    process_current = p;
    p->state = PROCESS_STATE_CALLED;
    ret = p->thread(&p->pt, ev, data);
    if(ret == PT_EXITED ||
       ret == PT_ENDED ||
       ev == PROCESS_EVENT_EXIT) {
      exit_process(p, p);
    } else {
      p->state = PROCESS_STATE_RUNNING;
    }
  }
}
这个函数中才是真正的执行了hello_world_process的内容。假如进程process的状态是PROCESS_STATE_RUNNING以及进程中的thread函数不为空的话,就执行这个进程:

首先把process_current指向p,接着把process的state改为PROCESS_STATE_CALLED,执行hello_world这个进程的body也就是函数p->thread,并将返回值保存在ret中,如果返回值表示退出或者遇到了PROCESS_EVENT_EXIT的时事件后,便执行exit_process()函数,process退出。不然程序就应该在挂起等待事件的状态,那么就继续把p的状态维持为PROCESS_STATE_RUNNING。


最后稍微看一下exit_process()函数:

static void
exit_process(struct process *p, struct process *fromprocess)
{
  register struct process *q;
  struct process *old_current = process_current;

  PRINTF("process: exit_process '%s'\n", p->name);

  if(process_is_running(p)) {
    /* Process was running */
    p->state = PROCESS_STATE_NONE;

    /*
     * Post a synchronous event to all processes to inform them that
     * this process is about to exit. This will allow services to
     * deallocate state associated with this process.
     */
    for(q = process_list; q != NULL; q = q->next) {
      if(p != q) {
	call_process(q, PROCESS_EVENT_EXITED, (process_data_t)p);
      }
    }

    if(p->thread != NULL && p != fromprocess) {
      /* Post the exit event to the process that is about to exit. */
      process_current = p;
      p->thread(&p->pt, PROCESS_EVENT_EXIT, NULL);
    }
  }
  
  if(p == process_list) {
    process_list = process_list->next;
  } else {
    for(q = process_list; q != NULL; q = q->next) {
      if(q->next == p) {
	q->next = p->next;
	break;
      }
    }
  }

  process_current = old_current;
}
exit_process函数有两个参数,前一个是要退出的process,后一个是当前的process。如果要退出的process就是当前的process,那么就只需要把它从进程列表中清除即可;

如果要退出的process不是当前的process,那么当前的process就要给要退出的process传递一个PROCESS_EVENT_EXIT事件让其进程的body退出,然后再将其从进程列表中清除。

这个hello_world的程序非常简单,基于contiki 2.4平台的,这里主要是关于几个重要宏、process相关数据结构、process相关函数的理解,关于process的精髓protothread,这里稍有涉及,以后会继续深入。

一篇hello_world阅读笔记,初涉contiki,理解尚浅,不懂的还有很多,如有错误之处,敬请指出。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值