ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Воскресенье
22 декабря
1013077 Топик полностью
Связанные сообщения
Многозадачность
Немного рассуждений про ch32v307+freerto+libwchnet.a2024-07-10
Выскажу ещё раз: FreeRTOS сырая недоделка, смысла особого, без реализации ряда перечисленного (см. ниже) не имеет и, хуже того, ...2019-10-18
Задача в принципе решима... Но для начала следует понимать некоторые вещи, после чего придёт также понимаение, что не стоит пыта...2014-01-02
Нефиг си пинать за то, что он не хаскель ;)2011-08-14
Тут очень любят рассуждать о RTOS и всём таком. Но как-то массово замалчивается, что стандартная C-библиотека для неопределённог...2011-08-13
fk0легенда (19.06.2020 00:55, просмотров: 1021) ответил LightElf на У тебя привычка обобщать все по самое некуда :) И в этом самом некуда - находить неразрешимые проблемы. Не бывает единого драйвера под разные OS и радикально разное железо.
Я не обобщаю, я подвожу к сложности реализации такой элементарной сущности как условная переменная. Без которой нормально задачу не решить. Да, её можно построить на базе пары семафоров, причём один нужен в TLS (а в ThreadX есть TLS?), или можно сделать как в SDL, по семафору на каждого ждуна (что реально плохо выглядит когда их много -- я уже как-то писал про это). В данной задаче можно схалявить и сделать недоделку (не являющуюся полноценной условной переменной, но 

работающей) на базе RWlock и семафора, причём RWlock тоже по-человечески не сделать... только реализацию с приоритетом читающей стороны, а чтоб его сделать по-человечески нужна опять же условная переменная (рекурсия!) ...


Есть ряд краеугольных примитивов на которых всё стоит. Это в первую очередь атомарные операции: test-and-set, fetch-add, compare-and-swap. Без них вообще невозможно (хотя есть алгоритм Петерсона, но он на мой взгляд настолько страшен, что лучше даже не пытаться). И, кстати, в младших ARM'ах нет пары инструкций LDREX/STREX и реализуется только test-and-set. Опять же я уже писал, что ядро Linux из-за этого предоставляет костыли с подпорками для реализации остальных операций. А как дело обстоит в ThreadX опять же?


На атомарных операциях сверху стоят быстрые реализации примитивов синхронизации. Тот же семафор или мьютекс, разумеется если они используются в системе с общей памятью (а embedded OS все такие) можно реализовать эффективно, без системного вызова, в ситуациях, например, когда мьютекс не залочен на входе и не имеет ждуна на выходе, когда семафор не имеет ждуна: достаточно просто инкрементировать ячейку памяти. Это единицы тактов, максимум десятки тактов (если Linux с костылями). Или запросто тысячи, если реальный системный вызов, с сохраненим контекста и всем таким прочим.


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


Наконец есть ряд краеугольных примитивов синхронизации на основе которых можно построить остальные. Собственно в Linux это ровно один примитив -- futex. Ячейка памяти, на которой можно ждать, и ядру можно сказать пробудить один или все потоки ожидающие на данной ячейке. Достаточно удобно. Имея атомарные операции сверху можно построить ту же условную переменную и мьютекс. На основе которых можно уже построить всё остальное: очереди, семафоры, флаги событий, таймеры и т.п. В обратную сторону -- сильно сложней. Построить условную переменную на основе мьютекса и семафоров -- сложно. Есть известная микрософтовская статья, где они предлагают всё новые реализации и находят всё новые недостатки...


Так же есть краеугольные функции OS, в частности TLS. Для реализации которого в MIPS даже аппаратный спец. регистр сделан. На ARMv6+ тоже. На ранних ARM, как я понимаю -- программно. А программно без поддержки OS тоже не просто. Можно, достаточно знать границы стеков всех потоков и на основе значения SP вычислить условно номер текущего потока и найти адрес local storage для данного потока. Но данная операция становится чрезвычайно тяжёлой. Особенно в ситуации, когда стеки потоков не фиксированные (например, все по 2МБайта начиная с такого-то адреса), а назначаемые произвольно (в Linux ещё есть sigaltstack...) Без TLS не реализовать ряд алгоритмов (да чего далеко ходить, элементарно errno из libc не работает). В частности условную переменную сделать сложней.


Но во многих embedded OS как под копирку реализуют какие-то одни функции и не реализуют остальные. Почему сделан именно такой выбор понять невозможно, кажется что просто список функций списывают с других ранних коммерческих RTOS. Есть мьютексы, семафоры, флаги событий (из TRON, но их обычно 32шт. и ни одним больше), очереди сообщений, откровенно плохой аллокатор памяти (и начинаются заумные разговоры про фрагментацию и необходимость пулов блоков фиксированных размеров -- при том, что если взять Doug Lea allocator, то оно как-то работает не хуже, а архитектуру ПО в рамках ограничений RTOS толком не выстроить). При этом все примитивы синхронизации как правило медленные, поддержка атомиков и TLS под вопросом (скорей никак), быстрые примитивы не реализовать, условную переменную реализовать сложно (а реализовать эффективно -- невозможно).


В итоге реализовать не предусмотренные в ОС примитивы, например имеющийся в Windows WaitForMultipleEvents() или в Linx select(), poll() -- невозможно очень сложно. А последний тоже своего краеугольный камень, без которого невозможна реализация да банально однопоточного веб-сервера (обрабатывающего в одном потоке параллельно множество соединений). Как ждать параллельно на нескольких сокетах? Какой-то аналог libevent тоже не построить. Вообще событийную систему, где событий достаточно много (больше 32 шт. предлагаемых в event flags), они потенциально разнородные (не только сокеты, но файлы, таймеры, пайпы, очереди сообщений...) и они обрабатываются в одном потоке. Да можно устроить какую-то дичь, когда создаются специальные потоки, которые ждут там чего-то одного и перекладывают всё в единую очередь сообщений (которая сама по себе может оказаться тяжёлая -- опять же вопрос реализации, в userspace или через системный вызов), из которой уже отдельный поток всё разбирает... потом можно задаться вопросом переполнения очереди сообщений, нужен какой-то механизм синхронизации того кто постит сообщения в очередь и того, кто их вынимает -- всё обрастает какими-то чудовищными подробностями.


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


Возвращаясь к обсуждаемой проблеме. Вариант 1 у тебя очевидно бажный: буфер будет использоваться сильно не полностью, все будут стоять и ждать пока отправится в компорт одна строка, потом будет записана одна следующая. Хотя потенциально могли уместиться все, например. Принципиальная проблема -- нужно отношение один-к-многим. Один читающий поток должен уметь будить всех писателей. С одним семафором этого принципиально никак не сделать. Ты сейчас начнёшь изобретать, мол семафор и счётчик, я легко могу парировать кейсами когда это опять же не работает, и на примерно пятой итерации у тебя получится вариант из статьи: https://www.microsoft.com/en-us/research/publication/implementing-condition-variables-with-semaphores/ Там будет два семафора и необходимость реализации TLS (один семафор глобальный, второй специфичный для потока, и список ждунов ещё). Лучше ты не изобретёшь.


Вариант 2 вообще не вариант. Логгер должен быть *сверхбыстрым*. Это означает большой буфер и торможение только если его _изредка_ переполняет. Ибо логгер который порядочно тормозит программу никому не нужен. Потери текста в логгере тоже не приемлимы -- как потом баги решать? В идеале логгер должен быть без лочки вообще (невозможно в случае переполнения), или с очень маловероятными блокировками.

[ZX]