Перейти к содержанию

    

brag

Свой
  • Публикаций

    1 047
  • Зарегистрирован

  • Посещение

Репутация

0 Обычный

Информация о brag

  • Звание
    Профессионал

Контакты

  • Сайт
    http://brag.org.ua
  • ICQ
    0

Информация

  • Город
    Kyiv, Ukraine

Посетители профиля

3 241 просмотр профиля
  • ig_z

  1. Собственно вопрос. В Constrain Manager правила для high-speed задать можно, но вот их проверку из Verify Design в Pads Layout почему-то убрали. Так должно быть или я просто не нашел нужную кнопку? По маршуту Pads Logic -> Pads Layout/Router все норм работает.
  2. В процессе написания очередного проекта столкнулся с частыми пропусками ошибок добавления в очередь (человеческий фактор однако). Кодvoid f(...,const delegate<void()>& cbk){ // ...    SST::postMessage(cbk); } Вместо: Кодbool f(...,const delegate<void()>& cbk){ //...    if(SST::postMessage(cbk)){       return true;    }else{ //...       return false;    } } Ну и последующей обработки этих ошибок. Поэтому изменил паттерн исполняемого обьекта, добавляемого в очередь. Кодtypedef VdelegateFIFO<void(Error)> TEventQueue; Движок очереди автоматически вызовет callback(Error::QueueFull) если нет места в очереди. Сам класс Error: CODEclass Error{ public: enum E{ NoError = 0, QueueFull = 1<<(sizeof(int)*8-1), last_ }; Error(int e): value_(static_cast<E>(e)){} bool isError()const{ return value_ < 0; } operator bool()const { return isError(); } private: E value_; }; От Error можно наследоваться, хоть и кривовато, но что поделаешь, C++ однако еще тот CODEclass DerivedError : public Error{ public: enum E{ first_ = last_-1, Error1, Error2, last_ }; using Error::Error; }; Ну и добавляется сообщение в очередь таким способом: Кодvoid f(...,const delegate<void(Error)>& cbk){ //...    SST::postMessage(cbk); } Ошибку подлавливаем уже в обработчиках Кодf([this](Error e){    if(e){ handle_error(e); return; } }); Если ошибку не обработать - компилятор об этом напомнит, это очень сильно помогает. Мало того, поскольку в SST нет блокировок - раскрутка стека происходит автоматически и бесплатно, в отличии от синхронных исключений C++. А обработка ошибок практически такая же, как catch в C++ - подлавливаем группу ошибок в одном месте (в данном примере это функция handle_error Да, не совсем удобно из за неприспособленности C++ к асинхронщине. Можно сделать по-удобнее, двумя вариантами: 1. либо написать препроцессор(например на питоне) - все зашибись, но это нестандарт. 2. либо выучить haskell (научиться на нем программировать), написать на нем обертку, потом перевести на шаблоны C++ (на шаблонах можно программировать только в чисто функциональном стиле, а чтобы это уметь - надо иметь опыт в haskell, там все куда проще и круче). - но синтаксис может получиться не совсем короткий, зато решение стандартное. Пока мне хватает ручного handle_error. Если будут проблемы - скорее всего буду делать по второму варианту.
  3. На мой взгляд, самая подходящая для embedded - это • heap_2 - permits memory to be freed, but not does coalescence adjacent free blocks. Тк в embedded нет такого изобилия обьектов разных длин, как в PC и нет такого сильного андетерминизма. Простым языком, если уж создался обьект размером N, значит он будет еще создаваться не один раз. А значит любая склейка блоков может усугубить фрагментацию. Мало того, при таком раскладе кучу можно сделать в виде какого-нибудь дерева, тогда добавление/удаление будет происходить очень быстро, практически constant time.
  4. На кортексе можно обойтись без запрета прерываний, у меня именно так планировщик был сделан в моей блокирующей ОС(которой я сейчас не пользуюсь, тк перешел на неблокирующий SST). Вот процедура добавления syscall-a из прерывания в очередь. Стиль еще старый сишный CODEint srqEnqueue(uint32_t svc, uint32_t r0, uint32_t r1){ // конкурентное резервирование места под syscall в очереди - без блокировок uint32_t t, newtail; do{ uint32_t h = dsrQueue.head; t = __ldrexh(&dsrQueue.tail); // по этому индексу t будем писать данные, если получиться newtail = (t+1)&(Srq::size-1); if(newtail==h)return Error::QUEUE_FULL; }while(__strexh(newtail, &dsrQueue.tail) ); // все, место удалось зарезервировать, теперь пишем туда данные Srq::qitem *qi = &dsrQueue.data[t]; qi->svc = svc; qi->r0 = r0; qi->r1 = r1; // выгрузка и выполнение syscall-ов из очереди происходит в PendSV // у него самый низкий приоритет изо всех прерываний, поэтому в нем тоже блокировок нет PendSVset(); return 0; } // Сама выгрузка и выполнение выглядит вот так. void Srq::DequeueRun(){ while(1){ uint32_ h = head; // эта head переменная volatile, это важно if(tail==h)break; svcTable[data[h].svc](reinterpret_cast<ExceptionStackFrame*>(&data[h])); // собственно виполнение head = (h+1)&(size-1); // make item available for writing } } // Блокировки тут не нужны, тк запись head происходит только здесь и она не конкурентная Этот код проверен анализатором и много раз прокручен разными мозгами, можно с у веренностью сказать, что гонок в нем нет, но при соблюдении условий: 1. PendSV не должен прерывать другие прерывания, которые пишут в очередь. 2. Планировщик и вся остальная работа ОС(мютексы итд) тоже должна выполняться только из PendSV(или из такого же уровня приоритета), то есть быть не конкурентной Для SST такая очередь не годится, там чтение и запись - конкурентные. Там нужна гораздо более сложная, шаблонная, lock-free очередь, ну или на локах, но локи должны покрывать только работу с метаданными(2 указателя), иначе будут тормоза - задержка обработки особо критических прерываний. Такая очередь на локах тоже довольно не тривиальна и требует тщательной отладки. Но делается это один раз. С event-driven мы уже научились работать , теперь нужно двигаться дальше, тк асинхронщина - это путь к функциональному стилю. Чисто ФП-язык(Haskell, Erlang,...) на мк уровня LPC175x/STM32F10x использовать для практических RT-задач вряд ли получится, МК слишком слабые для этого, поэтому придется пользоваться тем, что есть. Это на данный момент C++14 и Rust. Введение в ФП-асинхронщину тут https://www.fpcomplete.com/blog/2012/06/asy...tinuation-monad Но для начала на Haskell пописать все таки придется, без этого никак. Вот по этому туториалу учился(и учусь) я http://learnyouahaskell.com/introduction#about-this-tutorial А тут, интересные, на мой взгляд, практические рекомендации, как применять ФП на C++(14): http://cpptruths.blogspot.com/2014/03/fun-...yle-part-1.html http://cpptruths.blogspot.com/2014/05/fun-...yle-part-2.html http://cpptruths.blogspot.com/2014/08/fun-...yle-part-3.html
  5. С мютексом можно работать, но если начнутся вложенные мютексы - пора переходить на очереди... К стати как с семафором из прерывания работа выполняется? Через очередь или через блокировку всех прерываний? ЦитатаПочитал programming manual, стало ясно, но сложно, хочется проще. Проще не будет, кортекс не поддерживает рекурсивные прерывания, поддерживает только вложенные. А для SST нужна рекурсия: задача->планировщик->задача->планировщик->задача итд.
  6. ЦитатаВ С их нет. Они есть в gcc, но это его собственное расширение (для С) Да, действительно, с C их нет. как-то довелось их применять и компилятор схавал, поэтому и подумал, что они есть в C. Тогда энкапсулировать можно на уровне файлов. i2c движок не сложный, использовать указатели на функции и typedef, на C норм код получиться.
  7. ЦитатаВопрос был не про 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
  8. ЦитатаДля этого обычно используют примитивы захвата с timeout'ом A если надо таймаут не на захват одного ресурса, а на несколько? Не спорю, на потоках тоже можно реализовать, на на SST это проще. Та же задача с атомным реактором - считываем группу датчиков, если не успели за 5 сек - срабатывает высокоприоритетное событие, которое задвинет стержни и потушит реакцию. С очередями аналогично - вдруг где-то что-то в очередь не влезло - срабатывает событие, которое гарантировано(при любых раскладах) задвинет стержни. Для таких ответственных задач нужна статическая память(то есть гарантия ее наличия еще на этапе компиляции) - никаких стеков и куч и event-ы - никаких сложных планировщиков. И проверенные временем высокоурвневые обьекты - без ручной работы с памятью, иначе будет высокая вероятность схватить segfault - от человеческого фактора никто не застрахован. ЦитатаВы опять путаете причину со следствием. Динамические приоритеты появились не потому что планировщик в ОС такой сложный, а именно такой сложный планировщик появился потому, что возникла потребность в динамических приоритетах. Кстати, очередь с приоритетами не аналогична приоритетам задач в планировщике ОС. Если в очередь поставить задачу с меньшим приоритетом, а потом постоянно добавлять задачи с большим, то задача с меньшим приоритетом никогда не исполнится. А ОС все же будет исполнять задачу с меньшим приоритетом. Ну да, в SST аналогично - если будет флуд высокоприоритетных событий - низкоприоритетные никогда не запустятся. Если такое поведение не подходит - тогда нужна ОС со сложным планировщиком. Обычно в таких ОС о приоритетах никто уже не задумывается - приоритеты динамически расставляет сама ОС ЦитатаДля STM32F407 уже можно и обычную ОС (типа scmRTOS или freertos), памяти у него хватит, а заниматься переучиванием себя под SST выйдет дороже. Использование SST в этом случае оправданно если вы эксперт в ней, и у вас уже есть большие наработки, и event drive стиль для вас привычен. Но на этом форуме похоже вы один такой Да, нужно иметь опыт в even-driven. Много памяти - это очень удобно для SST - можно смело использовать динамическую память (кучи или пулы блоков фиксированных длин), там, где это уместно - тогда начинаешь чувствовать себя, как в Javascript-е ЦитатаЯ где-то здесь недавно задавал вопрос. Нужно было сделать запрос и подождать готовности ответа, в частности функция i2c_read. Ничего лучше двух мьютексов не нашлось. На захвате первого ждем готовности интерфейса, на втором готовности ответа. Только второй примитив не мьютекс, а семафор. Можно как-то лучше? На очередях получается, еще сложнее. А зачем ждать готовности интерфейса? Добавили запрос в очередь. Как только дойдет до него дело(а раз дошло, значит шина уже свободная) - он выполнится и по завершению выдаст соответствующий event. В любом контроллере есть для этого соответствующие прерывания. Та даже если и нет, а работаем с шиной вручную дрыганием ног - все точно так же - достали из очереди запрос, выполнили, выкинули соответствующий event. Почему просто не выполнить это без всяких очередей - потому что с SST можно выполнять работу с дрыганием ног в своем приоритете, чтобы и другим не мешать, и чтобы глюков(задержек клока итп) на шине не было, из за того, что кто-то прервал нашу работу. Такие задачи работы с аппаратурой на SST и очереди ложатся, как влитые.
  9. ЦитатаОтсюда следует, что ниша чистого SST - это мелкие МК (но не слишком мелкие, т.к. на совсем маленьких МК не может быть очень сложных программ, и обычный Super Loop позволит сделать то же самое, что и SST, но без какого либо управляющего кода/tasker'а вообще) Как по мне, SST подходит под контроллеры от уровня Atmega8 до STM32F407, а так же практически на все DSP. На что-то более крупное нужно что-то стандартное(например linux) Реализация SST настолько проста, что даже если у вас 1 приоритет(как в NodeJS/Javascript), то выкидывать его нет смысла - всегда работаешь с одним инструментом. ЦитатаЕще вижу, что можно использовать комбинированный подход - SST поверх обычных thread'ов. Эта комбинация мне кажется супер удобной. То, что просто ложится на SST будет запущено на нем, а все остальное - обычная программа. Например, если надо в процессе какой то сложной обработки (параллельной и запутанной) выдать результат в виде нескольких байтов в SPI железку, то запуск цепочки вывода в SST части будет очень простым и логичным действием. Сама цепочка вывода тоже будет достаточно простой и логичной. Если же это делать порождением отдельного thread'а, то с одной стороны это будет явный overkill, а с другой это может оказаться менее прозрачным (в коде), чем запуск SST цепочки. Наверное не просто будет подружить треды с SST, хотя все возможно. Действительно, есть целый класс задач, где без асинхронного приоритетного планировщика жизнь совсем плоха, я уже их приводил в этой теме. Там, где на SST нужно пару простейших асинхронных событий и асинхронный таймер, на тредах нужно 4-5 потоков с запутанной связью между ними. А может быть наоборот - в тредах будет штук 5 простых блокирующих вызовов(типа printf), а в SST получится настоящий callback-hell. Ну и желательно не смешивать блокирующий код с неблокирующим, иначе будет задержка(замирание) обработки событий всех задач данного и более низкого приоритета. Я думаю, если использовать смесь блокинг(тот же обычный printf) и нон-блокинг кода, то нужно это делать в классических тредах, возможно через active object. Но ни в коем случаи не использовать мютексы, а использовать высокоуровневые обьекты, а мютексы пусть используют разработчики этих обьектов внутри их. Конечно, на первый взгляд в SST неудобно работать, если нужно последовательно выполнять несколько асинхронных действий Кодvoid Client::do_socket_connect(){     settings->readSetting(&settings_union.host, [this](){         settings->readSetting(&settings_union.port, [this](){             socket->connect(settings_union.host.data, settings_union.port.data);         });     }); } Нужно заботится о том, где и как хранить обьекты host и port, немного некрасивый вид(callback hell назревает). В блокинг модели это бы выглядело так: Кодvoid Client::do_socket_connect(){     Settings::Item<Settings::ID_Host> host;             settings->readSetting(&settings_union.host);     Settings::Item<Settings::ID_Port> port;     settings->readSetting(&port);     socket->connect(host.data, port.data); } Но если копнуть глубже... В первом варианте мы однозначно знаем сколько RAM потребуется выделить, а это большой + для embedded. В случаи с хранением этoго дела на стеке ситуация гораздо хуже (но удобнее). Потом если это не embedded или есть памяти по-больше - можно выделять память в куче(или из пула фиксированных блоков - очень быстро и constant time), а удалится это все автоматически, через smart pointer - удобства не меньше, чем со стеком и затраты производительности копеечные. Но это не так важно. Важно другое - что, если нам на этот блок действий надо нацепить таймаут? Скажем, все эти 4 действия должны выполниться за 5 секунд(именно так и есть в оригинальной задаче, из которой я этот код выдрал). Если не успело - отменить и выдать ошибку. На SST это сделать очень просто - в конце(или в начале, это вовсе не важно) просто запустить таймер Кодvoid Client::do_socket_connect(){ // ...    timer.start([this](){        socket->abort();        settings->abort();        printf("do_socket_connect timeout\n");    }); } Реализовать такое на потоках - это довольно жутко, можете попробовать Если abort в SST - это по сути просто замена callbacka(или нескольких) на пустышку, то как абортануть поток, если он завис в ожидании мютекса? Ведь settings->read - довольно тяжелая и сложная задача - поиск в флеш-памяти файла, поиск в файле строчки с данным settingom, куча проверок итп. Аналогично и с socket->connect. Да и потоков ради этого всего понадобится как минимум 2, если не больше.
  10. ЦитатаВот именно - заставляет. Это механизм принудительного выкручивания рук у програмиста. Ну да, как и любой другой современный механизм. На практике(по своему опыту) кривой код на SST слетает сразу при включении прибора, а кривой код на мютексах может неделями работать. Не всегда разумеется так, но довольно часто и ошибку выявить проще, Цитатаtransfer B->A первым выполняется sync(A). Так что dedlock'а не будет sm.gif С чего бы это? Первым идет лок from, затем to. transfer A->B : from=A, to=B transfer B->A : from=B, to=A И все, deadlock (раз в год). ЦитатаК сожалению сам SST требует этот самый event driven способ построения программы, и принципиально не может использовать обычную flow модель. Для того, что бы безболезненно использовать очереди, это слишком высокая цена rolleyes.gif Не спорю, цена высокая и платить нужно временем привыкания к event-driven-модели, ну и несовместимостью с flow-model кодом. КодPS. Кстати, ваша реализация SST умеет запускать приоритетные задачи поверх менее приоритетных по аппаратному прерыванию? Если да, то как это реализованно? Конечно. Без этого это уже не SST, а обычный одно-приоритетный код (типа NodeJS). Реализовано по разному на разных платформах. На Cortex через связку PendSV/SVC (где-то в этой ветке приводил ассемблерный код). На других камнях через вложенные прерывания - при входе в обработчик разрешаются другие прерывания, а при выходе - запускается планировщик, который достает из очереди самую приоритетную задачу, приоритет которой выше текущей и выполняет ее (если такая имеется). В принципе так же, как в оригинальной статье по SST http://www.embedded.com/design/prototyping...r-Simple-Tasker Цитатабудет работать не правильно. Нельзя сделать атомарный тест а потом атомарный инкремент и считать, что это все будет по прежнему атомарным Этот код я специально оставил без защиты, чтобы relacy хоть там нашел ошибку, но он и на это оказался не способен(или я не сумел его научить). Я акцентировал внимание на двух переменных. Этот код(if в оригинале был защищен) из реальной практической задачи, где я спустя месяца 2 поймал гонку. А хуже того, если переменных/ресурсов не 2, а больше. Работать с таким вообще невозможно(годы уйдут на отладку) выход один - покрывать все одним локом, но это тормоз жуткий. ЦитатаНи в коем случае. В данном случае нужно взять всю работу с ff и nexec в критическую секцию (это самое простое решение). Ну так критическая секция(запрет прерываний) - это и есть глобальнейший мютекс. А что, если эти ff и exec не просто переменные, а сложные обьекты, хуже - работа с аппаратурой? Сами они защищены по отдельности, но не защищены в совокупности. ЦитатаРазумеется, если програмист не умеет работать с разделяемыми ресурсами с помощью низкоуровневых примитивов синхронизации, ему лучше поручить это кому нибудь другому (очереди например). Или вообще завязать с програмированием и идти продавать пирожки smile3046.gif А что если программист не умеет программировать в машинных кодах, вернее умеет, но это у него занимает очень много времени? Переходить на высокоуровневый язык или идти продавать пирожки? Почти любая сложная программа на мютексах имеет гонки, при чем такие запутанные, что ручным анализом кода их выявить невозможно. Да и инструментов не так много(можно сказать их нет), и сама симуляция сложной программы(перебор всевозможных вариантов переключения контекста) может занимать месяц времени на 4-ядерном 3ггц Core-i7 процессоре. Да и то это еще надо уметь описать assert-ы для симулятора, это тоже очень не просто. Поэтому и используют сейчас очереди(каналы, пайпы итд, называйте как хотите) взамен мютексов. И код стараются делать простым, без запутанных связей. Да и работа с очередями тоже делается не как вздумается, а по определенным шаблонам проектирования. ЦитатаНо вот беда, в очереди SPI уже стоит 500 команд на зажигание лампочек, и пока дойдет очередь до мотора, опускать уже будет нечего - реактор расплавится crying.gif Для этого есть очередь с приоритетами - сообщения в ней сортируются сначала в порядке приоритета, затем в порядке поступления. Реализация не сильно сложная, делается один раз и работает быстро. Именно SST решает эту проблему без изощренных алгоритмов планировки задач ОС. В то время как сериализация на мютексах рано или поздно приведет к взрыву реактора - 500 лампочек начнут хватать мютекс хаотично, а времени для движка не останется, и наследование приоритетов не поможет. Имхо, в нынешнее время единственное применение мютексам - защита данных внутри самой очереди, и то на системах, где нет другого механизма
  11. При синхронизации на мютексах не столько страшен deadlock, сколько гонки. Наглядный пример я уже приводил https://github.com/dvyukov/relacy/issues/3 - хоть и ff и nexec защищены (тк они atomic), да и по ходу исполнения на них по отдельности гонок нет, но есть так называемая гонка высшего порядка, то есть 2 ресурса защищены по отдельности, но не защищены в совокупности и результат работы программы будет зависеть не от программиста, а от варианта хода переключения контекста. Чтобы гарантировано(и то не факт) избавится от таких гонок нужно все разделяемые ресурсы покрывать одним глобальным мютексом, но такая система будет крайне тормознутая и никому не нужная. При общении обьектов через очереди сообщений таких проблем нет. При синхронизации на мютексах, кроме гонок, есть еще одна важная проблема - resource starvation. Это когда много потоков интенсивно используют один и тот же ресурс. Но кому-то из потоков удается его получать, а кто-то так и не дожидается его освобождения. По аналогии с лифтом - лифт ездит между первым и 4м этажом, тк там очень много народу, а один человек на 9м этаже так и не смог дождаться лифта. Это вторая проблема после гонок. С такой проблемой я тоже очень часто стыкался. Чаще всего это когда на шине SPI висит одно медленное устройство(типа термометра), а второе быстрое(скажем АЦП), занимающее 95% SPI. Потоки, работающие с этим быстрым устройством хаотично делятся мютексом шины, а потоку, работающему с термометром мютекс так и не достается. Хуже, если на шине не 2 устройства, а штук 5 разных. При работе с SPI через очереди такой проблемы нет - все получают доступ к шине строго в порядке очереди.
  12. ЦитатаЯ понимаю, что эхо с изменением тембра можно сделать без такой изощренной схемы Вот это главное - SST заставляет делать вещи просто там, где нужны были куча потоков, в SST это один поток и зачастую даже сам SST не нужен SST нужен для приоритизации задач, например когда есть риал-тайм звук и что-то тяжелое, типа декодировки jpeg. Надо чтобы jpeg не приводил к замиранию звука, тут и помогают приоритеты SST. Для обычных прикладных задач обычно хватает 1-3 приоритета. ЦитатаКстати, в данном примере dedlock'а не будет sm.gif Будет: Кодtransfer A->B:   sync(A);  // from   // context switch (on time quantum for example) transfer B->A:   sync(B); // from   // context switch transfer A->B: // continue   sync(B); // to.  DeadLock !   // context switch transfer B->A: // continue   sync(A); // to. Strong DeadLock finally Редко возникающий, тяжело находимый deadlock. Ну и контр-пример на SST, вернее без SST, один поток, async IO: Код   void transfer(Account from, Account to, double amount){         from.withdraw(amount);         to.deposit(amount);     } Поскольку все Account-ы равноприоритетные, никакой синхронизации не требуется вовсе. Синхронизация на очередях избавляет от очень капризных и тяжело находимых багов - гонок при переключении контекста/прерываниях. Я делал свой планировщик для перебора всевозможных комбинаций переключения контекста, типа Relacy http://www.1024cores.net/home/relacy-race-detector . Последний не использую потому что он не смог найти даже такую простую гонку: https://github.com/dvyukov/relacy/issues/3 Так вот этим своим планировщиком я понаходил немало гонок в чужом(да и своем тоже) продакшн-коде, при чем эти гонки ни разу не вылезли за несколько лет использования этого кода. Это я к тому, что синхронизация на мютексах(и семафорах) требует очень больших временных и мозговых затрат. При работе с очередями все гораздо проще.
  13. ЦитатаЭто абсолютно не такю Чтение этого регистра происходит в четко определенные моменты времени, а это значит, что аппаратный поток (который меняет значение регистра) жестко засинхронизирован с основным потоком. Основной поток может увидеть разные значения регистра в разных точках, но прочтенное значение не может самопроизвольно измениться между чтениями. То же самое происходит и при работе с очередями ЦитатаОчень хочу посмотреть на 'мьютексы на поллинге' rolleyes.gif Та что там смотреть, классика по сути Кодstruct Mutex{   void lock(){     while(true){ // poll variable 'm'       disable_irq();       if(!m){         m = true;         enable_irq();         return;       }       enable_irq();       // we can do something else here, run FSM for example     }   }   void unlock(){ m = false; }      Mutex(): m(false){} private:     bool m; } Такой мютекс можно применять и без RTOS(а можно и с), например в классической FSM, я когда-то так делал, пока не пришел к SST-модели. Что такое потоки в однопроцессорной системе? Это те же самые FSM с асинхронными прерываниями. Даже без асинхронных прерываний на FSM можно получить эффект deadlock-a, лок будет зависеть не от кода, а от стечения обстоятельств, точно так же, как это происходит в OS-thread-ах. В тредах лок зависит от хода переключения контекста(тоже по сути от событий-прерываний), в примере от XVR - от состояния очередей, в обычной FSM - от ее состояния и внешних или внутренних событий(прерываний, таймеров, регистров итп). Я привык оперировать не синтетическими задачами, а реальными. В моем понимании классический deadlock это что-то типа этого: Кодclass Account {   double balance;   void withdraw(double amount){      balance -= amount;   }   void deposit(double amount){      balance += amount;   }    void transfer(Account from, Account to, double amount){         sync(from);         sync(to);            from.withdraw(amount);            to.deposit(amount);         release(to);         release(from);     } } Задача решена как-бы правильно и бага или не/недо-проверки ошибок здесь как бы нет. Но.. Реальная ситуация: клиент А отправляет клиенту B 100 рублей, в этот же момент клиент В отправляет клиенту А 50 рублей - и все, это может привести к deadlock-у. Я такие ситуации не раз ловил, очень трудно найти и исправить, особенно, если код гораздо сложнее. Хотелось бы увидеть реальный(а не синтетический) пример на неблокирующих очередях, который привел бы к подобной ситуации.
  14. ЦитатаЭто почему не может? А работа по поллингу вместо прерываний? Или если у процессора выключить прерывания, то он сразу превратится в кирпич? если есть хоть один аппаратный регистр, значение которого меняет не проц(или не только проц), значит это уже 2 потока, не важно поллинг это или прерывание. С мютексами тоже можно работать по поллингу и тоже можно получить deadlock Ну или кривая FSM(тоже по сути polling) тоже может перестать обрабатывать сообщения - при стечении определенных обстоятельств. ЦитатаБолее того, если SST программа будет давать исключение на любое переполнение очереди, то это еще хуже, чем deadlock - она вылетит по исключению Никуда она не вылетит, исключение можно подловить и восстановиться или обработать ошибку сразу без использования исключения. При deadlock-e на мютексах/блокировках такое сделать не получится, разве что ради этого есть поддержка в ОС. Редко когда переполнение очереди - это нормальная работа, обычно это исключительные ситуации(по сути баги), которые обрабатываются отдельно и записываются в лог для дальнейшей правки. Да и юнит-тестирование быстро такие места(переполнение очередей) выявляет, в отличии от блокирующей модели, где просто так тест написать очень трудно, по сути надо писать свой планировщик, который будет перебирать всевозможные варианты переключения контекста, аналогичный планировщик я делал для быстрого выявления гонок.
  15. ЦитатаОригинальная программа даст deadlock. Т.е. сам по себе SST не в состоянии его предотвратить. Оригинальная программа на SST даст исключение. ЦитатаЧто и означает, что заявление 'в SST никаких dedlockов быть не может' несколько не соотвествует действительности, не так ли? Смотря что подразумевать под deadlock-ом. Цитатаdeadlock должен быть спорадическим, иначе это просто обычная бага в программе. Добится спорадического (и невоспроизводимого) поведения в одном потоке невозможно. Смотря что подразумевать под потоком Если параллельное исполнение кода (тру поток) - тогда можно добиться и в одном потоке. Если поток классической ОС (часто его называют Thread-ом) - тогда тоже можно и в одном потоке получить такой трудновоспроизводимый lock. Его невозможно получить только в тру-одном потоке, то есть там, где кроме инструкций процессора нет больше ничего - никаких прерываний, никаких других внешних устройств, то есть там, где все доступное адресное пространство может быть модифицировано только кодом и никем больше. Но такая система является бесполезной на практике, она кроме выполнения бесполезных вычислений больше ничего не может. Любое прерывание, любую модифицируемую извне память(например IO-регистры) уже можно назвать отдельным потоком. Каждый IO-регистр - это уже отдельный маленький поток, эти регистры могут изменятся параллельно, независимо друг от друга и от основного кода программы.