【问题标题】:Protothread implementation in Contiki OS -- why the state variable is not static?Contiki OS 中的 Protothread 实现——为什么状态变量不是静态的?
【发布时间】:2016-08-09 01:08:29
【问题描述】:

我正在阅读由瑞典 SICS 的 Adam Dunkels 开发的 Contiki OS 中的 protothread 实现的源代码。我真的很困惑它的实现和Simon Tatham 所展示的协同程序想法之间的细微差别——也就是说,为什么状态变量在Adam's protothread 实现中不必是静态的,而在西蒙的论文中声明为静态?

我们先来仔细看看西蒙的讨论。例如,如果能写出这样的函数,那就太好了

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),这将不起作用,即使您将状态变量设置为 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) 纯粹设置状态并标记屈服点。

当然,后者不适用于函数中的这种 for 循环情况。这种“返回并继续”行为的关键在于状态变量是静态的以及在返回语句之前设置的状态。显然 LC 宏不满足任何要求。那么为什么 LC 宏是这样设计的呢?

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

【问题讨论】:

    标签: coroutine contiki protothread


    【解决方案1】:

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

    另一件事是你想返回一个值。 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-local 变量也需要是静态的这一事实通常(在研究论文中)被描述为这种编程模型的主要限制之一,但实际上这并不是什么大问题。

    【讨论】:

    • 嗨 kfx。在这里感谢您的回答,这真的很有帮助。现在我明白了 Contiki 源代码中 LC 宏的“静态”属性在 LC 原语级别中没有明确说明,实际上“静态”属性是从继承它们的子类传播的,就像这样(process-- >protothread-->lc).
    • 但是,正如我所说,简单地在 s 声明前添加 static 是行不通的。为了让它工作,LC_SET 宏需要被return 语句分割,sth。就像 #define LC_SET(s, r) s=__LINE__; r; case __LINE__: 一样,这真的很奇怪! @kfx
    • @user2275498 不知何故完全错过了您想要返回 i 的值,不知道我在想什么...您的方法可行,另请参阅另一种方法的编辑答案希望不那么混乱。
    【解决方案2】:

    状态变量确实需要位于静态分配的内存中,其中包括您链接到的 Dunkels 示例中的全局变量。如果它是一个自动变量(在堆栈上,不是静态的),它的值将在函数的一次调用到下一次调用中丢失,除了在最琐碎的程序中。

    使用lc-switch实现时,可以将Tatham的crReturn()修改为LC_SET_AND_RETURN()宏,为需要返回值的函数添加返回功能,如下图,只需调用@987654323 @ 代表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;;,但这看起来很有趣。

    【讨论】:

      猜你喜欢
      • 2010-11-08
      • 2020-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多