Цитата
Вопрос был не про SST, на нем задача решится, не сомневаюсь. Проблема только в том, что в C это будет callback hell, а на более другие языки пока нет желания переходить.
Если грамотно разложить на функции, то hell-a не будет. Наоборот в C есть вложенные функции, а в C++ их нет

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

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

Цитата
А каким образом вы делаете выход из PendSV обработчика на Cortex-M* когда надо после него вызвать обработку события? Где-то упоминали, я не понял.
На ассемблере, довольно хитрым, хотя может и не совсем

CODE
__attribute__((naked)) void ePendSV(){
asm volatile(
// push = ldmia; pop = stmdb
// "push {r0,r4-r11, lr} \n" // not necessary, callee saved
// create new stack frame for scheduler
"mov r2, %0 \n" // PSR
"movw r1, #:lower16:SST_sync_scheduler \n" // PC
"movt r1, #:upper16:SST_sync_scheduler \n"
"movw r0, #:lower16:async_scheduler_return \n" // LR
"movt r0, #:upper16:async_scheduler_return \n"
"push {r0, r1, r2} \n" // push {lr, pc, xPSR}
"sub sp, #5*4 \n" // push {r0,r1,r2,r3,r12} - undefined values
// return from exception to scheduler
"bx lr \n"
: :"i"(xPSR_RESET_VALUE));
}
// return to preemtded task through SVC
extern "C" __attribute__((naked)) void async_scheduler_return(){
asm volatile("svc #0");
}
__attribute__((naked)) void eSVCall(){
asm volatile(
//"mov r0, sp \n"
//"bl dump_stack \n"
// kill current stack frame
"add sp, #8*4 \n"
// "pop {r4-r11, lr} \n" // not necessary
// perform an EXC_RETURN (unstacking)
"bx lr \n"
);
}
Смысл в чем - при входе в PendSV создаем кадр стека, кидаем туда адрес функции-планировщика и адрес возврата из этой функции(туда попадем, когда планировщик отработает).
По адресу возврата лежит единственная инструкция - SVC, которая кинет нас в другой обработчик, там нам нужно убрать после себя - удалить наш кадр стека, и таким образом при выходе из этого обработчика мы попадем туда, откуда ранее попали в pendSV.
Этот механизм нужен только для асинхронного прерывания задачи, то есть при добавлении задачи в очередь из прерываний.
Чтобы вызвать планировщик из прерывания(а это делается автоматически после добавления задачи в очередь) достаточно установить PendSV, и по завершению обработки всех прерываний мы попадем в наш хитрый pendsv. Сам pendsv тоже может быть прерван в любой момент - это абсолютно безопасно.
Синхронное прерывание задачи - это просто вызов функции

На кортексе я добавил рантайм-сахара, чтобы не задумываться откуда вызывается планировщик - из прерывания или Thread-mode, тк для меня, что прерывание, что любой другой код - это одно и то же - прерывание точно такая же задача, как и все остальные, имеет свой приоритет, только этот приоритет всегда выше приоритетов обычных задач(не-прерываний).
Код
// invoke sync or async scheduler
void SST::choose_and_invoke_scheduler(){
uint32_t psr;
__asm volatile("mrs %0, IEPSR" :"=r"(psr));
if((psr&0x1FF) == 0){ // Thread mode
SST_sync_scheduler(); // тупо вызов функции
}else{ // Handler mode
PendSVset(); // установка флага pendSV
}
}
Без очередей сериализация доступа к аппаратуре может превратиться в мрак. Допустим есть шина I2C.
Ок, для сериализации нам нужен мютекс, назовем его i2c_mutex.
На этой шине висит 5 устройств. Для сериализации доступа к ним нам опять нужно 5 мютексов device_mutex_X.
Дальше одно из устройств - память, часть из которой просто буфер, а другая часть хранит настройки(тн. Settings), для сериализации нам опять нужно 2 мютекса flash_buffer_mutex и settings_mutex.
Получается, чтобы прочитать какой-нибудь setting нам нужно:
1. схватить settings_mutex
2. схватить device_mutex_memory
3. схватить i2c_mutex
4. что-то сделать
5. отпустить i2c_mutex
6. отпустить device_mutex_memory
7. если нужно - перейти к п2.
8. отпустить settings_mutex
Мало того, работа с i2c будет скорее всего выполняться(хотя бы частично) из прерываний, а для этого нам понадобится еще и семафор.
Это еще довольно простой случай. До dead-locka тут еще далеко(хотя не факт), но вот resource starvation при таком обилии локов будет однозначно, если шина нагружена. Ну и оверхед на мютексы, в реализации самих мютексов по любому будут очереди(гы

и блокировки.
А если вдруг захочется прочитать setting при работе с устройством X на i2c шине, что будет? Правильно, можно схватить deadlock(раз в неделю/месяц/год).
Можно, конечно, всю работу устройств на i2c покрыть одним жирным мютексом(а не это кучей мютексов), но это во первых ослабляет инкапсуляцию, а во вторых - это тормоза, шина будет простаивать во время обработки данных с флешки.
На очередях этих проблем нет, все будет вертеться в порядке приоритета и очереди, задачи будут нормально выполняться, если для них хватит пропускной способности шины.
Но нужно грамотно расставить приоритеты(и чем их меньше, тем лучше). Если не получается - значит либо что-то не так, либо действительно нужна ОС с динамической планировкой приоритетов задач.
К стати такая динамическая ОС тоже может быть чисто асинхронной (event-driven), но это будет уже не SST, a SCT - Super Complex Tasker

Но не на столько complex, как обычная ОС. Если потоки будут без блокировок(но с тайм-квантами и динамическими приоритетами), то такой планировщик проще, тк поток никогда не блокируется, он может быть только вытеснен другим потоком, так, как это происходит в SST.
ps. Изначально на Javascript асинхронный стиль был вынужденной мерой. WEB-приложения становились все сложнее и сложнее. Потом народ просек, что этот стиль довольно крут и придумал NodeJS, чтобы писать в том же стиле и серверы(да и просто прикладной софт), которые раньше делались на блокирующем PHP