Contiki OS中的Protothread实现 - 为什么状态变量不是静态的?

时间:2016-04-17 09:05:57

标签: coroutine contiki protothread

我正在阅读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位置之后继续运行。

这两组宏之间的区别是:

  1. 状态变量s在Simon的示例中是静态的,但在Adam的LC定义中是非静态的;
  2. crReturn在返回结果之前设置状态,而在Adam的LC定义中,LC_SET(s)纯粹设置状态并标记屈服点。
  3. 当然后者在函数中对于for循环的情况不起作用。这种“返回并继续”行为的关键在于状态变量是静态的,而状态是在return语句之前设置的。显然,LC宏不符合要求。那么为什么LC宏是以这种方式设计的呢?

    我现在可以推测的是,这些LC宏只是非常低级的原语,不应该以for循环示例中所示的方式使用。我们需要进一步构建这些包含这些LC原语的PT宏,以使它们真正有用。并且crReturn宏仅用于演示目的,以专门适合for循环的情况,因为并非每次都希望通过函数返回来执行执行。

2 个答案:

答案 0 :(得分:1)

正如您所猜测的那样,所有应该在coroute返回之间保存其值的函数局部变量应该是静态的,此外,类型lc_t的变量描述了couroutine的当前状态应该是静态的。要修复您的示例,请在static的声明前添加s

另一件事是你想要返回一个值。 Contiki protothreads不支持返回任意值;它们只是一个代码,用于描述该线程是处于活动状态还是已经完成(PT_WAITINGPT_YIELDEDPT_EXITEDPT_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;;,但这看起来很有趣。