Contiki操作系统中的原线程实现——为什么状态变量不是静态的



我正在阅读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的值进行切换。这种行为产生的效果是,函数在上次调用设置的屈服位置之后继续运行。

这两组宏之间的区别是:

  1. 状态变量s在Simon的例子中是静态的,但在Adam的LC定义中是非静态的
  2. crReturn在返回结果之前设置状态,而在Adam的LC定义中,LC_SET(s)纯粹设置状态并标记屈服点

当然,对于函数中的循环情况,后者不起作用。这种"返回并继续"行为的关键在于静态状态变量和在返回语句之前设置的状态。显然,LC宏既不满足这两个要求。那么为什么LC宏是这样设计的呢?

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

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

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

相关内容

  • 没有找到相关文章

最新更新