Реализация Protothread в ОС Contiki, почему переменная состояния не статическая?

Я читаю исходный код реализации протопотока в Contiki OS, который разработан Адамом Данкелсом из SICS, Швеция. И меня действительно смущает одно небольшое различие между его реализацией и идеей совместных подпрограмм, продемонстрированной Саймон Тэтхэм - поэтому переменная состояния не должна быть статической в ​​Реализация протопотока Адама, хотя в статье Саймона объявлена ​​статической?

Давайте сначала внимательно посмотрим на обсуждение Саймона. Например, было бы неплохо написать функцию, которая говорит

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.

К сожалению, это не сработает, если мы будем использовать локальные макросы продолжения Адама в оболочке switch-case, как это (/core/sys/lc-switch.h в дереве Contiki src), даже если вы сделаете переменную состояния статической:

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);
}

Здесь, как в примере Саймона, s работает как переменная состояния, которая сохраняет позицию (предел текучести), установленную LC_SET (s). И когда функция позже возобновит выполнение (с начала), она переключится в соответствии со значением s. Такое поведение дает эффект, что функция продолжает работать после позиции yield, установленной предыдущим вызовом.

Различия между этими двумя наборами макросов:

  1. переменная состояния s статична в примере Саймона, но нестатична в определении Адама LC;
  2. crReturn устанавливает состояние прямо перед возвратом результата, в то время как в определении Адама LC LC_SET (s) просто устанавливает состояние и отмечает предел текучести.

Конечно, последнее не будет работать с этим случаем цикла for в функции. Ключ к такому поведению «возврат и продолжение» заключается как в статической переменной состояния, так и в том, что состояние устанавливается прямо перед оператором return. Очевидно, макросы LC не соответствуют ни одному из требований. Так почему же макросы LC устроены именно так?

Все, что я могу предположить прямо сейчас, это то, что эти макросы LC являются примитивами очень низкого уровня и не должны использоваться так, как показано в этом примере цикла for. Нам необходимо создать эти макросы PT, обернутые этими примитивами LC, чтобы сделать их действительно полезными. И макрос crReturn предназначен только для демонстрационных целей, чтобы точно соответствовать случаю цикла for, поскольку не каждый раз вы хотите, чтобы выполнение выполнялось возвратом из функции.


person L3w1s    schedule 17.04.2016    source источник


Ответы (2)


Как вы правильно догадались, все локальные переменные функции, значения которых должны сохраняться между возвратами сопрограмм, должны быть статическими, и, кроме того, переменная типа lc_t, которая описывает текущее состояние программы также должна быть статичным. Чтобы исправить ваш пример, добавьте static перед объявлением s.

Другое дело, что вы хотите вернуть значение. Протопотоки Contiki не поддерживают возврат произвольных значений; они просто код, который описывает, активен ли поток или уже завершился (состояния 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 использует эти 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 являются глобальными переменными, поэтому состояние протопотока сохраняется при различных вызовах сопрограммы протопотока.

Тот факт, что большинство локальных переменных программы также должны быть статическими, обычно описывается (в исследовательских статьях) как одно из основных ограничений этой модели программирования, но на практике это не имеет большого значения.

person kfx    schedule 17.04.2016
comment
Привет, kfx. Оценил ваш ответ здесь, это действительно полезно. Теперь я понимаю, что статический атрибут макросов LC в исходном коде Contiki явно не разъясняется на уровне примитивов LC, на самом деле статический атрибут распространяется от дочернего класса, который их наследует, точно так же (процесс - ›protothread-- ›Lc). - person L3w1s; 17.04.2016
comment
Однако, как я уже сказал, просто добавить static перед объявлением s не получится. Чтобы он заработал, макрос LC_SET должен быть разделен оператором return sth. нравится #define LC_SET(s, r) s=__LINE__; r; case __LINE__:, и это действительно странно! @kfx - person L3w1s; 17.04.2016
comment
@ user2275498 как-то совершенно упустил из виду, что вы хотите вернуть значение i, не знаю, о чем я думал ... Ваш подход будет работать, см. также отредактированный ответ для другого подхода, который, надеюсь, менее беспорядочный. - person kfx; 17.04.2016

Переменная состояния должна находиться в статически выделенной памяти, которая включает глобальные объекты, как в примере Dunkels, с которым вы связались. Если это автоматическая переменная (в стеке, а не статическая), ее значение будет потеряно от одного вызова функции к следующему, за исключением самых тривиальных программ.

При использовании реализации lc-switch вы можете изменить 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;;, но это выглядит забавно.

person zanedp    schedule 11.12.2017