进程调度器
进程调度器的作用是调用进程。进程调度器通过调用实现进程线程的函数来调用进程。Contiki中所有的进程被设计为响应传递到进程中的事件,或者相应进程请求的轮询。进程调度器在调度进程的时候会将事件标识符和一个不透明指针传递到进程中,该指针由进程调用者提供,可以设置为NULL(该事件不需要传递数据)。当进程轮询时,不会传递数据。
开始进程
1 /** 2 * Start a process. 3 * 4 * \param p A pointer to a process structure. 5 * 6 * \param arg An argument pointer that can be passed to the new 7 * process 8 * 9 */ 10 CCIF void process_start(struct process *p, const char *arg);
1 void 2 process_start(struct process *p, const char *arg) 3 { 4 struct process *q; 5 6 /* First make sure that we don‘t try to start a process that is 7 already running. */ 8 for(q = process_list; q != p && q != NULL; q = q->next); 9 10 /* If we found the process on the process list, we bail out. */ 11 if(q == p) { 12 return; 13 } 14 /* Put on the procs list.*/ 15 p->next = process_list; 16 process_list = p; 17 p->state = PROCESS_STATE_RUNNING; 18 PT_INIT(&p->pt); 19 20 PRINTF("process: starting ‘%s‘\n", PROCESS_NAME_STRING(p)); 21 22 /* Post a synchronous initialization event to the process. */ 23 process_post_synch(p, PROCESS_EVENT_INIT, (process_data_t)arg); 24 }
process_start()开始一个进程。该函数的目的是设置进程控制块,将进程加入到内核有效进程链表中,然后调用进程线程中的初始化代码。最后给该进程发送一个同步的初始化事件。process_start()被调用后,该进程就开始运行了。
process_start()函数先做一个明智的检查,检查是否该进程已经存在进程链表中。如果是,表明该进程已经被运行了,process_start()函数直接返回。在确认该进程没有开始运行后,内核将该进程加入到链表中,并设置进程控制块。进程的状态被设置为PROCESS_STATE_RUNNING,进程的线程被PT_INIT()初始化。最后,内核为进程发送给一个同步事件PROCESS_EVENT_INIT,且传递一个不透明指针给进程,该指针是由调用process_start()的进程传入的,用于传递给要运行的进程一些信息。不过,这个指针一般都设为NULL。当进程接收到它的第一个事件PROCESS_EVENT_INIT,进程将执行该进程线程的第一部分。通常,这部分包含进程开始时即将运行的初始化代码。
退出进程和杀死进程
进程退出有两种方法:进程自动退出和被其它进程杀死。调用函数process_exit()或者当进程线程执行到PROCESS_END()语句时,进程将退出。
进程可以调用函数process_exit()杀死另一个进程。
当一个进程退出(无论是主动退出还是被动退出),Contiki内核会发送给一个事件通知其它进程,这可以让其它进程释放退出进程所占有的资源。事件PROCESS_EVENT_EXITED将会被以同步事件的方式发送到其它所有进程。
当一个被另一个金曾杀死时,被杀的进程将会接收到同步事件PROCESS_EVENT_EXIT,该事件通知将要被杀死的进程和能够释放已分配资源的进程,或者其它将要退出的进程。
在Contiki内核发送事件通知将要退出的进程后,将从进程链表中删除该进程。
自启动进程
Contiki提供了一个机制,在系统启动时,或者包含进程的模块被加载时,自动运行进程。该机制由自启动模块实现。该机制能够让模块开发者告知系统该模块中包含什么进程。当模块从内存中移除时,也能让系统做相应的处理。
自启动进程保存在一个链表中,自启动模块利用该链表实现自动运行进程。进程启动的顺序与它在链表中的顺序一致。
自启动进程有两种时机:系统启动时和模块被加载时。所有需要在系统启动时自启动的进程必须被包含在一个单一的系统级的链表中。这个自启动链表由用户提供,且一般在一个用户模块中。当这个模块被用作可加载模块时,链表能够让系统知道模块被加载时需要运行什么进程。
当加载一个模块时,模块加载器将查找自启动进程链表,并在模块被加载到内存后启动链表中的进程。当模块将要被卸载时,模块加载器利用该链表杀死在模块加载时启动的进程。
一个接收事件并打印其数字的进程的例子:
1 #include "contiki.h" 2 3 PROCESS(example_process, "Example process"); 4 AUTOSTART_PROCESSES(&example_process); 5 6 PROCESS_THREAD(example_process, ev, data) 7 { 8 PROCESS_BEGIN(); 9 10 while(1) { 11 PROCESS_WAIT_EVENT(); 12 printf("Got event number %d\n", ev); 13 } 14 15 PROCESS_END(); 16 }
上面的代码是一个Contiki完整的进程。该进程被申明、定义和自动运行。
第三行,我们定义了进程控制块。进程控制块定义了进程控制块的名字example_process和文本的、用户可读的进程名字Example process。在定义了进程控制块后,我们可以在其它表达式中使用该变量名。
第四行,语句AUTOSTART_PROCESSES()告诉Contiki在系统启动时自动启动进程example_process。自启动链表由指向进程控制块的指针组成,所以需要在变量example_process前加&取地址符。
第六行,我们定义了进程线程。它包含了进程的变量名example_process和传递事件的变量ev及数据data。
第八行,我们使用了PROCESS_BEGIN()定义了一个进程的开始。该定义标志进程线程的开始。在进程每次被调度运行的时候,该申明语句上面的代码都会运行。在大多数情形下,你不需要在PROCESS_BEGIN()之上放任何代码。
第十行,开始进程的主循环。Contiki进程不能包含永不结束的死循环。但是在上面代码中,这样的死循环是安全的,因为进程将会等待时间。当一个Contiki进程在等待事件时,它会将控制器返回给Contiki内核。在该进程等待期间,内核将会为其它进程提供服务。
第十一行,进程等待事件的发生。表达式PROCESS_WAIT_EVENT()将返回控制权给Contiki内核,并等待内核传递事件到该进程。当Contiki内核传递事件给该进程后,PROCESS_WAIT_EVENT()后面的代码将被执行。在进程被唤醒后,将执行第十二行的打印语句。这一行的作用是打印进程接收到的事件编号。如果同时传入 了一个指针,该指针变量就是data(?)。
第十五行,PROCESS_END()标识进程的结束。每个Contiki进程必须包含PROCESS_BEGIN()和PROCESS_END()。当执行到PROCESS_END()时,该进程自动退出,并从内核中的进程链表中移除。不过,由于存在死循环,永远不会执行到PROCESS_END()。只有系统被关掉、或者该进程被process_exit()杀死的时候才会被停止运行。
一个启动进程并发送事件的函数
1 static char msg[] = "Data"; 2 3 static void 4 example_function(void) 5 { 6 /* Start "Example process", and send it a NULL 7 pointer. */ 8 9 process_start(&example_process, NULL); 10 11 /* Send the PROCESS_EVENT_MSG event synchronously to 12 "Example process", with a pointer to the message in the 13 array ‘msg‘. */ 14 process_post_synch(&example_process, 15 PROCESS_EVENT_CONTINUE, msg); 16 17 /* Send the PROCESS_EVENT_MSG event asynchronously to 18 "Example process", with a pointer to the message in the 19 array ‘msg‘. */ 20 process_post(&example_process, 21 PROCESS_EVENT_CONTINUE, msg); 22 23 /* Poll "Example process". */ 24 process_poll(&example_process); 25 }
两个进程间通过事件完成交互。上面的例子中的函数启动了一个进程,并向它发送一个同步事件和轮询请求。
总结
进程通过发送事件实现与其它进程通信。