Представитель Шуры Люберецкого в ЖЖ (brat_luber) wrote,
Представитель Шуры Люберецкого в ЖЖ
brat_luber

Операционная система из говна и палок

На “Гиктаймсе” опубликовали конспект первой лекции курса Олега Артамонова по программированию микроконтролеров. Курс, конечно, немного экзотический в сравнении с любым интернетовским руководством по тем же STM32 – в нем рассматривается программирование с использованием операционной системы RIOT. Никакого вам CubeMX, никакой FreeRTOS – но в целом материал не особо привязан к конкретной ОС и “железу” и ориентирован скорее на то, чтобы продемонстрировать подходы к программированию для микроконтролеров “вообще”.

Для тех, кому проще воспринимать видео – на Youtube выкладываются и видеозаписи лекций:

https://www.youtube.com/playlist?list=PLJEYfuHbcEIApuZR4L5tRiBCwTZCYeTNY

Но при всех заявленных и видимых достоинствах этих лекций, хвалить их целиком пока рано – поэтому перейду к всякой ерунде. Как водится, половина удовольствия от чтения “околоэлектронных” материалов на “Хабре” и “Гиктаймсе” – это комментарии, где обычно ссаными тряпками гоняют ардуинщиков. В этот раз к “гонимым” добавились также те, кто не осилил ничего, кроме всевозможных HAL и StdPeriphLib от производителя, и те, кто почему-то считает микроконтролером Raspberry Pi. Но все это не заслуживало бы упоминания – если бы не один комментарий:

…например, в Contiki — там многозадачность с инвалидностью третьей группы, там надо в треде либо без switch-case, либо без сообщений жить. Этому в университете всех учить не надо, кому в жизни не посчастливится — сами научатся.

https://geektimes.ru/company/samsung/blog/299187/#comment_10699171

Полез смотреть, что же за альтернативный подход к многозадачности исповедуют авторы операционной системы Contiki – и обнаружил там совершенно замечательную штуку. Оказывается, тамошнее подобие “потоков” обычной RTOS реализовано довольно необычно, исключительно средствами языка C.

Для начала – вот такой хитрый пример кода, который обычно называется Duff’s device – “Прием Даффа”, в честь Тома Даффа, обратившего внимание на то, что метки в конструкции switch языка C позволяют нарушать “блочную” структуру программы – например, перейти сразу внутрь цикла:

switch (count % 8) {
    case 0:    do { *to = *from++;
    case 7:         *to = *from++;
    case 6:         *to = *from++;
    case 5:         *to = *from++;
    case 4:         *to = *from++;
    case 3:         *to = *from++;
    case 2:         *to = *from++;
    case 1:         *to = *from++;
               } while ((count -= 8) > 0);
}

Кстати говоря, Duff’s Device упомянут [info]sharpc в известном “Теоретическом минимуме для программиста“. Конструкция довольно дикая, мало чем отличающаяся от GOTO – и хочу заметить, что особых преимуществ по скорости (в оригинале она использовалась для того, чтобы развернуть цикл в memcpy) на современных процессорах она не дает. Знать о ней, наверное, надо, а вот применять – только по необходимости.

А теперь сделаем еще один шаг вперед – обратите внимание, что такой переход позволяет “сохранить” текущее положение “внутри” выполняемой функции. Сначала пример без макросов (взятый со странички Адама Дункельса про protothreads и немного измененный):

volatile int counter;

int example( int *lc ) {
    switch ( *lc ) { case 0:

    printf( "First run!\n" );
    while ( 1 ) {
        *lc = 9; case 9: if ( !(counter > 10) ) return 0;
        printf( "Threshold reached\n" );
        counter = 0;
    }

    } *lc = 0; return 2;
}

Будем вызывать эту функцию примерно таким образом:

int main( void ) {
    int lc = 0;

    while ( 1 ) {
        example( &lc );
        printf( "Back in main, counter = %d\n", counter );
        counter++;
    }

    return 0;
}

Обратите внимание, что строчка First run! напечатается только один раз, несмотря на то, что функция вызывается многократно. Фактически, таким нехитрым приемом реализован механизм ожидания событий – нетрудно догадаться, что в промежутках между вызовами нашей функции counter может изменяться как угодно (на это я намекаю, объявив его volatile). А теперь определим несколько макросов:

struct pt {
    int lc;
};
#define PT_BEGIN(pt)          switch((pt)->lc) { case 0:
#define PT_WAIT_UNTIL(pt, c)  pt->lc = __LINE__; case __LINE__: if(!(c)) return 0
#define PT_END(pt)            } (pt)->lc = 0; return 2
#define PT_INIT(pt)           (pt)->lc = 0

И перепишем пример, используя их:

volatile int counter;

int example( struct pt *pt ) {
    PT_BEGIN( pt );

    printf( "First run!\n" );
    while ( 1 ) {
        PT_WAIT_UNTIL( pt, counter > 10 );
        printf( "Threshold reached\n" );
        counter = 0;
    }

    PT_END( pt );
}

int main( void ) {
    struct pt example_pt;
    PT_INIT( &example_pt );

    while ( 1 ) {
        example( &example_pt );
        printf( "Back in main, counter = %d\n", counter );
        counter++;
    }

    return 0;
}

Хочу добавить, что в компиляторе из Microsoft Visual Studio этот пример не работает, если при компиляции указан параметр /ZI (он по умолчанию установлен для конфигурации Debug) – можно заменить его на /Zi.

Оцените, что получилось – исключительно средствами языка C реализован простенький, из говна и палок, механизм кооперативной многозадачности. Если добавить к нему планировщик и, скажем, какой-нибудь таймер, то получится та самая операционная система Contiki – названная в честь построенной из тех же говна и палок лодки Тура Хейердала. Теоретически, в Contiki есть и механизм вытесняющей многозадачности – но реализован он не для всех архитектур, в отличие от кооперативной, для которой достаточно лишь компилятора C. Это позволяет “портировать” ядро Contiki куда угодно.

Какие же у этого подхода недостатки? Начну с очевидного – использовать одновременно и protothreads, и конструкцию switch нельзя. Другая нехорошая штука – при переходе потока в состояние ожидания и выходе в планировщик теряются значения всех локальных переменных внутри функции этого потока. Это довольно неприятно, так как требует постоянно держать в голове “нестандартное” поведение программы. Бороться с этим можно либо объявляя локальные переменные потока, как static, либо используя глобальные переменные. Наконец, ждать событий можно только в основной функции потока, что резко ограничивает “полет фантазии” в реализации какой-то нетривиальной логики.

Впрочем, все это позволяет реализовать некоторое количество не очень сложных примеров – которые и составляют большую часть “дистрибутива” Contiki.

Запись опубликована в блоге Шуры Люберецкого. Вы можете оставлять свои комментарии там, используя свое имя пользователя из ЖЖ (вход по OpenID).

Tags: #define
Subscribe

  • О фактчекинге

    Наткнулся на американскую, разумеется, статью “Фактчекинг речи Владимира Путина в ООН”:…

  • Неоламаркизма псто

    Из школьной биологии мы твердо знаем, что приобретенные признаки не наследуются. Кто-то вспомнит про опыт Вейсмана, кто-то задаст простой вопрос…

  • Про псевдонауку

    Пишу сейчас довольно большой и спорный пост, пока не буду раскрывать, о чем – скажу лишь, что залез в википедию (фу, бля – скажете вы и…

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments