我正在阅读由瑞典 SICS 的 Adam Dunkels 开发的 Contiki OS 中的 protothread 实现的源代码。我真的很困惑它的实现和Simon Tatham所展示的协同程序想法之间的细微差别——也就是说,为什么状态变量在Adam 的 protothread实现中不必是静态的,而在 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 的本地延续宏封装的 switch-case(Contiki src 树中的/core/sys/lc-switch.h),这将不起作用,即使您将状态变量设置为静态:
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) 纯粹设置状态并标记屈服点。
当然,后者不适用于函数中的这种 for 循环情况。这种“返回并继续”行为的关键在于状态变量是静态的以及在返回语句之前设置的状态。显然 LC 宏不满足任何要求。那么为什么 LC 宏是这样设计的呢?
我现在只能推测这些 LC 宏只是非常低级的原语,不应该以这个 for 循环示例中所示的方式使用。我们需要进一步构建那些包裹这些 LC 原语的 PT 宏,以使它们真正有用。并且 crReturn 宏仅用于演示目的,以特别适合 for 循环情况,因为并非每次您都想通过从函数返回来产生执行。