А давайте притащим мир большого программирования в Arduino!


Любая программа, а тем более программа близкая к аппаратуре (а какие еще на arduino бывают?) при рассмотрении представляет собой множество параллельно работающих ветвей.


При этом в реальной жизни обработка большинства вещей в реальном времени не требуется. Достаточно иметь нечто похожее на реальное время.


Например если мы программируем скажем гистерезисный регулятор температуры, то как правило совершенно не важно прямо сейчас сработает включатель нагревателя или через пару милисекунд.


А вот если мы программируем скажем регулятор ШИМ (не рассматриваем аппаратные способы), то тут нам возможно потребуется считать каждый такт процессора, чтобы обеспечить приемлемую точность регулирования.


Если рассмотреть структуру произвольного сложного программно-аппаратного проекта в том числе на Arduino, то увидим, что задач требующих "реального" (с жесткими требованиями) реалтайма — меньшинство, а большинству задач достаточно условного реалтайма.


Программирование реального реалтайма — это как правило прерывания и аппаратные хитрости. В этой статье поговорим о программировании реалтайма условного.


Давайте представим что мы разрабатываем скажем систему управления обычным бытовым холодильником. Эта система включает в себя:


  1. Регулятор температуры
  2. Органы управления этой самой температурой (пусть будет переключатель на три положения)
  3. Датчик открытия двери
  4. Свет в холодильнике
  5. Ну и например выход в интернет, куда ж без него.

Управляем температурой


Давайте попробуем рассмотреть для начала регулятор температуры. Как правило в холодильниках используют гистерезисное регулирование. Мы не будем придумывать велосипед и просто его реализуем.


void
temperature_regulator(void) {

    for (;;) {
        uint16_t current_t = get_adc(INPUT_T);

        if (current_t > t_on)
            enable_cooler();

        if (current_t < t_off)
            disable_cooler();
    }
}

Где: t_on и t_off — температуры гистерезиса. INPUT_T — вход АЦП измерения температуры. get_adc — некая функция производящая измерение АЦП. Функции enable_cooler и disable_cooler — соответственно включают и выключают охладитель.


Вроде просто?


Рассматривая поближе составляющие сразу натыкаемся на то, что многие вещи включают в себя циклы ожидания. Например функция get_adc могла бы выглядеть как-то так:


uint16_t
get_adc(uint8_t input)
{
    while (adc_is_busy());
    adc_switch_mux(input);
    adc_start();
    while (adc_is_busy());
    return adc_value();
}

Где adc_switch_mux — переключает входной мультиплексор АЦП на нужный вход, adc_start запускает АЦП преобразование. Пока преобразование выполняется нам приходится ждать — пустой цикл пока adc_is_busy возвращает истину. Когда преобразование выполнится adc_value вернет нам результат.


управляем светом


Управление светом в холодильнике тривиально:


void
light_control(void)
{
    for (;;) {
        if (sensor_door() == DOOR_OPEN)
            light_on();
        else
            light_off();
    }
}

О внедрении сюда выключения света по таймеру мы поговорим немного позднее. Сейчас попробуем соединить эти две программы.


Обе программы вполне наглядны понять и написать их сможет школьник. Но как соединить их в один процесс?


Самое простое — преобразовать программы в функции и вызывать их в бесконечном цикле. Именно этот подход предлагает нам Arduino с его традиционной функцией loop:


void
temperature_regulator(void) {

    uint16_t current_t = get_adc(INPUT_T);

    if (current_t > t_on)
        enable_cooler();

    if (current_t < t_off)
        disable_cooler();
}

void
light_control(void)
{
    if (sensor_door() == DOOR_OPEN)
        light_on();
    else
        light_off();
}

void
loop(void)
{
    temperature_regulator();
    light_control();
    ...
}

Вроде все просто? Но давайте вернемся к get_adc. Просто так уже эта функция не разворачивается. Можно конечно оставить все как есть (АЦП преобразование надолго нас не задержит), для случая холодильника возможно и подойдет, но давайте попробуем развернуть и этот цикл.


Какие сложности возникают:


  1. Поскольку имеется возвращаемое значение get_adc, то нужно его где-то хранить
  2. Если АЦП не используется нигде в другом месте, то у разработчика возникает большой соблазн взять и сунуть измерение АЦП прямо внутрь temperature_regulator:

enum adc_state { FREE, BUSY, VERYBUSY } state = VERYBUSY;

void
temperature_regulator(void)
{
    uint16_t current_t;
    switch(state) {
        case VERYBUSY:      // АЦП не нами занято
            if (adc_is_busy())
                return;
            state = FREE;
        case FREE:
            adc_switch_mux(input);
            adc_start();
            state = BUSY;
            return;
        case BUSY:
            if (adc_is_busy())
                return;
            current_t = adc_value();
            state = FREE;
            break;
    }

    if (current_t > t_on)
        enable_cooler();

    if (current_t < t_off)
        disable_cooler();
}

Вроде не сильно сложно? Но из неприятностей:


  1. появилось внешнее по отношению к функции хранилище состояния АЦП-модуля
  2. мы смешали код работы с АЦП с кодом регулятора (инкапсуляция нарушена)

Если мы АЦП захотим использовать еще в паре мест, то придется тщательно работать над рефакторингом:


  • восстанавливать инкапсуляцию (котлеты отдельно, мухи — отдельно)
  • организовать еще одно хранилище — результаты работы АЦП между вызовами надо где-то хранить

Итого получается у нас при таком подходе недостатки:


  1. Резко возрастает сложность программ;
  2. Появляются внешние (по отношению к программным сущностям) хранилища данных (где мы их храним: в статических переменных или глобальных — вопрос стиля);
  3. Либо если мы хотим отказаться от внешних хранилищ данных, появляется протокол обмена данными (каждая функция может вернуть данные или признак их отсутствия. Другие функции будут обрабатывать данные или ничего не делать при их отсутствии.

Если мы решим вывести наш холодильник в интернет, то программирование его в такой парадигме может стать адом.


Как бороться с этим адом?


Резкое занижение требований к ПО


Это нормальный метод, если он подходит, то можно на нем остановиться. Помните выше мы сформулировали что можно не разворачивать функцию get_adc, а оставить как есть.


И так сойдёт!


Вполне себе работоспособный подход, для холодильника (без сложного интернета) подойдет вполне.


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


Треды и процессы


В какой-то момент возникает соблазн даже взять и портировать на нашу систему полноценные треды/процессы. Гугля находим массу проектов, например вот этот.


Заглядывая в код треда видим все те же функции. Разглядывая код поближе — видим попытку организовать периодический вызов функций через примерно равные интервалы.


Например


loop() { ... }

Вызывается максимально часто, а вот Thread.onRun можно сконфигурировать чтобы вызывался скажем раз в две секунды.


То есть человек назвал тредом то что не является тредом в смысле CPU. Увы.


Реальных тредов в том понимании как их понимают в "большом" мире я не нашел. Буду благодарен, если кто-то подбросит мне ссылку на такой проект.


Однако в рамках обсуждения тредов и процессов скажу еще что в "большом" мире для решения задач треды обычно не применяют.
Почему? Реализация тредов (процессов) неизбежно приводит нас к введению понятия "квант времени": каждый тред/процесс выполняется определенный квант времени, после чего управление у него отнимается и передается другому треду/процессу.


Такая многозадачность называется вытесняющей: текущий процесс вытесняется следующим.


Почему в рамках "больших" проектов треды в основном не применяются? Для того чтобы заставить работать множество тредов на одном CPU необходимо делать очень маленький квант времени. Частота квантования например на современном Linux — равна 1000Гц. То есть если у Вас в системе выполняется 1000 процессов одновременно, то каждый из них будет получать 1 квант времени на 1мс один раз в секунду (это если оверхеда на вытеснение нет), а в реальном мире переключая 1000 процессов хорошо если получится выдать каждому по милисекунде раз в десять секунд.


Кроме того, поскольку многозадачность вытесняющая, то возникает масса вопросов по межпроцессному взаимодействию. Возились с гонками между прерыванием Arduino и основной программой? И здесь те же проблемы.


В общем все нагруженные, так называемые HighLoad проекты в "большом" мире делают без массового использования тредов. Проекты HighLoad делают с применением кооперативной, а не вытесняющей многозадачности.


Чтобы обслужить 1000 клиентов выделив каждому тред — нужно примерно 20 современных компьютеров. При том что на практике достаточно одного сервера чтобы обслужить 50 тыс клиентов.


Кооперативная многозадачность


Что это такое? Вот типовой loop() проекта Arduino и есть один из вариантов кооперативной многозадачности: переключение к следующей функции не произойдет до тех пор пока предыдущая не завершится. Все функции стараемся писать чтобы они возвращали управление максимально быстро и таким способом решаем задачу.


Этот способ реализации кооперативной многозадачности можно называть колбечным (или функциональным).
Если обратиться к "большому" миру, там есть проекты для HighLoad построенные исключительно на этом способе, например тот же Node.JS.


Если Вы почитаете отзывы о Node.JS, то увидите весь набор от восторженных "наконец я нашел инструмент на котором МОЖНО реализовать мою задачу", до типовых: "callback hell!".


Существует второй способ реализации кооперативной многозадачности — сопрограммы (корутины, файберы). Идея тут примерно такая же как в традиционных тредах: каждый процесс работает как бы независимо от других. Однако ключевое отличие тут в том, что переключение между процессами производится не по таймеру, а тогда, когда сам процесс решит что ему процессор больше не нужен.


Какие прелести дает подобный подход?


  1. Нет ада функций;
  2. Межпроцессное взаимодействие очень простое (ведь если процесс не прервут в критической секции, то и само понятие "критическая секция" нивелируется): мютексы, семафоры — все или резко упрощается или заменяется простыми переменными;

"Лучшие умы человечества" (ц) разрабатывавшие в прошлом веке для нас язык C и писавшие о нем книги по которым многие из нас учились читать, попытались обобщить все достоинства и тредов и кооперативной нефункциональной многозадачности и в итоге родился язык для HighLoad — Go.


Но впрочем давайте вернемся из большого мира в наш мир Arduino. Go у нас нет, поэтому будем работать с тем что есть.


Кооперативная многозадачность в Arduino


Итак нам нужны:


  1. Возможность создать процесс;
  2. Возможность переключиться на другой процесс из процесса.

Традиционное название функции переключения между процессами — yield или cede.


Вернемся к нашей функции get_adc:


uint16_t
get_adc(uint8_t input)
{
    while (adc_is_busy());
    adc_switch_mux(input);
    adc_start();
    while (adc_is_busy());
    return adc_value();
}

Если бы у нас были кооперативные процессы, то в их среде ее бы следовало доработать до следующего вида:


uint16_t
get_adc(uint8_t input)
{
    while (adc_is_busy())
        cede();         // пока ждем - пусть другие работают
    adc_switch_mux(input);
    adc_start();
    while (adc_is_busy())
        cede();         // пока ждем - пусть другие работают
    return adc_value();
}

Температурный регулятор выглядел бы так:


void
temperature_regulator(void) {

    for (;;) {
        uint16_t current_t = get_adc(INPUT_T);

        if (current_t > t_on)
            enable_cooler();

        if (current_t < t_off)
            disable_cooler();
    }
}

Но позвольте! Тут же никаких изменений нет! Скажете Вы. А все изменения вошли в get_adc, зачем нам еще?


Ну и управление светом тоже доработаем:


void
light_control(void)
{
    for (;;) {
        if (sensor_door() == DOOR_OPEN)
            light_on();
        else
            light_off();

        cede(); // сам поработал - дай другому
    }
}

Красиво? Наглядно? По моему максимально наглядно насколько это возможно.


Все используемые нами функции подразделяются на два вида:


  1. вызывающие yield/cede внутри себя
  2. остальные

Если в Вашем цикле есть хоть одна функция гарантировано вызывающая yield/cede внутри себя, то добавлять вызовы cede()/yield() не нужно.


В "большом" мире хорошим тоном считается писать вызовы cede()/yield() внутри так называемых низкоуровневых функций и сводить вызовы этих операторов к минимуму.


Сколько это будет стоить?


Поскольку процессы все-таки слабо зависимы друг с другом (хоть и передают друг другу управление), то, очевидно, у каждого процесса должен быть собственный стек.


Собственный стек — понятие относительно дорогое. Происходит например прерывание. В стек попадает текущий адрес выполнения программы. Так же в стек попадают все регистры которые будут использованы в прерывании.
Вы вызываете функцию: в стек попадают адрес возврата, все ее аргументы и ее временные переменные.


Я занимался замерами, практика показывает что прерывание обслуживающее например таймер (проинкрементировать счетчик, послать уведомление) занимает на стеке 16-30 байт. Ну и обычный цикл тоже доходит до 16-30 байт глубины. Например наш temperature_regulator занимает на стеке:


  1. всего 2 байта под свою переменную
  2. 2 байта на вызов get_adc
  3. 1 байт на аргумент для get_adc
  4. 2 байта на вызов adc_switch_mux
  5. 1 байт на ее аргумент

Итого 2 + 2 + 1 + 2 + 1 = 8 байт. Плюс компилятор посохраняет регистры в стек/подостает их оттуда. Умножим на два. Плюс возможный вектор прерывания. Итого получается где-то 50-60 байт на файбер нам было бы достаточно. Сколько файберов можем запустить на Arduino nano328? 5-10 штук со стеком 64-128 и еще останется память для всего остального.


Расходы памяти на 5-10 полноценных файберов стоят упрощения реализации алгоритмов программы? Ну и поскольку 640 килобайт хватит всем 5-10 файберов хватит для того чтобы комфортно написать не только холодильник но и скажем http-клиента займемся написанием такой библиотеки!


Прототип


Мной реализован прототип (уже можно пользоваться но API будет расширяться) библиотеки файберов для Arduino. Пока только AVR (есть завязки на avr-libc).


Данная статья пишется в гит того же проекта и оригинал ее (будет дорабатываться) лежит здесь.


Библиотека написана на чистом C (не C++).


API


Начинается все с инклюда и инициализации:


#include <fiber.h>

void
setup()
{
    ...
    fibers_init();
}

Для создания файбера используется функция fiber_create, принимающая ссылку на заранее выделенный для него стек, его размер и ссылку на данные которые будут переданы в файбер:


struct fiber *
fiber_create(fiber_cb cb, void *stack, size_t stack_size, void *data);

Для того чтобы не мучиться в программе над выделением стека, предусмотрены пара макросов:


FIBER_CREATE(__cb, __stack_size, __data);
FIBERV_CREATE(__cb, __stack_size);

Которые за Вас выделят память в статической области (обратите внимание: malloc не используется, поэтому нельзя применять эти макросы в цикле).


Или даже если Вы определите заранее какой размер стека будут иметь все Ваши файберы то два макроса:


FIBER(__cb, __data);
FIBERV(__cb);

для определения файбера.


Файбер может иметь несколько состояний, вот базовые:


  • 'работает' — обычное состояние
  • 'усыплен' — не автопланируется

Усыпить можно только текущий файбер (то есть сам файбер себя усыпляет, получив ссылку на себя fiber_current) вызвав функцию fiber_schedule, разбудить — функцией fiber_wakeup:



    struct fiber *waiter;

    // где-то в файбере
    waiter = fiber_current();
    fiber_schedule();

    // где-то в другом месте, например в прерывании или другом файбере
    if (waiter) {
        fiber_wakeup(waiter);
        waiter = NULL;
    }

На механизме усыпления/пробуждения можно сокращать использование CPU на циклах ожидания: если Ваш файбер сейчас ждет и есть кому разбудить (например прерывание), то можно его усыпить: другие файберы получат больше процессорного времени на работу.


Передача управления из процесса в процесс выполняется вызовом fiber_cede():


void
light_control(void)
{
    for (;;) {
        if (sensor_door() == DOOR_OPEN)
            light_on();
        else
            light_off();

        fiber_cede(); // сам поработал - дай другому
    }
}

Тонкости


  • Никакие функции (кроме fiber_wakeup) нельзя вызывать из прерываний. Это обстоятельство видимо не преодолеть;
  • Нет возможности контроллировать автоматически переполнение стека;

Соответственно некоторые файберные паттерны из "большого" мира тут применять не получится. Данная библиотека годится под паттерн: на стадии инициализации запускаем N файберов и дальше они работают.


Паттерн "при событии запускаем файбер", при повторном — еще один — тут увы не проходит. Ресурсов Arduino не хватит. Но можете "разбудить" того кто обрабатывает редкие события.


Паттерн: положи ссылку на себя в переменную, засни, а прерывание тебя разбудит — имеет тонкости: прерывание может прийти раньше чем мы заснём. На эту тему решение внедрено, но требует дополнительного разъяснения. Просто исходите из того что так делать МОЖНО.


Что не доделано


  1. Пока не дооформил как библиотеку Arduino: не смог понять пока как заставить его компилировать (из GUI имеется ввиду, с Makefile-то все просто) C'шные файлы в стандарте C99 (даже заголовки avr-libc предполагают C99). Не хочу оформлять как C++ (потому что не только Arduino в планах);
  2. В большом мире есть fiber_join, так и не знаю стоит ли его реализовывать.
  3. Больной вопрос: если программа использует регистровые переменные и при этом переключает файберы, то предсказать поведение невозможно. Сохранять ВСЕ регистры на стеке — дополнительный оверхед. Пока не натыкался на эти проблемы. Возможно придется сделать опцию: будут файберы более требовательны к памяти, но более надёжные;

В общем буду рад коментариям, дельным предложениям и pull-реквестам :)


Пинг-понг на файберах:


#define FIBER_STACK_SIZE 64
#include <fiber.h>
#include <stdio.h>

void
ping(void *data)
{
       printf("ping\n");
       fiber_cede();
}

void
pong(void *data)
{
     printf("pong\n");
     fiber_cede();
}

void
setup(void)
{
       fibers_init();
       FIBERV(ping);
       FIBERV(pong);
}

void
loop()
{
     fiber_schedule(); // этот файбер нам не нужен
}

Ссылки


  1. Библиотека fiber;
  2. Эта статья в Git;
  3. Библиотека Thread, в тексте статьи;
  4. Многозадачность в терминах википедии;
  5. Когда-то на HighLoad выступал о аналогичном но в "большом" мире;
  6. Node.JS.

