我正在阅读Contiki操作系统中原线程实现的源代码,该操作系统由瑞典SICS的Adam Dunkels开发。我真的很困惑它的实现和Simon Tatham所展示的协同例程思想之间的一个细微区别——也就是说,为什么状态变量在Adam的原线程实现中不必是静态的,而在Simon的论文中声明为静态的?
让我们先仔细看看西蒙的讨论。例如,如果能够编写一个函数来表示,那就太好了
int function(void) {
int i;
for(i=0; i<10; i++)
return i; //actually won't work in C
}
并且对函数的连续十次调用返回数字0到9。
这可以通过在该功能中使用以下宏来实现:
#define crBegin static int state=0; switch(state) { case 0:
#define crReturn(i,x) do { state=__LINE__; return x;
case __LINE__:; } while (0)
#define crFinish }
int function(void) {
static int i;
crBegin;
for (i = 0; i < 10; i++)
crReturn(1, i);
crFinish;
}
如预期的那样,调用该函数十次将得到0到9。
不幸的是,如果我们使用Adam的本地延续宏来封装像这样的开关情况(Contiki-src树中的/core/sys/lc-switch.h),即使您将状态变量s设置为静态:,这也不会起作用
typedef unsigned short lc_t;
#define LC_INIT(s) s = 0; // the ";" must be a mistake...
#define LC_RESUME(s) switch(s) { case 0:
#define LC_SET(s) s = __LINE__; case __LINE__:
#define LC_END(s) }
int function(void) {
static int i;
lc_t s;
LC_INIT(s);
LC_RESUME(s);
for (i = 0; i < 10; i++)
{ return i;
LC_SET(s);
}
LC_END(s);
}
在这里,就像Simon的例子一样,s作为一个状态变量,它保留LC_set(s)设置的位置(屈服点)。当函数稍后(从一开始)恢复执行时,它将根据s的值进行切换。这种行为产生的效果是,函数在上次调用设置的屈服位置之后继续运行。
这两组宏之间的区别是:
- 状态变量s在Simon的例子中是静态的,但在Adam的LC定义中是非静态的
- crReturn在返回结果之前设置状态,而在Adam的LC定义中,LC_SET(s)纯粹设置状态并标记屈服点
当然,对于函数中的循环情况,后者不起作用。这种"返回并继续"行为的关键在于静态状态变量和在返回语句之前设置的状态。显然,LC宏既不满足这两个要求。那么为什么LC宏是这样设计的呢?
我现在所能推测的是,这些LC宏只是非常低级别的基元,不应该以循环示例中所示的方式使用。我们需要进一步构建那些封装在这些LC基元中的PT宏,使它们真正有用。crReturn宏仅用于演示目的,特别适合for循环情况,因为并非每次都希望通过函数的返回来实现执行。
正如您正确猜测的那样,所有应该在路由返回之间保存其值的函数局部变量都应该是静态的,此外,描述程序当前状态的类型为lc_t
的变量也应该是静态。要修复您的示例,请在s
的声明前面添加static
。
另一件事是你想要返回一个值。Contiki原线程不支持返回任意值;它们只是描述线程是否仍处于活动状态或已经完成(PT_WAITING
、PT_YIELDED
、PT_EXITED
和PT_ENDED
状态)的代码。
但是,使用LC_xxx
宏可以很容易地实现这一点;您还需要一个标志(想法与PT_YIELD()
中的相同):
int function(void) {
static int i;
static lc_t s;
int flag = 0; // not static!
LC_INIT(s);
LC_RESUME(s);
for (i = 0; i < 10; i++) {
flag = 1;
LC_SET(s);
if (flag) { /* don't return if came to this point straight from the invocation of the coroutine `function` */
return i;
}
}
LC_END(s);
}
Contiki原线程库使用这些LC_xxx
宏来实现PT_xxx
宏,这些宏又用于创建对处理的应用程序级别(PROCESS_xxx
宏)的支持。
lc_t
状态变量实际上与原线程的状态相同:https://github.com/contiki-os/contiki/blob/master/core/sys/pt.h,pt
结构简单定义为:
struct pt {
lc_t lc;
};
pt
结构又被包括为process
结构中的一个成员(参见https://github.com/contiki-os/contiki/blob/master/core/sys/process.h)。Contiki中的进程结构是全局变量,因此原线程状态存储在原线程协程的不同调用中。
大多数程序局部变量也需要是静态的,这一事实通常被描述为(在研究论文中)这种编程模型的主要限制之一,但在实践中这并不是什么大不了的。
状态变量确实需要在静态分配的内存中,其中包括全局变量,如您链接到的Dunkels示例中所示。如果它是一个自动变量(在堆栈上,而不是静态变量),则其值将在函数的一次调用到下一次调用时丢失,除非是在最琐碎的程序中。
当使用lc开关实现时,您可以将Tatham的crReturn()
修改为LC_SET_AND_RETURN()
宏,该宏为需要返回值的函数添加了返回功能,如下所示,并且只需为void
函数调用LC_SET(s); return;
。
#include "lc.h"
#define LC_SET_AND_RETURN(lc, retval) do { lc = __LINE__ ; return retval; case __LINE__: } while (0)
int function(void) {
static int i;
static lc_t s;
LC_RESUME(s);
for (i = 0; i < 10; i++) {
LC_SET_AND_RETURN(s, i);
}
return -1; // done
LC_END(s);
}
CCD_ 21看起来应该像CCD_ 22一样被调用。代码
static lc_t s;
LC_INIT(s);
扩展到
static lc_t s;
s = 0;;
其不等同于static lc_t s = 0;
并且导致代码以非故意的方式表现。
您可以使用static lc_t LC_INIT(s);
扩展到static lc_t s = 0;;
,但这看起来很有趣。