Реализация протопотока в ОС Contiki - почему переменная состояния не является статической?
Я читаю исходный код реализации протопотока в ОС Contiki, разработанной Адамом Дункелсом из 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, как и ожидалось.
К сожалению, это не сработает, если мы воспользуемся регистром-переключателем локальных макросов продолжения Адама, подобным этому (/core/sys/lc-switch.h в дереве src Contiki), даже если вы сделаете переменную состояния 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);
}
Здесь, как и в примере с Саймоном, s работает как переменная состояния, которая сохраняет позицию (предел текучести), установленную LC_SET(s). И когда функция позже возобновит выполнение (с начала), она переключится в соответствии со значением s. Такое поведение дает эффект, что функция продолжает работать после позиции выхода, установленной предыдущим вызовом.
Различия между этими двумя наборами макросов:
- переменная состояния s является статической в примере Саймона, но не является статичной в определении Адама LC;
- crReturn устанавливает состояние непосредственно перед возвратом результата, в то время как в определении LC Адама LC_SET (s) просто устанавливает состояние и отмечает точку доходности.
Конечно, последний не будет работать с этим для случая цикла в функции. Ключ к этому поведению "возврат и продолжение" находится как в статической переменной, так и в состоянии, устанавливаемом непосредственно перед оператором возврата. Видимо макросы LC не соответствуют ни одному из требований. Так почему же макросы LC разработаны таким образом?
Все, что я могу предположить прямо сейчас, - это то, что эти макросы LC являются только примитивами очень низкого уровня и не должны использоваться способом, показанным в этом примере для цикла. Нам нужно дополнительно построить эти PT-макросы, обернутые этими примитивами LC, чтобы сделать их действительно полезными. А макрос crReturn предназначен только для демонстрации, чтобы специально соответствовать регистру цикла for, поскольку не каждый раз, когда вы хотите завершить выполнение, возвращаясь из функции.
2 ответа
Как вы правильно догадались, все функционально-локальные переменные, значения которых должны сохраняться между возвратами coroute, должны быть статическими, и, кроме того, переменная типа 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.hpt
структура определяется просто как:
struct pt {
lc_t lc;
};
pt
структура в свою очередь включена в качестве члена в process
структура (см. https://github.com/contiki-os/contiki/blob/master/core/sys/process.h). И структуры процессов в Contiki являются глобальными переменными, поэтому состояние protothread хранится в разных вызовах сопрограммы protothread.
Тот факт, что большинство локально-переменных переменных также должны быть статическими, обычно описывается (в исследовательских работах) как одно из главных ограничений этой модели программирования, но на практике это не имеет большого значения.
Переменная состояния должна находиться в статически выделенной памяти, которая включает глобальные переменные, как в примере с 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;;
но это выглядит смешно.