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

brag

Свой
  • Постов

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

  • Посещение

Репутация

0 Обычный

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

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

Контакты

  • Сайт
    Array
  • ICQ
    Array

Информация

  • Город
    Array

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

3 908 просмотров профиля
  1. Вот асинхронная реализация. 80 строк чисто кода(без учета пустых). Примерно столько же понадобилось для тредовой реализации. Только тут учтены все таймауты и конкуренция. Работа с SPI скопирована из Вашей, но семафор ожидания заменен на асинхронный post(тупо добавление в очередь на исполнение после выходa из всех прерываний). Реализация BLK_Read/BLK_write будет аналогичная spi_xfer - закидывание в очередь аргументов. С последующим выполнением spi_xfer(...); // команды начала чтения on_xfer1_complete(){ spi_xfer(...); } on_xfer2_complete(){ spi_xfer(spidev, NULL, req->buf, req->size); } BLK_Cancel - соответственно удаление соответствующей записи из очереди, если она там есть. Если очередь реализована на circular doubly linked lists, то такая проверка происходит автоматически, дополнительный if не нужен и вся операция занимает 6 Thmub-инструкций - 2 чтения и 4 записи.
  2. Ок. Хороший ос ;) Но как быть с общим таймаутом на несколько(сотен) как параллельных, так и последовательных операций? Потому что такой накопитель. Некоторые блоки могут читаться очень медленно с кучей повторов, а другие моментально с первого раза да на полной скорости, все помним компакт-диски или дискеты ;)) Да и сетевые соединения тоже себя так могут вести. Такая задача просто. Таймаут на одну операцию 5 сек, на группу операций - 10 минут. Ок, согласен. Но это сработает, если есть поддержка нужного функционала со стороны OC. ОС должны быть уже реализованы все возможности, которые потребуются пользователю. Всякие там waitForMultipleObjects,как в винде, семафоры с таймаутами итд. Тот же printf, у него нет таймаута, а если понадобится? Хорошо, когда готовое есть. Можно вообще ничего не программировать, просто купить(скачать) и все ;) А когда его нет, приходится выдумывать что-то. Вот это что-то без асинка сделать нереально на практике. Нужно все больше и больше потоков, а их синхронизация - это боль. Не даром же в эпоху очень мощного железа появляются такие вещи, как nodejs. Можно ведь было по старинке на php с обычным printf серваки клепать. Ресурсы ведь позволяют(а позволяют ли?) Если ресурсов хватает на контекст, стек и очереди для примитив синхронизации, а так же на очереди, в которые будут падать данные, когда printf висит, значит их с лихвой хватит на то, чтобы просто увеличить очередь printf и забыть. Бонусом можно печатать с прерываний и любого другого места. Ведь printf же не просто печатает какой-то хардкод, он должен где-то брать данные, которые тоже должны где-то хранится и как-то этому printf передаваться. А если хардкод, то можно просто генернуть цепочку асинк-вызовов каким-нибудь скриптом )) А так согласен, если хочется линейного кода, то да, надо делать.
  3. Да, только это асинхронные функции внутри, вернее на половину, их можно реализовать только, если имеется асинхронный таймер. И поддержка потоками асинхронных сигналов. Мало того, Вам пришлось в драйвер устройства(точнее во все драйвера!) добавить поддержку таймаута(естественно асинхронным способом ибо иначе реализовать это невозможно). А если понадобится какая-то функция еще, Вам придется править не пользовательский линейный код, а асинхронный драйвер, и не один, а все! В случае полностью асинхронной программы драйвер будет максимально простым, а нужные навороты можно добавлять на любом слое на любом этапе проектирования, а главное - использовать уже имеющиеся наработки. То есть можно реализовать отдельно простейшие BLK_read BLK_write и BLK_cancel (отмена текущей операции). Потом отдельно реализовать op_timeout. И соединить это в кучу на следующем слое. Программа получается модульная, состоящая из простых кубиков, а не сложных огромных монстров. Tочность такого таймаута будет крайне низкая - с джиттером зависимым от BLK_read timeout. А надо выходить сразу, не дожидаясь BLK_read timeout. Кроме того не реализована логика печатания в лог с шагом 1% ;) Размер блока не может быть 1мб, поскольку у нас всего 32кб оперативки. А надо мгновенно(в разумных пределах конечно). BLK_read timeout - 5-10 секунд. таймаут на весь файл 10 минут. По запросу пользователя необходимо корректно прервать операцию так, чтобы у пользователя не возникло желания нажать на кнопку "Stop" еще раз, на практике это 100-200мс, не больше. То есть оберткой над асинхронным драйвером :)) Иными словами львиную и самую сложную долю задачи пришлось таки решать асинхронно. Да все понятно, но в коде нет таймаута! А это ключевой момент в данной задаче. И чтобы его добавить, Вам понадобится править самый нижний уровень, тобышь spi_xfer. И реализовать такую правку будет непросто, честно говоря я даже не знаю как, давно спрыгнул с блока, программирую только асинк, как под PC, смартфоны, серверы, так и под embedded. По идее надо сначала дождаться semtake, затем проверить статус асинхронного таймера, остановить таймер, и на основе этих данных пихнуть в очередь либо ResulutOK, либо TimeoutError. Итого понадобилось: 3 потока, асинхронный таймер, 2 очереди, 2 семафора. С тесной взаимосвязью всего этого. С миксом синхронного и асинхронного кода. И все это по идее должно работать корректно, если нигде не была допущена не очевидная ошибка, с синхронизацией ее допустить легко, а узнать о ее наличии сложно. И это еще без реализации BLK_read, BLK_write. А туда еще как минимум 2 очереди. И все равно задача не решена, поскольку "BLK_read timeout - 5-10 секунд. таймаут на весь файл 10 минут. По запросу пользователя необходимо корректно прервать операцию так, чтобы у пользователя не возникло желания нажать на кнопку "Stop" еще раз, на практике это 100-200мс, не больше."
  4. Тривиальная задача для асинхронного программирования(при чем без всяких вытеснений и приоритетов - проблема гонок отсутствует полностью, никаких критических секций не нужно) и вообще нерешаемая для линейного(пусть и многопоточного): Есть 2 SPI-шины на каждой по 3 устройства. Нужно переписать файл объемом 100мб из одного из устройств на шине 1 в любое другое на шине 2. Программа должна уметь копировать файлы одновременно по всем 3м устройствам в произвольном порядке. То есть одновременных операций копирования может быть 3. Количество оперативной памяти ограничено и равно 32кб для всей программы. На каждую операцию чтения и записи должен быть установлен таймаут. Также таймаут должен быть установлен на всю операцию копирования - не скопировались все 100мб за 10 минут - все, таймаут. При срабатывании любого таймаута необходимо освободить шину\шины (поднять соответствующие CS). Нужно печатать в uart прогресс копирования от 1 до 100 % строчками вида: "Copy from SPI1.dev1 to SPI2.dev3 in progress 5%", строчки должны появляться каждый 1%. Пропускной способности uart для этого достаточно. При таймауте или любой другой ошибке записи\чтения тоже нужно отправить соответствующее сообщение в лог. Копирование нужно выполнять максимально эффективно, то есть так, чтобы ни одна из шин не простаивала, если это физически возможно(скорость чтения равна скорости записи). В том числе и во время печатания в uart. Также нужна возможность отмены операций пользователем с корректным освобождением шин.
  5. Как заведомо знать, что размера очереди хватит? Без блокировок посчитать легко, с ОС и блокировками куда сложнее, ведь может быть такая ситуация, что тред в блоке, а данные в очередь летят. Но это еще не все. Доступ к UART-TX у нас конкурентный! Из 5 потоков. Как будем разруливать эту ситуацию? Очередь из фреймов(какого размера?)? Или может быть мютексы и блокировать порт(по крайней мере передающую его часть) на все время передачи фрейма? Ведь фрейм должен быть целостный, промаркированный (чтобы сетевой драйвер смог распознать какому соединению он принадлежит) и с контрольной суммой(чтобы сетевой стек мог отбрасывать кривые фреймы). Как по мне, тут ад уже начался. В асинхронной модели этих всех проблем нет. Нет потоков, нет потоковой конкуренции, нет проблем. Сериализация операций над портом осуществляется тривиальной intrusive-очередью(это та, которая никогда не переполняется) + такой же тривиальной fsm: Итого имеем две потенциальные блокировки. Рассчитать требуемый размер первой очереди на случай заполненной второй довольно непросто. Остается только лупануть очереди с огромным запасом и надеяться, что этого хватит )) В асинхронном варианте и одно и другое легко достижимо, собственно реализовано в моем примере. В блокирующем же подходе нам ничего не остается, как заблокироваться, если очередь заполнилась(как это делает стандартный putc), что еще больше усугубит проблему. Да и он асинхронный. блокирующим способом потенциально реализовать можно, но это будет мрак. Но это еще цветочки. Настоящий бич тредового подхода - таймауты. Без введения асинхронных костылей в тредовую модель таймаут реализовать линейным кодом невозможно в принципе - тред не умеет одновременно делать sleep и read. А при введении таких костылей(например unix-сигналы) или просто kill_thread(что тоже по сути является асинхронным сигналом\прерыванием) программа перестает быть линейной и начинается ад.
  6. Следующая задачка. Есть 2 шины I2C и SPI. К каждой подключено по 5 устройств. Для простоты рассмотрим 2 из них - I2C дисплей и SPI-флешка. Нужно реализовать такую программу, чтобы при ошибке инициализации дисплея была сделана соответствующая запись в лог-файл на флешке. При ошибке инициализации флешки необходимо вывести уведомление на дисплей. Если ни то ни другое не удалось - сжечь прибор :))) Инициализация этих обеих устройств должна выполняться параллельно - так, чтобы одна шина не простаивала, когда идет обмен на другой. Объем данных на шинах в процессе инициализации каждого устройства большой и занимает много времени. Процедуры инициализации сложные. Но вдаваться в подробности мы тут не будем, у нас уже это все давно реализовано в двух вариантах - блок и нонблок. Асинхронным способом решение простейшее: Даже думать боюсь, как бы такое решение выглядело в линейном (чисто блокинг - тредовом) коде
  7. А иначе никак, хоть callbacks, хоть blocking. Если на этапе проектирования не было заложено больше - ничего не выйдет. Можно динамическую память пристроить легко, но тогда все равно упремся в лимит размера кучи(пула) заданный на этапе компиляции или(что куда хуже) в физический лимит RAM Волшебства там нет, async/await это просто синтаксический сахар над Primise-ами. Код все равно не будет линейным, он по прежнему остается асинхронным, просто синтаксически(отдаленно) похож на линейный. А Primise - ни что иное, как обычные конечные автоматы. И на C/C++ можно реализовать их, только без(много-много) динамической памяти толку от них 0. Да, это и экономия ресурсов и простота управления множеством конкурентных событий. Весь современный софт движется в сторону асинхронного - появление таких вещей, как NodeJS, Promise и интеграция их в языки программирования, поддержка языками async/await, синтаксические решения в языке Kotlin итд - тому доказательства. Может и через некоторое время мы и для embedded получим какой-то язык, который будет как-то помогать нам работать со стейтом. А пока лучшего решения, чем явно определять структуры и гнать его в виде указателя that нет. Но я привык, мне норм ;)) Thread, без прикручивания к нему асинхронных фишек, например древнейших unix-сигналов )) больше одного события обработать не может, а обычынй копеечный автомат может хоть сотни тысяч
  8. здесь https://electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=136908&do=findComment&comment=1784127 https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTP-server/ какая разница uart или ethernet? оба интерфейса последовательны, разница лишь в протоколах. Да хоть pci-express с сотней слоев, сути это не меняет. Тема практическая, про реализацию задач автоматным и блокирующим способом, их сравнение, преимущества и недостатки каждого. Очень желательно с реальным примером кода.
  9. Теперь реализация самого сервера. Все ессно в один поток одного приоритета. Задачка в общем то тривиальная, такую решают все начинающие node-js программисты. Осталась реализация socket_write. Она в общем то простейшая, то об этом в следующий раз
  10. Дело привычки ;) Я думаю, что это невозможно. Линейный код будет только тогда, когда задача линейная. Например классические команды OS Unix - ls, rm итд. НО не cp, нормальный cp в чисто блокирующем коде, то есть без привлечения асинхронного :)) драйвера реализовать практически невозможно. Когда программа интерактивная, она в принципе не может быть линейной. Даже самая простая, из диода и двух кнопок. Да такие задачи решаются довольно часто. И очень хорошо ложатся на неблокирующий подход. Именно это мы видим в моей реализации выше ;) А по-другому никак! Теоретически можно реализовать на тредах, но код будет адище. А вот с этого места хотелось бы по-подробнее. Смотрите, есть UART(регистры rx_dat, tx_dat и 2 прерывания: rx_full, tx_empty), и есть блокирующая ОС, что делаем дальше? Как будем организовывить работу с UART с тредов? ок. есть пул тредов Thread pool[5]; void Thread::get_request_thread() { // что делаем здесь??? } Как эта задача будет получать запросы и как передавать дальше на обработку? Как будет вести учет свободных тредов? Ок. Какие конкретно нужны очереди, какого размера и сколько штук? Чтобы гарантировано хватило и программа не заглючила ни при каких обстоятельствах? То есть чтобы ни один пакет не был утерян, чтобы ни одна АЦП выборка не была утеряна. Именно последний способ применен у меня в коде, его можно использовать и в тредовом подходе. struct Output get_last_output(void) { struct Output out; // защищаем данные короткой критической секцией global_disable_interrupts(); out = g_adc.out; global_enable_interrupts(); return out; }
  11. Вот реализация нашего uart/tcp-ip. Нужная нам функция тут одна void uart_tcp_write(const uint8_t *p_data, int data_size, Callback_T completion_callback) она пакует наши данные в фреймы и отправляет эти фреймы в uart. Но ее нельзя вызывать повторно, пока не отработает предыдущая, то есть пока не получим completion_callback. Нас это совершенно устраивает, потом увидим почему. RX-данные пока просто складываются в очередь. Hикак не обрабатываются, но нам это пока и не нужно. Допустим, они обрабатываются корректно. Позже, если понадобится, покажу и их обработку. А теперь попробуйте реализовать uart_tcp_write блокирующим способом, да так, чтобы ваш тоже блокирующий АЦП-код работал корректно. ;)
  12. Итак, асинхронная реализация первой части задачи - АЦП и обработка данных 75 строк кода с комментами. Попробуйте реализовать это блокирующим подходом, сравним сложность и размер кода ;) Продолжение следует
  13. Теперь пример веб сервера. У нас есть АЦП с тактовой 100kHz и 10 каналов. Выборки терять нельзя. Буфера нет, точнее он одноуровневый - туда лезет всего одна выборка. Дальше у нас есть функция, подобна той, которую привел amaora. Она принимает на вход эти данные, и выдает выход вида float y[8]; Частота выходных выборок достоверно неизвестна, поскольку зависит от самих данных на входе(как у amaora), но мы точно знаем, что она не выше 1кгц. Данных может и вовсе не быть на выходе долгое время, поскольку функция на основе полученных данных пока не смогла найти решение. Также у нас есть UART 115200 бит/s на который надо в виде неких пакетов выводить данные. Допуcтим это будет (условный) TCP/IP протокол. Особо вдаваться в подробности тут не будем, достаточно одного условия - данные идут не непрерывно, а пакетами(фреймами) по 256 байт. Нужно сделать веб-сервер, который одновременно будет обслуживать до 5 клиентов(через один uart). По GET запросу должен выдаваться HTML-документ вида <html><body><h1>timestamp</h1> <table><tr><td>y[0]</td></tr><tr><td>y[1]</td></tr>.....</table></body></html> Размер этого документа неизвестен, но он заведомо больше того, что лезет в 1 фрейм. timestamp - это время последнего результата, полученного на выходе нашей функции. В формате hh:mm:ss. y - собственно последний результат. Предыдущие результаты нас не интересуют и их можно смело терять. Если результата все еще нет нужно вывести <h1>No result yet</h1> и таблицу не выводить вовсе. Производительности процессора заведомо достаточно, чтобы наша функция при любых раскладах смогла обработать входной поток с частотой 100кгц. А также ее заведомо достаточно, для выполнения всей задачи. НО время выполнения самой функции иногда может доходить до 200мкс, что значительно превышает периодичность поступления данных по UART 8/115200 = 69мкс, а так же периодичность данных АЦП 10мкс. Данные UART и АЦП терять нельзя! Предлагаю всем желающим решить эту задачу блокирующим способом. А в следующих постах я решу ее асинхронным. Вдаваться в конкретные реализации драйверов и ОС не нужно, достаточно самой концепции.
  14. Согласен. Но оба эти подхода несовместимы практически. Если в FSM применить блокирующую функцию - вся FSM зависнет и не сможет реагировать на другие события(входы). Поэтому на практике задачи решаются либо тем, либо другим способом. При попытке их комбинировать получается АД. Не криво, а так, как требует условия задачи. Если в задаче сказано, что в случаи нехватки памяти лог можно терять, значит так и нужно, при этом а) программа должна продолжать работу корректно. б) печать лога никак не должна сказываться на обработке важных событий.
  15. Давайте конкретный пример, покажу, как делается ;)) Но даже здесь. Как будете добывать текущие данные с АЦП и как будете печатать? Можно псевдокод? Именно того участка, где данные передаются с периферии к нашим максимум 5 блокирующимся в putc процессам(потокам). Данные с АЦП как возникают? Прерывание adc_isr() или может быть busy loop while(adc_reg.dataready); Как именно? Есть ли у АЦП какой-то fifo или просто регистр ADCDAT? Какая частота? Какая пропускная способность TCP-соединения? Что если она в эквиваленте ниже, чем частота АЦП? Давайте пример, посмотри на сколько будет сложнее или проще В каких? Покажите хоть один конкретный случай. amaora показал и я сразу выдал асинхронное решение, хоть и задача была спроектирована под блок сложнее оно особо не стало. Именно так работает Javascript да и любой аналог console.log Будет работать до тех пор, пока не закончится память. А когда закончится данные начнут улетать в пропасть. Для важных данных применяются asyc io: fs.appendFile(path, data[, options], callback)
×
×
  • Создать...