Jump to content

    

brag

Свой
  • Content Count

    1044
  • Joined

  • Last visited

Community Reputation

0 Обычный

About brag

  • Rank
    Профессионал

Контакты

  • Сайт
    Array
  • ICQ
    Array

Информация

  • Город
    Array

Recent Profile Visitors

3498 profile views
  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: class 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++ однако еще тот :( class 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 из прерывания в очередь. Стиль еще старый сишный :) int 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. С мютексом можно работать, но если начнутся вложенные мютексы - пора переходить на очереди... К стати как с семафором из прерывания работа выполняется? Через очередь или через блокировку всех прерываний? Проще не будет, кортекс не поддерживает рекурсивные прерывания, поддерживает только вложенные. А для SST нужна рекурсия: задача->планировщик->задача->планировщик->задача итд.
  6. Да, действительно, с C их нет. как-то довелось их применять и компилятор схавал, поэтому и подумал, что они есть в C. Тогда энкапсулировать можно на уровне файлов. i2c движок не сложный, использовать указатели на функции и typedef, на C норм код получиться.
  7. Если грамотно разложить на функции, то hell-a не будет. Наоборот в C есть вложенные функции, а в C++ их нет :( Правда есть лямбды, недавно появились относительно. Сахарку можно добавить в С с помощью макросов, если нужно. А можно(я так иногда делаю) прогонять исходник через скрипт на Python-е(или что больше по душе) - расширять сишный синтаксис своими фантазиями ;) Иногда мучаюсь с шаблонами, макросами неделю, потом плюю на все стандарты, беру питон и решаю задачу за 10 минут. Но согласен, без мощного языка работать с event-ами напряжно и лень пробирает :) На ассемблере, довольно хитрым, хотя может и не совсем :) __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. A если надо таймаут не на захват одного ресурса, а на несколько? Не спорю, на потоках тоже можно реализовать, на на SST это проще. Та же задача с атомным реактором - считываем группу датчиков, если не успели за 5 сек - срабатывает высокоприоритетное событие, которое задвинет стержни и потушит реакцию. С очередями аналогично - вдруг где-то что-то в очередь не влезло - срабатывает событие, которое гарантировано(при любых раскладах) задвинет стержни. Для таких ответственных задач нужна статическая память(то есть гарантия ее наличия еще на этапе компиляции) - никаких стеков и куч и event-ы - никаких сложных планировщиков. И проверенные временем высокоурвневые обьекты - без ручной работы с памятью, иначе будет высокая вероятность схватить segfault - от человеческого фактора никто не застрахован. Ну да, в SST аналогично - если будет флуд высокоприоритетных событий - низкоприоритетные никогда не запустятся. Если такое поведение не подходит - тогда нужна ОС со сложным планировщиком. Обычно в таких ОС о приоритетах никто уже не задумывается - приоритеты динамически расставляет сама ОС Да, нужно иметь опыт в even-driven. Много памяти - это очень удобно для SST - можно смело использовать динамическую память (кучи или пулы блоков фиксированных длин), там, где это уместно - тогда начинаешь чувствовать себя, как в Javascript-е :) А зачем ждать готовности интерфейса? Добавили запрос в очередь. Как только дойдет до него дело(а раз дошло, значит шина уже свободная) - он выполнится и по завершению выдаст соответствующий event. В любом контроллере есть для этого соответствующие прерывания. Та даже если и нет, а работаем с шиной вручную дрыганием ног - все точно так же - достали из очереди запрос, выполнили, выкинули соответствующий event. Почему просто не выполнить это без всяких очередей - потому что с SST можно выполнять работу с дрыганием ног в своем приоритете, чтобы и другим не мешать, и чтобы глюков(задержек клока итп) на шине не было, из за того, что кто-то прервал нашу работу. Такие задачи работы с аппаратурой на SST и очереди ложатся, как влитые.
  9. Как по мне, SST подходит под контроллеры от уровня Atmega8 до STM32F407, а так же практически на все DSP. На что-то более крупное нужно что-то стандартное(например linux) Реализация SST настолько проста, что даже если у вас 1 приоритет(как в NodeJS/Javascript), то выкидывать его нет смысла - всегда работаешь с одним инструментом. Наверное не просто будет подружить треды с 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 слетает сразу при включении прибора, а кривой код на мютексах может неделями работать. Не всегда разумеется так, но довольно часто и ошибку выявить проще, С чего бы это? Первым идет лок from, затем to. transfer A->B : from=A, to=B transfer B->A : from=B, to=A И все, deadlock (раз в год). Не спорю, цена высокая и платить нужно временем привыкания к 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 и exec не просто переменные, а сложные обьекты, хуже - работа с аппаратурой? Сами они защищены по отдельности, но не защищены в совокупности. А что если программист не умеет программировать в машинных кодах, вернее умеет, но это у него занимает очень много времени? Переходить на высокоуровневый язык или идти продавать пирожки? Почти любая сложная программа на мютексах имеет гонки, при чем такие запутанные, что ручным анализом кода их выявить невозможно. Да и инструментов не так много(можно сказать их нет), и сама симуляция сложной программы(перебор всевозможных вариантов переключения контекста) может занимать месяц времени на 4-ядерном 3ггц Core-i7 процессоре. Да и то это еще надо уметь описать assert-ы для симулятора, это тоже очень не просто. Поэтому и используют сейчас очереди(каналы, пайпы итд, называйте как хотите) взамен мютексов. И код стараются делать простым, без запутанных связей. Да и работа с очередями тоже делается не как вздумается, а по определенным шаблонам проектирования. Для этого есть очередь с приоритетами - сообщения в ней сортируются сначала в порядке приоритета, затем в порядке поступления. Реализация не сильно сложная, делается один раз и работает быстро. Именно 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 приоритета. Будет: 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. То же самое происходит и при работе с очередями :) Та что там смотреть, классика по сути 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) тоже может перестать обрабатывать сообщения - при стечении определенных обстоятельств. Никуда она не вылетит, исключение можно подловить и восстановиться или обработать ошибку сразу без использования исключения. При deadlock-e на мютексах/блокировках такое сделать не получится, разве что ради этого есть поддержка в ОС. Редко когда переполнение очереди - это нормальная работа, обычно это исключительные ситуации(по сути баги), которые обрабатываются отдельно и записываются в лог для дальнейшей правки. Да и юнит-тестирование быстро такие места(переполнение очередей) выявляет, в отличии от блокирующей модели, где просто так тест написать очень трудно, по сути надо писать свой планировщик, который будет перебирать всевозможные варианты переключения контекста, аналогичный планировщик я делал для быстрого выявления гонок.
  15. Оригинальная программа на SST даст исключение. Смотря что подразумевать под deadlock-ом. Смотря что подразумевать под потоком :) Если параллельное исполнение кода (тру поток) - тогда можно добиться и в одном потоке. Если поток классической ОС (часто его называют Thread-ом) - тогда тоже можно и в одном потоке получить такой трудновоспроизводимый lock. Его невозможно получить только в тру-одном потоке, то есть там, где кроме инструкций процессора нет больше ничего - никаких прерываний, никаких других внешних устройств, то есть там, где все доступное адресное пространство может быть модифицировано только кодом и никем больше. Но такая система является бесполезной на практике, она кроме выполнения бесполезных вычислений больше ничего не может. Любое прерывание, любую модифицируемую извне память(например IO-регистры) уже можно назвать отдельным потоком. Каждый IO-регистр - это уже отдельный маленький поток, эти регистры могут изменятся параллельно, независимо друг от друга и от основного кода программы.