Комментарии (111)


  1. Alex_ME
    22.11.2017 02:21

    Разработка интересная, но…
    Сомнительный посыл про то, что вытесняющая многозадачность не подходит. Сейчас у меня в системе ~2156 потоков. Каких-то проблем, которые должны возникнуть, если каждый поток выполняется 1мс за 10-20с не обнаруживается. Ответ заключается в приоритетах и ожиданиях.


    На встраиваемой системе потоков обычно не много. И чаще всего им не надо работать постоянно. Большая часть потоков находится в состоянии блокировки и ожидании событий, планировщик их не вызывает. А ОСРВ с вытесняющей многозадачностью удобнее и проще в использовании зачастую (кроме общего доступа). Тот же FreeRTOS легковесен и доступен под AVR.


    1. Dima_Sharihin
      22.11.2017 07:59

      Проблема вытесняющей многозадачности — в повышенном (в разы, по сравнению с кооперативкой) требовании к оперативной памяти. Каждая задача требует как минимум места под полный контекст процессора и отдельно еще (в лучшем случае) стек под прерывания.


      1. На некоторых МК недостаточно ОЗУ, чтобы на каждый чих выделять стек (особенно когда трудно предсказать его загруженность)
      2. Не считая FreeRTOS (у меня к ним претензии только за naming convention), многие RTOS не распространены дальше одной-двух платформ. Кооперативки в чистом виде (Protothreads) работают на любом компиляторе.

      Но мне нравится идея вытесняющей многозадачности, а еще больше нравится концепт QP, жаль для коммерческого использования оно платное, а просто так поиграться еще времени не появлялось


    1. linuxover Автор
      22.11.2017 09:32

      Тот же FreeRTOS легковесен и доступен под AVR.

      надо посмотреть на него, спасибо


    1. rkfg
      22.11.2017 09:44

      Ответ заключается в приоритетах и ожиданиях.

      Я правильно понимаю, что при таймере с частотой 1000 Гц потоку гарантируется максимум в 1 мс времени каждую секунду, но если он в состоянии S, то переключение, фактически, произойдёт намного быстрее, и неиспользованное время достанется другим потокам, выполняющим код? Ну это при условии, что у всех потоков равный приоритет, конечно. А при неравных кто-то, у кого приоритет выше, может получить заведомо больше одного тика?


      Вообще, я думал, таймер и квант выполнения не связаны и отличаются на порядки. Например, в Debian ядро собирают с таймером 100 Гц, неужели там настолько крупные кванты, по 10 мс?


      1. Alex_ME
        22.11.2017 10:39

        Да. Например, если поток пытался взять мьютекс и уснул, то система не будет давать ему процессорное время, до тех пор, пока мьютекс не будет освобожден (правда, для коротких пауз есть спинлоки, чтобы не иметь лишних затрат на смену контекста). Будут вызываться другие потоки.


        неужели там настолько крупные кванты, по 10 мс?

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


      1. lingvo
        22.11.2017 11:47

        Попытаюсь объяснить, так как это является одной из основных функций типовых операционных систем реального времени.
        В ОСРВ задачей таймера с частотой 1000Гц — это просто делать прерывание каждую миллисекунду, которое вызывает одну конкретную функцию ОС — планировщик задач. При этом приоритет этого таймера выше приоритета всех выполняющихся задач в системе, чтобы планировщик гарантированно выполнялся раз в миллисекунду.
        Что же делает планировщик задач? В зависимости от выбранного типа многозадачности он может:


        • При round-robin многозадачности, он проверяет израсходовала ли текущая задача свой лимит времени (это обычно в несколько раз выше частоты планировщика. При 1мс, это может быть 4-10мс) и если да, переключает контекст на следующую задачу с таким же приоритетом, если она готова к выполнению.
        • При вытесняющей многозадачности планировщик смотрит нет ли более высоко-приоритетной задачи, которая готова к выполнению и переключается на нее, если такая есть.
        • Иначе продолжает выполнение текущей задачи.

        Таким образом при round-robin задаче гарантируется определенное непрерывное время исполнения, которое можно настроить. При preemptive время гарантируется только самому высоко-приоритетному потоку, и для правильной работы надо распределять приоритеты между задачами, например по концепции rate-monotonic scheduling.


        Благодаря этому в ОСРВ реализуется основное понятие реального времени — гарантированное время реакции, которое в данном конкретном случае будет составлять 1мс.


        1. rkfg
          22.11.2017 11:52

          О, спасибо, вот эту информацию я как-то и не мог найти. В частности, чем же отличается preemptive kernel от обычного, вроде, везде уже в современных ОС многозадачность не кооперативная. А preemptive я лично собираю, потому что без него в PulseAudio появляются ощутимые скипы, теперь понятно, откуда это всё. Хотя линукс без патчей и не является ОСРВ, эти концепции подходят.


        1. Dima_Sharihin
          22.11.2017 14:08

          Вообще-то переключение контекста может произойти и гораздо раньше одной миллисекунды, если просемафорил не таймер, а какое-то другое прерывание. Если задача, ждущая семафор, находится выше по приоритету, чем активная, то после выхода из прерывания произойдет переход в задачу с семафором.


          1. lingvo
            22.11.2017 16:43

            Ну да, но это уже более детально.
            В частности, естественно, переключение контекста также произойдет раньше, если текущая задача заблокировалась, например в ожидании какого либо флага, или ей надо сделать паузу. Тогда произойдет автоматическое переключение на следующую готовую задачу такого же приоритета или на продолжение выполнения более низкоприоритетной задачи, которая до этого была прервана планировщиком


        1. Ryppka
          23.11.2017 15:12

          1. При round-robin многозадачности...
          2. При вытесняющей многозадачности планировщик ...

          round-robin scheduling в вашем описании — тоже вытесняющая многозадачность. Если все задачи во втором случае имеют одинаковый приоритет — получается как-раз round-robin scheduling, просто есть и другие алгоритмы планирования. Противоположность вытесняющей многозадачности — кооперативная. Т.е. важно кто инициирует переключение контекста: если планировщик по таймеру или еще как, то вытесняющая, а если сама задача «уступает» — то кооперативная. Как-то так. А в остальном — согласен на все сто.


    1. linuxover Автор
      22.11.2017 11:10

      > Сейчас у меня в системе ~2156 потоков.
      > Ответ заключается в приоритетах и ожиданиях.

      у Вас в системе 2000 потоков, но и событий в секунду крайне немного. Если пустить хотя-бы 100 событий в секунду будящих случайные из 2000 потоков, то система будет работать где-то на грани около максимума производительности.

      если поток событий возрастет до 500 в секунду — то потоковая модель уже просто будет стоять и требовать апгрейда.
      меж тем ОДИН поток на асинхронной парадигме вполне справится с 15 тыс событий в сек.

      обслуживать запросы 50К клиентов одним CPU — вполне рядовая задачка. и 50К ограничивается как правило не CPU а числом сокетов :-\


      1. Alex_ME
        22.11.2017 12:57

        Так я это и привелтв ккчестве примера того, что большинство потоков спят.


        1. linuxover Автор
          22.11.2017 13:04

          я это понимаю. я писал что в современных HighLoad модель обслуживания запросов клиентов тредами как правило не применяют.


          фиксированный пул тредов — да, по треду на клиента — редкость.


          у Вас получилось иметь приемлемую нагрузку при 2000 процессах, но среднестатистически это не получается. У меня одно объяснение — у Вас низкая частота событий в системе — можете себе позволить "спать" всем этим 2000.


          но это как бы не хайлоад. все эти 2000 тредов можно заменить на 2000 файберов в одном процессе и у системы появится возможность нарастить число клиентов на том же одном процессе до 20000 :)


          как-то так :)


          1. Alex_ME
            22.11.2017 13:17

            Ну я и не спорю. Более того, часто схема запрос каждого пользователя обрабатывается быстро и подключение не висит долго. Потоковая модель потребовала бы создание нового треда, а затем завершение, что еше более накладно.


            но это как бы не хайлоад. все эти 2000 тредов можно заменить на 2000 файберов в одном процессе и у системы появится возможность нарастить число клиентов на том же одном процессе до 20000 :)

            Я привел в пример просто свой десктоп с win10. Ясно, что тут своя специфика. Много разнородных поэроцессов и потоков


            1. linuxover Автор
              22.11.2017 14:09

              > Я привел в пример просто свой десктоп с win10

              а десктоп. десктоп — это такая штука которая 99.9% времени ничего ж не делает (ждет ввода пользователя).


  1. xapon
    22.11.2017 07:43

    А как насчёт FreeRTOS http://www.freertos.org? Вроде бы все что нужно есть — таски (они же треды), и очереди для коммуникации между ними в стиле go.


  1. borisxm
    22.11.2017 08:20

    Для кооперативной многозадачности в AVR'ах, пока ничего лучше Protothreads не придумали. Есть и порт на C++.


    1. linuxover Автор
      22.11.2017 09:30

      прототред — это по сути работа над калбечным вариантом кооперативной многозадачности.


      то есть все тот же loop по сути, который препроцессором преобразуется в некое подобие функций.
      программирование ПОХОЖЕ на традиционное, но использование переменных на стеке затруднено (поскольку функция все равно перевызывается, то переменные в ней между вызовами не живут).


      в данном же случае мы пишем приложения в обычном стиле


      1. borisxm
        22.11.2017 15:34

        использование переменных на стеке затруднено

        Верно, но это касается лишь функций которые содержат вызовы wait/yield, а в них все равно требуется повышенная внимательность при написании. Кроме того, сохранение пары переменных состояния, обычно гораздо дешевле сохранения всего контекста. А с учетом возможностей нынешних компиляторов по оптимизации кода без указателей на функции, такие переменные вообще могут не занимать места в статической памяти.


  1. EGregor_IX
    22.11.2017 09:03

    Есть очень мелкая оська для всяких микрашей под названием OSA. Минимальна, работает даже на тиньках.


  1. lingvo
    22.11.2017 09:31

    Боже, какой замечательный велосипед! И как хорошо, что такие "большие" программисты еще не добрались до автопилотов и систем управления автомобилей, а то было бы совсем худо.
    Вы еще даже не выбрались в интернет со своим холодильником и не поуправляли ничем сложнее, чем GPIO и ADC, а уже изобрели подобие RTOS, понятие какого-то условного реалтайма, попытались представить себе тысячную многозадачность на микроконтроллере…
    Данный пост — это очень интересный пример, как Ардуино извращает понимание проектирования встраиваемых систем до неузнаваемости.


    1. linuxover Автор
      22.11.2017 18:01

      Боже, какой замечательный велосипед! И как хорошо, что такие "большие" программисты еще не добрались до автопилотов и систем управления автомобилей, а то было бы совсем худо.

      а что ж хорошие программисты, которые программируют автопилоты не могут нам ничего на старт предложить в Arduino, кроме Blink.ino delay которого базируется на пустых циклах?


      Вы еще даже не выбрались в интернет со своим холодильником и не поуправляли ничем сложнее, чем GPIO и ADC, а уже изобрели подобие RTOS

      10 лет назад я ушел из Embedded, сейчас вернулся на ардуино для "домашней" (то есть в личных целях) задачки и пытаюсь полученный в области "большого" мира опыт перенести туда над чем я мучился еще 10 лет назад.


      последний проект 10 лет назад как раз был построен на калбеках


      вот кусочек кода из последнего проекта (типа плата о себе раз в пол секунды в CAN-шину чет передает):


      // общий init
      ...
      event_timer(DELAY_s(.2), _send_status);
      ....
      static void _send_status(struct timer *tmr) {
          static uint8_t stage;
          struct can_message msg;
      
          led_switch(STATUS_SEND_LED);
          uint8_t sensor;
      
          switch(stage) {
              /* когда все передано, начинаем с начала */
              default:
                  stage = 0;
      
              case 0:
                  msg.id = CAN_MEASURE_T1;
                  sensor = T1;
                  break;
              case 1:
                  msg.id = CAN_MEASURE_T2;
                  sensor = T2;
                  break;
      ...
      stage++;
      msg.sample.value = sensor_value(sensor);
      can_send_message(&msg);
      ....

      то есть еще 10 лет назад возявкались мы с этими же колбеками которые предлагаются ардуиной сейчас. Мало того тогда все эти обвязки вроде event_timer писали сразу для AVR/ARM/PIC переносимо.
      Всякие CAN/ModBus прокси и прочее — на калбеках — это АД. Пусть и структурированный.


      Сейчас мне потребовалось реально "холодильник в интернет вывести" и я как посмотрел на тот ад что предстоит: весь этот набор команд AT — стейтмашину хоть бери да на рагеле пиши. И кстати почему бы и не на рагеле/флексе ее и не написать?


      Вы, настоящий embedded программист, как относитесь к идее общение с WiFi на рагеле/флексе написать?


      1. lingvo
        22.11.2017 18:49

        а что ж хорошие программисты, которые программируют автопилоты не могут нам ничего на старт предложить в Arduino, кроме Blink.ino delay которого базируется на пустых циклах?

        Почему не могут? Вы же не спрашиваете. :-)


        то есть еще 10 лет назад возявкались мы с этими же колбеками которые предлагаются ардуиной сейчас.

        Ну как бы у меня тоже 15 лет опыта и с калбеками я не возявкался, хотя на одном 8-битнике и RS232 и CAN и таймеры с SPI — и все работало в реальном времени. Так что не могу сказать, что "Мы". Скорей "Вы", это не было мейнстримом.


        Вы, настоящий embedded программист, как относитесь к идее общение с WiFi на рагеле/флексе написать?

        Никак. Я не знаю что такое "на рагеле/флексе".


        1. linuxover Автор
          22.11.2017 19:19
          +1

          > Ну как бы у меня тоже 15 лет опыта и с калбеками я не возявкался

          а с чем возявкались? если не калбеки то что? вариантов то немного же

          > Никак. Я не знаю что такое «на рагеле/флексе».

          языки программирования предназначенные для работы со стейтмашинами


        1. linuxover Автор
          22.11.2017 19:20

          Так что не могу сказать, что "Мы". Скорей "Вы", это не было мейнстримом.

          так расскажите в двух словах, как мейнстрим программит параллельные процессы-то?


          1. lingvo
            22.11.2017 20:28
            +1

            Если говорить о серьезном ембеддерском мейнстриме — который идет сегодня в реальное железо — авто/мото/самолето/ракето-электронику, индустриалку, и прочую электронику, которая хоть чем-то управляет в реальном времени, то в первую очередь это ОСРВ.
            µC/OS, RTX, QNX, vxWorks, FreeRTOS и куча других ОС, которые портированы на сотни встраиваемых аппаратных платформ(Arduino, кстати часто в их числе) и дают реальную многозадачность из коробки на любой вкус и кошелек.


            1. linuxover Автор
              22.11.2017 23:01

              можете привести пример приложения, которому требуются ДВА процесса реального времени не реализуемые на прерываниях и заодно поведать о том какое время гарантированной реакции процесса дает FreeRTOS например?


              1. lingvo
                22.11.2017 23:21

                Не очень понятно, что вы хотите. Почему именно два, а не пять или десять? Причем здесь прерывания? Про время гарантированной реакции я, по-моему, понятно объяснил вот здесь. FreeRTOS, насколько я знаю, также исповедует данный общий принцип


                1. linuxover Автор
                  22.11.2017 23:40

                  ну хотя бы два. я просто, как вы понимаете, не такой специалист и представить себе некоторых вещей не могу ;)

                  итак РЕАЛЬНОЕ применение с двумя процессами которым требуется реалтайм есть?


                  1. lingvo
                    23.11.2017 12:09

                    итак РЕАЛЬНОЕ применение с двумя процессами которым требуется реалтайм есть?

                    Ну вот у вас же самих реальное применение:


                    • Управлять лампочкой по датчику
                    • отслеживать температуру в холодильнике.
                      Два независимых процесса.

                    Также при rate-monotonic scheduling каждое время цикла должно исполняться в отдельном процессе.


                    1. linuxover Автор
                      23.11.2017 12:19

                      Два независимых процесса.

                      независимых != реалтаймовых.


                      я же в статье и пишу про то что вытесняющая многозадачность практически нигде не нужна.
                      но Вы тут спозиционировали себя как специалиста который не велосипедит а знает как правильно.


                      Так что не могу сказать, что "Мы". Скорей "Вы", это не было мейнстримом.

                      вот я и интересуюсь, что там в мейнстриме? есть ли примеры РЕАЛЬНЫЕ? для расширения кругозора так сказать


                      1. lingvo
                        23.11.2017 12:42

                        независимых != реалтаймовых.

                        Почему Вы считаете, что у вас не-реалтаймовые процессы? Думаете, что если у вас лампочка зажжется на пару секунд позже, это не будет Вас раздражать?


                        я же в статье и пишу про то что вытесняющая многозадачность практически нигде не нужна.

                        Еще один пример независимых процессов — это обработка клавиатуры/кнопок и отображение. Поэтому практически в каждом устройстве вокруг вас, которое имеет дисплей, кнопки и чем-то управляет — стиральная, кофейная машина, лифт, — у вас будут разные процессы, где обработка клавиатуры будет иметь свой приоритет. И если в каком-то из таких устройств кнопки будут "тормозить" — то скорей всего RTOS там нет.


                        1. linuxover Автор
                          23.11.2017 13:30

                          Думаете, что если у вас лампочка зажжется на пару секунд позже, это не будет Вас раздражать?

                          Пара секунд — будет
                          пара милисекунд нет


                          а второе делается без всякого реалтайма


                          Еще один пример независимых процессов — это обработка клавиатуры/кнопок и отображение.

                          обработка кнопок опять реалтайм не нужен.


                          пользователь нажмет кнопку: свет зажжется сразу по нажатии или через 20 милисекунд — совершенно безразлично.


                          то есть обработка кнопок — опять задача не реального времени.
                          параллельных процессов — да. Но зачем параллельные реалтаймовые процессы тут?


                          1. lingvo
                            23.11.2017 13:58
                            +1

                            Потому, что вы упорно в своих рассуждениях не хотите заметить одну вещь:


                            Думаете, что если у вас лампочка зажжется на пару секунд позже, это не будет Вас раздражать? Пара секунд — будет

                            Это фейл.


                            обработка кнопок опять реалтайм не нужен.
                            пользователь нажмет кнопку: свет зажжется сразу по нажатии или через 20 милисекунд — совершенно безразлично.

                            А если через 40 мс- это фейл.


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


                            И вот это максимальное время реакции — оно фиксировано. Его не изменишь в проекте потом, его не поменяешь в требованиях. Во многих вещах оно вообще ограничено физическими законами — например в системах защиты электросетей защитным устройствам надо среагировать за пол-периода частоты сети — это 10мс. Поэтому это время вам придется соблюдать, независимо от количества задач в вашем контроллере и его загрузки и на любой стадии проекта.


                            Вам просто кажется, — ой ну пусть свет в холодильнике будет зажигаться максимум, скажем, через 0,3 секунды, а задержка обработок кнопок будет максимум 40мс.
                            С двумя этими задачами вы справитесь и с прерываниями. И вроде как свободного времени у процессора осталось полно. Но потом добавите свой Wi-Fi модуль, АЦП, AT-команды, SD-карту и — ой — худшее время реакции на клавиатуру вдруг стало не 40, а 100мс. И что будете делать? Начнете говорить, что 40 и 100мс — это тоже безразлично? Рассказывать про какой-то условный реалтайм?


                            1. linuxover Автор
                              23.11.2017 14:20

                              > А если через 40 мс- это фейл.

                              и 40 мс — не фейл. 0.04 секунды пользователю пофиг
                              400 мс — да фейл.

                              > То есть обе задачи у вас — это задачи реального времени.

                              что такое ОС реального времени и почему там разговоры ведут о методах вытеснения процессов? потому что разговор всегда ведется не о 40-400 милисекунд, а о 40-1000 микросекунд

                              то есть таки понятие «реальное время» это о микросекундах.

                              если говорить о милисекундах — какая разница: ROUND ROBIN, PREEMPTIVE или вообще кооп многозадачность?
                              никакой разницы.

                              с реакцией 60 милисекунд можно даже по USART отвечать вполне: никого эта пауза не взволнует особо


                          1. h0rr0rr_drag0n
                            23.11.2017 14:04

                            Думаете, что если у вас лампочка зажжется на пару секунд позже, это не будет Вас раздражать?

                            Пара секунд — будет
                            пара милисекунд нет

                            а второе делается без всякого реалтайма


                            Так если у вас в системе гарантируется определённая задержка между сигналом с датчика и включением лампочки — пара миллисекунд — то это и есть реальное время. С учётом того, что от нарушения гарантий ничего особо страшного не произойдёт — это мягкое реальное время.

                            Еще один пример независимых процессов — это обработка клавиатуры/кнопок и отображение.

                            обработка кнопок опять реалтайм не нужен.

                            пользователь нажмет кнопку: свет зажжется сразу по нажатии или через 20 милисекунд — совершенно безразлично.


                            Боюсь, что нет — если пользователь, вызубрив все функции устройства, начнёт управлять им тыкая по кнопкам заученными движениями, а устройство будет тупить, потому что у нас нет гарантированного времени реакции на действия пользователя, то ничего кроме ненависти к разработчику такое устройство не принесёт.


                            1. linuxover Автор
                              23.11.2017 14:26

                              Так если у вас в системе гарантируется определённая задержка между сигналом с датчика и включением лампочки — пара миллисекунд — то это и есть реальное время.

                              я никогда не буду заниматься обеспечением гарантии этого времени для лампочки.


                              именно поэтому я просил Вас привести реальный пример мест где это нужно. Причем чтобы это нужно было в количестве 2+ штук на устройство.


                              вот мы например делали векторное управление двигателем: там жестко было: надо было дифуравнение порешать 6 раз за электрический оборот: там никакая RTOS не подходила: дифура решалась тупо в прерывании (и плевать что занимала 9/10 процессорного времени), а оставшееся от основной реалтайм-функции время распределялось машиной условных таймеров (дает в среднем X вызовов функции в секунду) между всеми остальными задачами.


                              приложений где надо было ДВЕ задачи реального времени решать — я не встречал (прерывания не считаются задачей реального времени: то есть считаются, но это решение аппаратное, а не операционной системы реального времени)


                              1. lingvo
                                23.11.2017 16:03
                                +1

                                Простой пример — система управления лифтом:


                                • цикл управления движением кабины и открытия закрытия дверей — порядка 5мс, так как иначе лифт не будет успевать отрабатывать показания датчиков и неточно останавливаться (на скорости 4м/с за 5мс лифт проезжает 20см). Также нужна защита привода от перегрузок, которая должна отрабатываться через четко определенное время.
                                • опрос кнопок вызова на этажах и кабине — раз в 20мс. Быстрее не надо и иначе надо отрабатывать дребезг. Медленнее — люди начнут бить по кнопкам.
                                • алгоритм, который определяет какому лифту на какой этаж ехать — 200мс. Быстрее не получится, так как там нечеткая логика, медленней — не будет поспевать за изменяющейся ситуацией.
                                • параллельно всему этому идет обмен по Modbus RTU, CAN и RS232.
                                • параллельно этому всему опрос и обновление порядка сотни различных логических сигналов. Защита от нештатных ситуаций (неправильные комбинации, обрыв цепей )


                                1. linuxover Автор
                                  23.11.2017 16:28

                                  вот в этом списке критическое только обработка концевиков связанных со скоростью лифта. Тут эта хрень скорее всего реализуется на прерывании.
                                  Но даже в рамках указанных допусков — милисекунда — это сколько тысяч инструкций AVR? Нафига тут вытеснение?

                                  типа мы неправильно построили архитектуру: вывод который должен генерить прерывания — не генерит и мы исправляем это RTOS'ом с вытеснением?
                                  жуть какая-то

                                  > алгоритм, который определяет какому лифту на какой этаж ехать — 200мс. Быстрее не получится, так как там нечеткая логика

                                  эта, ну пусть 100 этажей. что ж за алгоритм там? задачу комивояжера решаем? ну дык что ее решать 200мс-то? Есть же алгоритм градиентного спуска например.

                                  > параллельно всему этому идет обмен по Modbus RTU, CAN и RS232.

                                  этот мусор кто-то еще делает программно? нет? а аппаратно — там же уже некритичны становятся задержки на «общее управление»


                                  1. lingvo
                                    23.11.2017 17:18

                                    Окей. Сделайте вашу лампочку с холодильником и интернетом, тогда поговорим.


                  1. Dima_Sharihin
                    23.11.2017 15:46
                    +1

                    Не знаю, что вы так вцепились в слово "реалтайм", но есть класс задач, в которых вытесняющие RTOS очень хорошо себя показывают: математика с разделением по приоритетам.


                    К примеру, какой-то набор вычислений надо делать на каждую выборку АЦП, при этом когда мы закончили с этой выборкой, можно вернуться к фоновому длинному математическому процессу. Сделать длинный занудный алгоритм вроде БПФ или еще чего кооперативным — проще застрелиться, эти алгоритмы никто не велосипедит, их берут готовыми и они все как один блокирующие до конца выполнения.


                    Когда все построено на очередях будет сразу видно состояние overrun, то есть алгоритм не успевает обработать принятые данные.


                    1. linuxover Автор
                      23.11.2017 15:50

                      > Не знаю, что вы так вцепились в слово «реалтайм», но есть класс задач, в которых вытесняющие RTOS очень хорошо себя показывают: математика с разделением по приоритетам.

                      дык из пушки по воробъям же.
                      недаром в «большом мире» в HighLoad нафиг практически везде шлют это самое вытеснение и строят даже новые языки на том чтоб максимально его НЕ использовать.

                      > эти алгоритмы никто не велосипедит, их берут готовыми и они все как один блокирующие до конца выполнения.

                      вот уже теплее: то есть пользуемся внешней библиотекой, не поддающейся редактированию. хороший пример, но опять же редкий имхо


                      1. Dima_Sharihin
                        23.11.2017 15:56

                        Ага, давайте еще перепишем рекурсивный алгоритм на машину состояний.


                        практически везде шлют это самое вытеснение

                        Ой, да тут все просто — архитектура х86-64 — неповоротливый монстр с тяжеленнейшим контекстом, длинным конвейером и кучей кешей. Его в принципе нельзя отвлекать от важной работы. Получается, что крутиться в цикле while (my_flag.await_condition()) банально быстрее, чем сохранить и восстановить контекст.
                        Особенно в многоядерных системах, когда приложение может практически монопольно оккупировать процессорное ядро.


                        А теперь берем конкретный пример: железка, которая обслуживает математические вычисления в реалтайме. На этом же процессоре крутится GUI на Qt, сетевые службы и прочая лабудень. Если Qt решит чуть подольше (миллисекунд на 40 так) подержать выполнение потока (а что, пользователь не заметит же) — у меня нарушаются временные рамки реалтаймого процесса.
                        И как тут без вытеснения?


                        1. linuxover Автор
                          23.11.2017 16:29

                          > рекурсивный алгоритм на машину состояний

                          на AVR?


                          1. Dima_Sharihin
                            23.11.2017 16:38

                            На Cortex M4F.
                            Я пришел в embedded когда мода на атмеги уже прошла.


                            Нет, вы не подумайте, я не считаю классическое понятие RTOS с бесконечными циклами чем-то хорошим. Все проекты на RTOS любого вида (хоть вытесняющая, хоть кооперативная) у меня превращались в объекты-диспетчеры сообщений, ожидающие любое из возможных сообщений и, в зависимости от флагов, реагирующие на события.


                            Но считать, что вытеснение — зло, это, по меньшей мере, глупо. Как и считать RTOS каким-то монстром, это обычно сишная библиотека из десятка файлов. Просто не нужно думать, что на каждую отдельную лампочку в проекте нужно создавать собственный контекст.


                            1. linuxover Автор
                              23.11.2017 17:41

                              я не считаю что вытеснение зло, я считаю что использовать вытеснение в современных контроллерах в большинстве случаев — стрельба из пушки по воробъям.

                              ну а так же в «большом мире» вытеснение в хайлоадах не юзают. и не из за сказки о том что якобы это проблемы x86 архитектуры.
                              переключение процессов на ЛЮБОЙ архитектуре будет дорогим. а помимо дороговизны — проблема квантования никуда не денется. проблема IPC.
                              почему кооператив работает быстрее? не только потому что дорогое вытеснение, но и потому что IPC дорогой, организация взаимодействия частей программы между собой дорогая.


  1. smart_alex
    22.11.2017 09:35

    В проекте AMS для множества платформ (Mega, Due, 101, M0, ESP8266, ESP32 и т. д.) реализована кооперативная многозадачность, которая позволяет «одновременно» работать десяткам задач, таким как веб-сервер, опрос датчиков и управление актуаторами, приём и передача беспроводных команд nRF24, nooLite и т. п. и в это же время индивидуально управлять положением десятков серв и шаговых моторов — то есть полный фарш и любые прихоти в смысле «многозадачности» на Ардуино. И при этом в веб-интерфейсе стандартного АМС есть индикатор-график в реальном времени показывающий задержки выполнения этой «многозадачности».
    Вот примеры работы этой многозадачности в реальных проектах:
    https://hi-lab.ru/arduino-mega-server/ams-pro


    1. linuxover Автор
      22.11.2017 09:37

      эм. по ссылке реклама. а ссылка на код есть?

      PS: я вам добавил кармы — может поможет чтобы посты не требовалось модерировать


      1. smart_alex
        22.11.2017 09:41

        Конечно, код для любой платформы можно скачать в разделе загрузок:
        https://hi-lab.ru/arduino-mega-server/details/download


        1. linuxover Автор
          22.11.2017 09:43

          дайте ссылку (название файла) на конкретный модуль кооп многозадачности
          я в дороге поразглядываю


          1. smart_alex
            22.11.2017 10:00

            В АМС нет какого-то отдельного «модуля кооперативной многозадачности» или весь код АМС можно назвать таким «модулем». Более того, в АМС нет даже функции yield() или её подобия. То, что реализовано в АМС можно назвать «чистой кооперативной многозадачностью», когда процессы активируются поочерёдно и добровольно отдают управление.
            Просто так это конечно работать не будет и там есть много тонких моментов и «трюков» — код доступен и можно посмотреть что и как реализовано. Но в итоге всё работает как часы — зафиксированы аптаймы во много месяцев чёткой и беспроблемной работы.


            1. linuxover Автор
              22.11.2017 10:02

              > То, что реализовано в АМС можно назвать «чистой кооперативной многозадачностью», когда процессы активируются поочерёдно и добровольно отдают управление.

              то есть колбеки? ну я о них в первой части статьи писал :)


              1. smart_alex
                22.11.2017 10:07

                Из вашей статьи я узнал много новых и интересных терминов. Теперь буду знать как это называется по-научному. :)


  1. Px2
    22.11.2017 09:43

    > А давайте притащим мир большого программирования в Arduino!

    А давайте не будем этого делать. Давайте оставим хоть где-то «мир маленького программирования». Давайте не будем тащить в diy-сферу мавен, слоноподобные фреймворки, непрерывную интеграцию, разработку через тестирование и прочий буллшит.
    Понимаю, статья не об этом, просто начальная фраза зацепила.


    1. linuxover Автор
      22.11.2017 09:47

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


      1. lingvo
        22.11.2017 11:55

        Непрерывная интеграция в embedded натыкается на очень простые препятствия — часто ваш код можно полностью проверить только на реальном железе. А для этого надо лепить аппаратные тестовые стенды, на которых надо выключатели ручками нажимать, которые в непрерывную интеграцию включить не так легко.
        Непрерывную интеграцию можно сделать для аппаратно независимых алгоритмов управления, но не для всего контроллера.


        1. linuxover Автор
          22.11.2017 12:11

          эмулятор контроллера надо писать и давать возможность на скриптах эмулировать внешнюю аппаратуру. тогда CI получится.


          1. lingvo
            22.11.2017 12:48

            У вас может получиться, что эмуляция внешней аппаратуры может быть сложнее самой программы в МК. Так как для встроенных систем в большинстве случаев надо эмулировать физические процессы, которые они контролируют, а это не так просто, как кажется.


            1. linuxover Автор
              22.11.2017 12:59

              У вас может получиться, что эмуляция внешней аппаратуры может быть сложнее самой программы в МК

              а это нормальная ситуация кстати. мы же не сложность программы в МК обсуждаем, а скорость и сложность внесения в нее изменений. возможность получения быстрой обратной связи


              система тестирования ДОЛЖНА быть сложной.


              вот пример из реальной жизни:


              $ find lib|wc -l                       
              751
              $ find t|wc -l  
              2250

              на 751 файл кода библиотек 2250 файлов с их тестами. При этом тестов не совсем достаточно: иногда всплывают таки баги :(


              1. lingvo
                22.11.2017 13:40
                +1

                вот пример из реальной жизни: на 751 файл кода библиотек 2250 файлов с их тестами. При этом тестов не совсем достаточно: иногда всплывают таки баги :(

                Сорри, но рассмешили. Вот примеры из реальных систем тестирования встроенных устройств:
                На этапе разработки:
                image
                На этапе производства:
                image
                И все это только для того, чтобы полностью протестировать вон те маленькие коробочки на столе, в которых процессорной мощности может быть меньше чем в вашем ардуино. И все это тестовое оборудование стоит десятки тысяч евро, а его еще нужно собрать, настроить, проверить и написать под него ваши тестовые скрипты. Вы действительно уверены, что CI того стоит?


                1. linuxover Автор
                  22.11.2017 14:10

                  я говорю о CI в области ПО


                  1. Dima_Sharihin
                    22.11.2017 14:20

                    В ембеддеде 90% ПО часто завязано на конкретную аппаратную реализацию.


                    Даже с сугубо математическими задачами не все так просто: FPU на Intel x64 и оный в TI C2000 ведут себя по-разному в случае, если используются аппаратные ускорители функций (sqrt, sin, и так далее). Одна и та же программа на разных платформах даст разные результаты на одних входных данных.


                    1. linuxover Автор
                      22.11.2017 14:24

                      > В ембеддеде 90% ПО часто завязано на конкретную аппаратную реализацию.

                      а вот это не совсем верно.

                      вот возьмите скажем софтину работающую с ардуинным модулем WiFi.
                      вроде завязано на аппаратуру, так?
                      однако если бы был эмулятор, то разве эти команды AT весь набор и ошибок связанных с этим набором нельзя было бы прогнать на автоматических тестах?


                      1. Dima_Sharihin
                        22.11.2017 14:30

                        AT-команды — это проклятие, а не добро.
                        Как минимум потому, что часто документация не соответствует действительности.


                        вроде завязано на аппаратуру, так?

                        Нет, не завязано. На аппаратуру завязан обмен по UART и управление DMA. Это как раз такой случай, когда (в теории) очень легко покрыть код автотестами. Ну, до тех пор, пока вы не узнаете, что реализация printf (ой, он не выводит float! ой, он не поддерживает int64_t!) различается от компилятора к компилятору и даже внутри оного в зависимости от установленных флагов.


                        1. linuxover Автор
                          22.11.2017 14:33

                          Ну, до тех пор, пока вы не узнаете, что реализация printf

                          почему не узнать о том что реализация printf на стадии тестирования ПО а не тестирования железки?


                          1. Dima_Sharihin
                            22.11.2017 14:36

                            Если подкинете систему автотестов, что умеет сама заливать 25 файлов прошивок в камень, а потом удаленно прогонять автотесты — я буду признателен.


                            на стадии тестирования ПО

                            Так у нас тестируется на железке или на 86-ом декстопе?


                            1. linuxover Автор
                              22.11.2017 14:40

                              Если подкинете систему автотестов, что умеет сама заливать 25 файлов прошивок в камень, а потом удаленно прогонять автотесты — я буду признателен.

                              дискуссия началась с чего? с высказывания "давайте не будем в ардуино тащить из большого мира всякую мишуру вроде автотестирования"


                              а я сказал "очень жаль кстати что этого в мире ардуино пока нет"


                              так что если кто-то сделает систему для автотестирования, то я рядышком с Вами тоже признателен буду :)


                            1. linuxover Автор
                              22.11.2017 14:42

                              Так у нас тестируется на железке или на 86-ом декстопе?

                              например ARM'ы можно потестировать на 86-м десктопе. посидеть и наладить qemu. теоретически, но можно: эмулятор есть.


                              почему бы не появиться (я мечты же свои озвучиваю) эмулятору для AVR?


                              1. Dima_Sharihin
                                22.11.2017 14:49
                                +1

                                Меня больше интересуют STM8, TI C28x, TI C66x и прочие монстры (эмуляция последнего может оказаться медленнее прогона на реальном железе). Для некоторых (STM8, C28x) из них я не то, что эмулятора, я компилятора стороннего не видел. А у DSP очень легко нарваться на ошибку конвейера, когда результат операции еще не успел вычислиться, или не успела завершиться операция записи в ОЗУ.


                            1. grossws
                              24.11.2017 16:33

                              Некоторые, кстати, идут в сторону гибридных тестов: https://github.com/japaric/utest (библиотека для работы стандартных растовских unit test'ов в embedded), когда тесты запускаются и на реальной железке через jtag, и на qemu.


                        1. linuxover Автор
                          22.11.2017 14:34

                          На аппаратуру завязан обмен по UART и управление DMA.

                          эти вещи по идее можно сделать же на эмуляторе.


                          1. Dima_Sharihin
                            22.11.2017 14:39

                            Для 8051, MSP430 и ARMv6 я эмуляторы еще встречал, но


                            1. это далеко не весь набор платформ, на которых ведется разработка
                            2. обычно эмулируют ядро ЦП, оставляя в стороне детали реализации периферии


                        1. linuxover Автор
                          22.11.2017 14:38

                          На аппаратуру завязан обмен по UART и управление DMA

                          в случае WiFi как бы два уровня для тестов:


                          1. тесты на работу с модулем UART
                          2. тесты на работу с WiFi через этот самый UART


                          1. Dima_Sharihin
                            22.11.2017 14:43

                            тесты на работу с модулем UART

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


                            тесты на работу с WiFi через этот самый UART

                            А вот тут пожалуйста. И микроконтроллер для этого не нужен, если честно.


                            Но это очень легкий пример. Часто периферия выполняет задачи сложнее уровня "передать байт"-"принять байт".


                            1. linuxover Автор
                              22.11.2017 14:53

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

                              1. если тестовое окружение писать на скриптовом языке — то нет
                              2. написание программы, расчитанной в т.ч. под автоматические тесты часто требует ее реорганизации, это да. Особенно когда автоматические тесты только начинают внедряться. Позже, когда к ним привыкаешь — без них даже код о трех строках кажется ненадёжным

                              > Но это очень легкий пример. Часто периферия выполняет задачи сложнее уровня «передать байт»-«принять байт».

                              если мы поведение периферии задаем на скриптовом языке, почему нельзя сформулировать что-то более сложное?


                              1. borisxm
                                22.11.2017 15:23

                                если мы поведение периферии задаем на скриптовом языке, почему нельзя сформулировать что-то более сложное?

                                Не очень понятно, каким боком тут скриптовость языка, но в любом случае, пока вы будете формулировать и отлаживать поведение связки, скажем, таймер-АЦП-ПДП в STM32, другой уже отладит всё в железе и выпустит железку на рынок.

                                Современная периферия это уже не то, что мы привыкли видеть в старых добрых однокристалках типа 8051.


                                1. linuxover Автор
                                  22.11.2017 15:58
                                  -1

                                  > Не очень понятно, каким боком тут скриптовость языка

                                  на скриптах просто моделировать сложные вещи

                                  > пока вы будете формулировать и отлаживать поведение связки, скажем, таймер-АЦП-ПДП в STM32, другой уже отладит всё в железе и выпустит железку на рынок

                                  проблемы у другого начнутся на стадии когда у него сменится нанятый программист, а новый придет и скажет «тут все надо переписывать», «потому что надо».

                                  все это растет как раз из за отсутствия CI: из него растет невозможность вносить изменения в собственный код с какого-то уровня развития


                                1. linuxover Автор
                                  22.11.2017 16:01
                                  -1

                                  > пока вы будете формулировать и отлаживать поведение связки, скажем, таймер-АЦП-ПДП в STM32, другой уже отладит всё в железе и выпустит железку на рынок

                                  если на русский перевести, то будет известная сказка: «нам пилу точить некогда, нам пилить надо!»


                    1. linuxover Автор
                      22.11.2017 14:28

                      В ембеддеде 90% ПО часто завязано на конкретную аппаратную реализацию.

                      вот кстати то что в ембеддеде не занимаются внедрением CI по моему мнению есть причина того что ембеддед программистам так мало платят: они ж до сих пор не смогли свой труд нормально организовать, зачем им платить?


                      PS: не воспримите как оскорбление/наезд, я сам в embedded ~15 лет отработал, пока не ушел в "большой" мир :)


                  1. lingvo
                    22.11.2017 16:51

                    Ну вы же хотели применить CI к Ардуино, вроде?
                    А вот представьте себе ситуацию — вы решили немного усовершенствовать софт и добавить туда мигание каким-нибудь светодиодом, который будет подключен к определенной ножке вашего МК. Как вы это будете делать с точки зрения CI?
                    Напомню, что до реализации этого у вас нет ни скриптов, ни тестового стенда (как вы будете проверять мигает светодиод или нет, его период), ни даже самой платы, так как железячники прикрутят светодиод только в следующей ревизии платы.


                    1. linuxover Автор
                      22.11.2017 17:13

                      Ну вы же хотели применить CI к Ардуино, вроде?

                      к ардуине в частности и микроконтроллерам вообще


                      Думаю такая компания как скажем Atmel вполне потянула бы написать и поддерживать софт-эмуляторчик для того чтобы его использовать в CI. Причем эмулировать можно прямо все контроллеры AVR: благо там вариантов периферии по пальцам посчитать.


                      А вот представьте себе ситуацию — вы решили немного усовершенствовать софт и добавить туда мигание каким-нибудь светодиодом, который будет подключен к определенной ножке вашего МК. Как вы это будете делать с точки зрения CI?

                      Вы сразу разбираете 100%-й вариант покрытия тестами. к нему можно прийти. но обычно тесты как пишутся? берется некий блок программы, оформляется как библиотека и покрывается тестами.


                      то о чем Вы пишете — это уже интеграционное тестирование, оно как бы поверх юнит-тестирования бывает. Чаще всего бывает в зачаточной форме.


                      если говорить о Вашем светодиоде, то выделяем функцию им управляющую в библиотеку, на эмуляторе запускаем и смотрим состояния меняющиеся по создаваемым тестом условиям


                      1. lingvo
                        22.11.2017 17:57

                        то о чем Вы пишете — это уже интеграционное тестирование, оно как бы поверх юнит-тестирования бывает. Чаще всего бывает в зачаточной форме.

                        Это где это оно в зачаточной форме? В эмбеддерстве?


                        1. linuxover Автор
                          22.11.2017 18:02

                          в большом мире :)


                          в эмбеддерстве в общем и юниттестирование крайне слабо развито.


                1. linuxover Автор
                  22.11.2017 14:16

                  вот внесли мы изменения в ПО: что дальше? дальше CI прогоняет тесты, когда мы видим что тесты прошли, то только тогда понимаем, что внесенные изменения не приведут к регресси в пределах данной тестовой базы и только после этого доходит до аппаратуры



    1. linuxover Автор
      22.11.2017 09:56

      Давайте не будем тащить в diy-сферу мавен, слоноподобные фреймворки

      мне кажется с Arduino это УЖЕ произошло. Я лет 10 назад программировал AVR. Потом как-то переключился в "большой мир". Сейчас вернулся — есть задачка. Наткнулся на Arduino.


      какие впечатления? МОНСТР.


      • вот ЗАЧЕМ скажите изобретать псевдоязык и расширение файлов .ino? чтобы пользователю не писать include?
      • ЗАЧЕМ C++ повсюду? Чтобы стек получше грузить?
      • ЗАЧЕМ своя GUI-штукенция?

      проект avr-libc был и 10 лет назад. Почему в него не добавить функции с синусами-косинусами, а добавлять их на уровне проекта Arduino?


      1. Px2
        22.11.2017 10:08

        Мне тоже это непонятно. Но до того, что творится в мире Java, Ардуино еще далеко.


      1. Alex_ME
        22.11.2017 10:56

        Не понимаю, зачем свой псевдоязык (ладно, это просто C++ с библиотеками), свое расширение и своя GUI-штукенция (отвратная).


        Ардуино — это легкий старт. Не побоюсь признаться, сам начинал с этого.


        Заголовок спойлера

        Хотя, классе в 6-7 пытался что-то сделать на PIC, отлаживая в Proteus. А вот схему простого программатора не нашел, они сами зачастую требовали контроллер, который надо прошить. Ну либо искать тогда не умел.


      1. Amomum
        23.11.2017 14:47

        Справедливости ради, никакого псевдоязыка в ардуино нет. Просто некоторые инклуды вставляются перед компиляцией автоматически (и прототипы функций генерируются), а дальше обычный avr-gcc.

        Основная проблема Ардуины — убогая среда разработки, которая только подсвечивает синтаксис и загружает прошивку, но не умеет ни отладку делать, ни простейший рефакторинг. Я уже не говорю про нормальные многофайловые проекты.

        Вторая проблема — убогая библиотека, которая прячет слишком много и вызывает привыкание.

        Третья — убогие восьмибитные атмеги, с которых ардуина никак не может слезть.

        В С++ самом по себе тоже ничего плохого нет, если уметь им пользоваться, код получается чище/проще/строже, чем на чистом С. Отстрелить себе ногу при этом, разумеется, тоже проще.


        1. linuxover Автор
          23.11.2017 15:22

          Справедливости ради, никакого псевдоязыка в ардуино нет. Просто некоторые инклуды вставляются перед компиляцией автоматически (и прототипы функций генерируются), а дальше обычный avr-gcc.

          ну вот я написал #include <util/atomic.h> — оно мне пожаловалось что "вы компилируете не в стандарте C++".
          как в GUI это исправить — ХЗ. в Makefile-то я просто CFLAGS нужный добавил.


          Основная проблема Ардуины — убогая среда разработки

          тут палка о двух концах: с одной стороны они хотели дать "коробку"
          с другой они явно не тянули сами IDE делать.


          В С++ самом по себе тоже ничего плохого нет

          у меня просто есть проекты на чистом C, я думал туда вклиниться с либой. Поэтому либу хотел на чистом C и сделать.
          так-то против C++ я тож ничего не имею.


          Третья — убогие восьмибитные атмеги, с которых ардуина никак не может слезть.

          дык они ценой же определяются. атмеге всего-то питание подать надо.
          а ARM'ке одних конденсаторов обвязки килограмм надо повесить, ну и стоить плата будет куда дороже.


          1. Amomum
            23.11.2017 17:08

            ну вот я написал #include <util/atomic.h> — оно мне пожаловалось что «вы компилируете не в стандарте C++».
            как в GUI это исправить — ХЗ. в Makefile-то я просто CFLAGS нужный добавил.

            Ну только это не проблема языка, это убогая система сборки. Это да, никаких возражений.

            дык они ценой же определяются. атмеге всего-то питание подать надо.
            а ARM'ке одних конденсаторов обвязки килограмм надо повесить, ну и стоить плата будет куда дороже.

            Но при этом сам чип армовый может быть и дешевле атмеги. Насчет платы и обвязки говорить не могу, не знаю.


            1. linuxover Автор
              23.11.2017 17:43

              вот выпускали бы армовые чипы о 32-х ножках, с тремя таймерами на борту и с RAM/Flash/EEPROM, USART и прочим, при той же стоимости что и AVR, кто б вспомнил об AVR?


              1. Amomum
                23.11.2017 17:55

                Если исключить требование EEPROM (в stm его вроде бы нигде нет), то чипы вроде stm32f030k6, stm32f031k6 подходят по остальным требованиям и должны стоить в районе доллара за штуку. Есть в LQFP32.

                О цене аналогичных AVR судить не берусь.


                1. linuxover Автор
                  24.11.2017 10:42

                  прекрасные кристальчики, вот на них бы проект аля ардуино: 256К flash и 32К RAM на борту и корпус можно самому запаять.

                  вот бы ардуино на таком :)


                  1. Alexeyslav
                    24.11.2017 11:34

                    Так ведь есть же. Есть она, ардуина на STM32. давно уже пару платок заказал, лежат руки никак не дойдут.


                1. linuxover Автор
                  24.11.2017 10:45

                  интересно, аналог AVR-LIBC для них есть? то есть набор заголовков ориентированный именно на контроллеры?


                  1. Amomum
                    24.11.2017 11:35

                    Есть microlib и nanolib. Вообще, есть gcc для arm-none-eabi таргета, он вполне прилично компилирует. У проприетарных компиляторов получается немного лучше, но не на порядок.


                  1. Amomum
                    24.11.2017 11:41

                    Если линкеру сказать --specs=nosys.specs и --specs=nano.specs.


      1. Alexeyslav
        24.11.2017 11:23

        Ответ на все «ЗАЧЕМ» — чтобы понизить порог входа.
        Вам-то что, вы уже знакомы с техникой и не представляет проблемы настроить среду(блокнот с подсветкой?), настроить компиляцию под конкретный проект, взять нужный программатор и т.д. в случае ошибок/непоняток вы точно знаете что гуглить и куда смотреть.
        Но поставьте себя в роли новичка, который видит это всё в первый раз и норовит подключить светодиод напрямую к батарейке…
        Попробуйте например через неделю провести хирургическую операцию, даже при условии доступности всей обучающей литературы. Предполагается что АРДУИНА, со всей своей требухой, это временная стартовая площадка после которой переходят к полноценным компиляторам. Но некоторые всё-таки не переходят а так и остаются…


    1. F0iL
      22.11.2017 10:53

      Ну как сказать. Помнится, когда-то давно делали мы замерные установки и устройства контроля скважинных насосов для нефтегазодобычи, в качестве базы были в половине случаев x86-совместимые крохотные контроллеры, во вторых случаях вообще свои приборы на STM8/STM32, и вот знаете, в связи с постоянно меняющимися требованиям от заказчиков, частыми релизами, и необходимости тащить обратную совместимость с десятком изделий многолетней давности, непрерывной интеграции и разработки через тестирование там реально не хватало. Микрофреймворк свой в итоге сами написали (например там даже было что-то типа dependency injection, и возможность гонять тесты как на целевой платформе, так и на ПК), деплой на сотню устройств на нефтепромысле по радиоканалу после сборки и прохождения тестов тоже сделали, правда, в полуавтоматизированном виде (ограничения там были скорее административные, а не технические). Так что это не «буллшит», многие вещи из мира «большого ПО» в мире встраиваемых систем могут принести достаточно пользы, если знать как их использовать и использовать с умом.

      А у некоторых промышленных контроллеров так вообще есть многозадачность «из коробки», (например, SCADAPack с VxWorks на борту) — в некоторых задачах это довольно хорошо упрощало код.


      1. Px2
        22.11.2017 13:12

        Согласен. Бизнесу такой подход приносит очень большую пользу. Лично мне — нет. Сейчас профессия программиста незаметно превращается в профессию конфигуриста. Это не интересно.


        1. Alex_ME
          22.11.2017 13:24

          Точная фраза, возьму на вооружение.


  1. Px2
    22.11.2017 10:04

    Каждом свое. Это не мой вариант; такого «добра» мне на работе хватает. Я вроде как программист, и в трудовой написано, что программист, но фактически приходиться разбираться с кучей посторонних вещей, не имеющих непосредственное отношение к программированию. Боюсь, что через несколько лет с Ардуино сложится такая же ситуация: новичку нельзя будет просто открыть IDE и, написав пару строк, поморгать светодиодом. Нет, надо будет развернуть систему сборки, завести репозиторий на гитхабе, и, спустя неделю, перелопатив гугл вдоль и поперек, найти ответ (или намек, что бывает чаще), почему все это не работает.


    1. smart_alex
      22.11.2017 10:20

      У нас есть Ардуино 1.6.5 и никакие нововведения нам не страшны :) (шутка)


    1. linuxover Автор
      22.11.2017 11:01

      дык оно УЖЕ:


      я поставил Arduino 1.0.5, заработало пусть и вот с такими кривыми шрифтами



      чтобы исправить те шрифты надо залезть куда-то в Java.


      я поигрался — написал тот самый светодиод, сохранил проект, но шрифты… да
      посмотрел — в "большом" мире уже Arduino 1.5.
      заглянул в Debian/experimental — там 1.5 есть — установил.


      во первых GUI банально не запустился (еще не разбирался)
      а во вторых Arduino.mk теперь имеет другие переменные управления что-то в области BOARD_SUB поменяли, хз.


      то есть проект который я 10 минут назад компилял в 1.0.5 в 1.5 уже не собирается.


      и вот как скажите программисту Arduino не разбираться с кучей посторонних вещей?


      1. Px2
        22.11.2017 11:09

        Устанавливал недавно Arduino IDE под Linux Mint. Тоже GUI не запускался. Проблема была в том, что ставил его под рутом, а надо было под своим пользователем.


        1. linuxover Автор
          22.11.2017 11:12

          > Проблема была в том, что ставил его под рутом, а надо было под своим пользователем.

          установка ПО под пользователем — то еще зло. есть вещи которые я не могу делать. все внутри протестует.
          и одна из этих вещей — скачивание бинарников пользователем и запуск их :)
          поэтому сижу с 98-го года в Linux :)


          1. Ryppka
            22.11.2017 21:12

            Как-то странно получается: программы ставить только из-под рута? Почему?! И что значит «частота Linux 1000 Гц»? Частота чего? Если планировщика, то там сейчас нет фиксированной латентности планировщика и фиксированного кванта для процессов. При этом потоку в среднем будет дано около 100 мсек, если нет более приоритетного конкурента. Если я не путаю, я все-таки прикладной программист.


            1. linuxover Автор
              22.11.2017 23:02

              Как-то странно получается: программы ставить только из-под рута? Почему?!

              дело не в руте, дело в репозитариях


  1. Kot_dnz
    22.11.2017 23:02

    Вы просили: «Реальных тредов в том понимании как их понимают в „большом“ мире я не нашел. Буду благодарен, если кто-то подбросит мне ссылку на такой проект.»
    Вот: github.com/scmrtos/scmrtos


    1. linuxover Автор
      22.11.2017 23:27

      судя по документации шикарная штучка, спасибо, попробую ее


      1. Kot_dnz
        23.11.2017 10:14

        Там есть красивый документ на русском языке от создателей.
        Во всех смыслах прекрасная вещь