我正在阅读Contiki OS中的protothread实现的源代码,该实现由瑞典SICS的Adam Dunkels开发。我真的很困惑它的实现和Simon Tatham所展示的协同例程之间的一点点差异 - 也就是说,为什么状态变量在Adam's protothread实现中不是静态的,而声明为static在西蒙的论文中?
让我们先仔细看看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的值进行切换。此行为表示函数在前一次调用设置的yield位置之后继续运行。
这两组宏之间的区别是:
当然后者在函数中对于for循环的情况不起作用。这种“返回并继续”行为的关键在于状态变量是静态的,而状态是在return语句之前设置的。显然,LC宏不符合要求。那么为什么LC宏是以这种方式设计的呢?
我现在可以推测的是,这些LC宏只是非常低级的原语,不应该以for循环示例中所示的方式使用。我们需要进一步构建这些包含这些LC原语的PT宏,以使它们真正有用。并且crReturn宏仅用于演示目的,以专门适合for循环的情况,因为并非每次都希望通过函数返回来执行执行。
答案 0 :(得分:1)
正如您所猜测的那样,所有应该在coroute返回之间保存其值的函数局部变量应该是静态的,此外,类型lc_t
的变量描述了couroutine的当前状态也应该是静态的。要修复您的示例,请在static
的声明前添加s
。
另一件事是你想要返回一个值。 Contiki protothreads不支持返回任意值;它们只是一个代码,用于描述该线程是处于活动状态还是已经完成(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 protothread库使用这些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中的进程结构是全局变量,因此protothread状态存储在protothread协程的不同调用中。
大多数couroutine局部变量也需要是静态的这一事实通常被描述(在研究论文中)作为这种编程模型的主要限制之一,但在实践中它并不是什么大问题。
答案 1 :(得分:0)
状态变量确实需要在静态分配的内存中,其中包括您链接到的Dunkels示例中的全局变量。如果它是一个自动变量(在堆栈上,而不是静态变量),它的值将从函数的一次调用中丢失,除了在最简单的程序中。
使用lc-switch实现时,您可以将Tatham的crReturn()
修改为LC_SET_AND_RETURN()
宏,以便为需要返回的函数添加返回功能,如下所示一个值,只需为LC_SET(s); return;
函数调用void
。
#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);
}
LC_INIT()
看起来像是被称为LC_INIT(static lc_t s);
。代码
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;;
,但这看起来很有趣。