Мне тут у английской версии статьи оставил комментарий некто Алекс. Т.к. там — только по-английски, то я оттуда удалил, скопирую сюда, и отвечу.
Видел статью на хабре, но не зарегистрирован там… и тем не менее.
Такая реализация как у вас, довольно наивна. (комментирую статью на хабре)
у вас какие-то архитектурные недостатки видны.
ну например зачем спасать контекст треда если обрабатывается прерывание? если вы хотите переключить треды в результате обработки этого прерывания, это надо сделать позже.
треды должны быть изолированы и нельзя чтобы один тред останавливал другой. это в большинстве случаев сделать корректно нельзя. все что может сделать один тред по отношению к другому — запустить тред, послать сообщение в очередь. все. чтобы тред остановить надо посылать ему в очередь сообщение об остановке. поскольку корректно извне тред не остановишь в общем случае.
далее. надо изолировать треды и прерывания вообще. треды ничего не могут знать про прерывания, а прерывания не могут вызывать тредовые функции. обработчики прерываний лишь берут данные(или не берут ничего) по внешнему событию, и кладут их в очереди, кольцевые буферы, и сигналят неким способом о событии в “мир тредов”. такая штука надывается каналом(обычно кольцевой буфер). Кернел с разделенными мирами прерываниий и тредов запрещает прерывания всего в паре мест
при занесии данных в канал, и выборе из него. При переключении тредов и всех реализации всех функций из мира тредов, как то — локи мьютексов, всякие слипы и проч. вплоть до переключения тредов — не запрещаются прерывания вообще. тогда система не теряет событий. Наивная же реализация, когда прерывания везде запрещены-разрешены приводит к тому что на частых событиях происходит потеря данных.
Вообще рилтайм эт оне вполне — немедля запустить тред обработки ивента с железа, а немедля забрать у железа данные и положить в буфер для обработки, — это как минимум.
таймеры у вас какие-от странные — там просто сортированная очередь обьектов(двусвязный список) — ближе к голове — наиболее ранний по срабатыванию… а у вас там какой-то наворот, даже не стал разбираться.
таймеры надо делать — жесткие и мягкие. жесткие могут работать прямо из контекста таймерного прерывания, а мягкие — запускаются текущим тредом, например при ближайшем переключении тредов. жесткие для жесткого рантайма, мягкие — для каких-то не сильно рантаймовых действия, например светодиодом помигать и так далее.
обработку прерываний можно разбить на два этапа — isr и dsr. dsr — просто активируемые из isr хуки постобрабтоки данных из прерывания, работающие в контексте текущего треда или собственном стеке, например(поскольку в isr вообще не видят тредов и не знают что это такое(в системе строгого разделения прерываний и тредов))…
ну и так далее…
критические секции лучше не давать юзеру, любой слип или ожидание в них заблокируют систему. синхронизация делается мьютексом.
критическая секция это не запрет прерываний(это убийственно для рантаймовой системы), а запрет переключений тредов. запрет прерываний это вообще стоит особенной секцией оформлять.
как вы взаимную блокировку там обнаруживаете — непонятно, она может глубоко косвенной, через цепь тредов, когда они по кольцу захватили мьютексы и не отдают.
все опреации дложны быть с таймаутом, потому дедлоки там в принципе невозможны… дедлоки это у студентов в учебниках.
1 кБ для ISR — как я написал, это полностью зависит от приложения. Может, где-то и 200 байт достаточно будет. Как минимум, есть 128 байт для контекста, плюс нужно для каждого приоритета прерывания предположить, что все вложенные прерывания вызывались в самом худшем случае. Точно высчитывать, сколько стека нужно, и выделять ровно-ровно — вообще дело неблагодарное, только в крайнем случае можно этим заняться, когда RAM ну вообще нет больше. Я обычно просто смотрю, сколько испачкано, и оставляю раза в два больше, на всякий случай.
Позволяете ли вы вызывать из ISR все вызовы RTOS, или только подмножество?
Как можно все сервисы RTOS вызывать из прерывания? В прерывании нельзя вызывать любой сервис, который может ждать. Пока что, по образу и подобию TNKernel и других РТОС, для прерываний существует отдельный набор сервисов, но вообще-то, как минимум на поддерживаемых в данный момент архитектурах, можно было бы просто позволить вызывать любые сервисы, которые не могут ждать. Но пока, как наследие от TNKernel, есть отдельный набор сервисов. Доп. инфу см. тут, тут.
Как код переноса SP в область ISR после входа (и по все видимости, сохранения минимального контекста — флагов и IP) обрабатывает ситуацию вложенных прерываний? Насколько он эффективен (какая латентность привносится в обработчик прерываний до пользовательского кода)?
Конечно, от архитектуры зависит. На MIPS — 6-10 лишних инструкций до и после ISR, на PIC24/dsPIC — 3-5 инструкций. А вот Кортекс я люблю за то, что эта платформа была спроектирована «with OS in mind», так что она все делает за меня. Ядро ничего особенного не делает, когда происходит прерывание.
Вещи правильные говорите, спасибо, я и сам об этом уже думал, но не знаю, когда смогу добраться. Идей больше, чем времени.
В идеале, мне хотелось бы следующего: для всех поддерживаемых платформ иметь однотипные девайсы, которые я смогу подключить по USB все сразу к какой-нибудь машине, и эта машина при появлении новых коммитов должна собрать прошивки с юнит-тестами для каждого устройства, залить их, прогнать и выдать результат, чтобы я сразу видел, появились ли проблемы. После чего непрерывно гонять текущую прошивку с асинхронными событиями, как вы говорите. Пусть хоть месяцами подряд. Ну и если когда-нибудь что-то всплывет, то я об этом должен узнать.
Это, конечно, все классно, но пока выделить столько времени нет возможности.
Подробные формализованные тесты написаны — уже очень хорошо; а эти вероятностные вещи приходится гонять на реальных устройствах, где асинхронных событий сколько хочешь. За последний год проблем не было, так что пока считаю, что все хорошо.
Если вы читали статью, то, может быть, заметили: был нужен API, по-максимуму совместимый с TNKernel, чтобы без проблем перенести существующие проекты (оставлять на старой ОС нельзя). Так что изначальный план был — улучшить TNKernel, ну а получилось так глобально в итоге. Это первое.
Второе: с грустью глядя на найденные баги в TNKernel (которая достаточно популярна, и используется давно), я уже не очень хотел доверять другим РТОСам. Может, конечно, ChibiOS и другие тестируются очень хорошо и подробно — я же не знаю.
Третье: пока я этим занимался, я неплохо разобрался в матчасти, так что это, в некоторой степени, вклад в будущее. Самообучение для вас — тоже «удовлетворение амбиций»? Ну и, кстати, с работодателем мы в доле: пока я работал над ядром, я реально горел, так что работал по 12 часов в сутки, включая некоторые выходные. И, конечно, в целом работодатели вкладываются в обучение своих сотрудников, это нормально.
Тон вашего комментария, конечно, такой, малоприятный. К сожалению, когда подобные комментарии оставляют у моих статей, я вынужден отвечать и на них.
Конечно, есть. То, о чем вы говорите — это инверсия приоритетов. Только для этого нужно использовать не семафор, а мютекс. Ядро поддерживает два метода обхода инверсии приоритетов: priority ceiling и priority inheritance (и, кстати, это работало криво в TNKernel: с помощью юнит-тестов я нашел две простые ситуации, когда инверсия приоритетов не обрабатывается корректно. Подробнее тут: Bugs of TNKernel 2.7)
Вообще, эти термины, мютекс и семафор, очень часто путают, даже в серьезных библиотеках и РТОСах. Насчет отличий семафоров и мютексов крайне рекомендую к прочтению две ссылки:
Когда задача захватывает мютекс, она им владеет. И только она может его разблокировать. Кроме прочего, именно то, что мютекс «принадлежит» задаче, позволяет обойти инверсию приоритетов, о которой вы говорили. Поэтому мютексы нужно использовать именно для защиты разделяемых ресурсов: задача блокирует мютекс, работает с каким-либо ресурсом, разблокирует мютекс. По этой же причине, мютексы нельзя использовать в прерываниях (т.к. у мютекса должна быть задача-владелец).
Семафор
С семафором картина совершенно иная: его не блокируют или разблокируют, а ждут и сигналят. И эти действия производятся в разных потоках: например, поток А должен сделать какую-то работу, после которой поток Б может делать свою. Тогда поток Б ждет семафор, а поток А сигналит ему: типа, у меня все готово, теперь давай ты. Также сигналить семафором можно из прерывания.
По ссылкам выше можно найти более подробное объяснение.
Конечно, можно использовать семафоры вместо мютексов, работать кое-как будет (без обработки инверсии приоритетов). Но можно и саморезы в стену гвоздями забивать, держаться будут.
И еще раз подчеркну: эти термины очень часто путают, даже в серьезных продуктах. Так что если кто-то работал в какой-то среде, где мютексы назывались семафорами — я нисколько не удивлюсь.
А вот насчет визуализации: я сам когда-то пытался найти инструмент, который сможет визуализировать map-файл, сгенерированный компилятором; очень полезно иногда было бы; когда, например, где-то память портится. Не нашел такого инструмента. Если знаете — поделитесь, пожалста.
А про списки, правильно ли я понимаю, что при старте новой задачи память под новый экземпляр struct TN_Task выделяется приложением, а не ядром?
Да, правильно. Небольшое уточнение только: память выделяется не при старте задачи, а при ее создании (созданную задачу можно останавливать и запускать сколько угодно раз; правда, в реальной жизни мне это ни разу не пригождалось).
См. tn_task_create(): первый аргумент struct TN_Task *task — это как раз указатель на дескриптор задачи, который приложение должно где-то выделить до вызова этого сервиса.
Спасибо и вам за отзыв. Я постарался сделать документацию понятной (есть и Quick Guide, и примитивные примеры для разных платформ в репозитории), но если что — спрашивайте.
Хм. Так в статье ведь есть карта распределения памяти: вот. Приложение выделяет экземпляры объектов (т.е. MyBlock примера в статье) самостоятельно и как угодно (в моих приложениях, чаще всего, статически). И передает указатели на эти выделенные объекты ядру. И когда ядро должно добавить этот экземпляр в какой-нибудь список, то оно просто модифицирует несколько указателей, и все.
В случае же с отдельным (не встроенным) TN_ListItem, для каждого добавляемого объекта в список, ядру пришлось бы отдельно где-то выделить этот TN_ListItem: работает гораздо дольше, нужно больше памяти, cache locality хуже.
Насчет «слова» — этот термин, к сожалению, обозначает разные вещи. Здесь имеется в виду платформозависимая величина, равная разрядности регистров процессора. Процессор может обрабатывать значения размером в 1 слово за один такт (32-битный процессор может, например, за один такт сложить два 32-битных числа, в то время как 8-битному потребуется для этого гораздо больше тактов)
Около двух месяцев, но эта первая стабильная версия значительно отличается от текущей версии: только одна архитектура (PIC32, т.е. MIPS), и нет большинства плюшек, добавленных впоследствии.
Ну а распространяться о том, «как платят», все-таки, наверное, ни к чему, и, насколько я знаю, это не очень зависит от того, в какой области работать. Хороших специалистов мало во всех областях.
С момента, когда функция возвращает значение, больше никто не обращается к его scope-объекту, поэтому его собирает сборщик мусора. Но что если определить вложенную функцию, вызвать ее и дождаться возвращения?
Выделенный курсивом текст — это не то, что я имел в виду: But what if we define nested function and return it (or store somewhere outside)?
Т.е. я имел в виду следующее:
Но что если определить вложенную функцию и вернуть ее (или сохранить где-то вне текущего scope-объекта)?
Я долго не мог найти инструмента для закладок в шелле, все найденные решения меня не очень устраивали: все равно неудобно. А потом нашлась очень классная и универсальная штука: fzf, он позволяет не только быстро искать файлы, но и фильтровать произвольные текстовые данные. Поэтому появилась простая идея: почему бы не держать просто файл со списком директорий-закладок, и fuzzy-фильтровать его. Вот как это выглядит:
Я просто пишу «cdg» (от «cd global»), печатаю 3-4 буквы из нужной закладки (или перемещаюсь вверх-вниз по списку), и жму Enter. Очень быстро и удобно, пользуюсь больше года, очень доволен.
1 кБ для ISR — как я написал, это полностью зависит от приложения. Может, где-то и 200 байт достаточно будет. Как минимум, есть 128 байт для контекста, плюс нужно для каждого приоритета прерывания предположить, что все вложенные прерывания вызывались в самом худшем случае. Точно высчитывать, сколько стека нужно, и выделять ровно-ровно — вообще дело неблагодарное, только в крайнем случае можно этим заняться, когда RAM ну вообще нет больше. Я обычно просто смотрю, сколько испачкано, и оставляю раза в два больше, на всякий случай.
Как можно все сервисы RTOS вызывать из прерывания? В прерывании нельзя вызывать любой сервис, который может ждать. Пока что, по образу и подобию TNKernel и других РТОС, для прерываний существует отдельный набор сервисов, но вообще-то, как минимум на поддерживаемых в данный момент архитектурах, можно было бы просто позволить вызывать любые сервисы, которые не могут ждать. Но пока, как наследие от TNKernel, есть отдельный набор сервисов. Доп. инфу см. тут, тут.
Конечно, от архитектуры зависит. На MIPS — 6-10 лишних инструкций до и после ISR, на PIC24/dsPIC — 3-5 инструкций. А вот Кортекс я люблю за то, что эта платформа была спроектирована «with OS in mind», так что она все делает за меня. Ядро ничего особенного не делает, когда происходит прерывание.
В идеале, мне хотелось бы следующего: для всех поддерживаемых платформ иметь однотипные девайсы, которые я смогу подключить по USB все сразу к какой-нибудь машине, и эта машина при появлении новых коммитов должна собрать прошивки с юнит-тестами для каждого устройства, залить их, прогнать и выдать результат, чтобы я сразу видел, появились ли проблемы. После чего непрерывно гонять текущую прошивку с асинхронными событиями, как вы говорите. Пусть хоть месяцами подряд. Ну и если когда-нибудь что-то всплывет, то я об этом должен узнать.
Это, конечно, все классно, но пока выделить столько времени нет возможности.
Подробные формализованные тесты написаны — уже очень хорошо; а эти вероятностные вещи приходится гонять на реальных устройствах, где асинхронных событий сколько хочешь. За последний год проблем не было, так что пока считаю, что все хорошо.
Второе: с грустью глядя на найденные баги в TNKernel (которая достаточно популярна, и используется давно), я уже не очень хотел доверять другим РТОСам. Может, конечно, ChibiOS и другие тестируются очень хорошо и подробно — я же не знаю.
Третье: пока я этим занимался, я неплохо разобрался в матчасти, так что это, в некоторой степени, вклад в будущее. Самообучение для вас — тоже «удовлетворение амбиций»? Ну и, кстати, с работодателем мы в доле: пока я работал над ядром, я реально горел, так что работал по 12 часов в сутки, включая некоторые выходные. И, конечно, в целом работодатели вкладываются в обучение своих сотрудников, это нормально.
Тон вашего комментария, конечно, такой, малоприятный. К сожалению, когда подобные комментарии оставляют у моих статей, я вынужден отвечать и на них.
Вообще, эти термины, мютекс и семафор, очень часто путают, даже в серьезных библиотеках и РТОСах. Насчет отличий семафоров и мютексов крайне рекомендую к прочтению две ссылки:
Вкратце:
Мютекс
Когда задача захватывает мютекс, она им владеет. И только она может его разблокировать. Кроме прочего, именно то, что мютекс «принадлежит» задаче, позволяет обойти инверсию приоритетов, о которой вы говорили. Поэтому мютексы нужно использовать именно для защиты разделяемых ресурсов: задача блокирует мютекс, работает с каким-либо ресурсом, разблокирует мютекс. По этой же причине, мютексы нельзя использовать в прерываниях (т.к. у мютекса должна быть задача-владелец).
Семафор
С семафором картина совершенно иная: его не блокируют или разблокируют, а ждут и сигналят. И эти действия производятся в разных потоках: например, поток А должен сделать какую-то работу, после которой поток Б может делать свою. Тогда поток Б ждет семафор, а поток А сигналит ему: типа, у меня все готово, теперь давай ты. Также сигналить семафором можно из прерывания.
По ссылкам выше можно найти более подробное объяснение.
Конечно, можно использовать семафоры вместо мютексов, работать кое-как будет (без обработки инверсии приоритетов). Но можно и саморезы в стену гвоздями забивать, держаться будут.
И еще раз подчеркну: эти термины очень часто путают, даже в серьезных продуктах. Так что если кто-то работал в какой-то среде, где мютексы назывались семафорами — я нисколько не удивлюсь.
А вот насчет визуализации: я сам когда-то пытался найти инструмент, который сможет визуализировать map-файл, сгенерированный компилятором; очень полезно иногда было бы; когда, например, где-то память портится. Не нашел такого инструмента. Если знаете — поделитесь, пожалста.
Да, правильно. Небольшое уточнение только: память выделяется не при старте задачи, а при ее создании (созданную задачу можно останавливать и запускать сколько угодно раз; правда, в реальной жизни мне это ни разу не пригождалось).
См. tn_task_create(): первый аргумент
struct TN_Task *task— это как раз указатель на дескриптор задачи, который приложение должно где-то выделить до вызова этого сервиса.В случае же с отдельным (не встроенным)
TN_ListItem, для каждого добавляемого объекта в список, ядру пришлось бы отдельно где-то выделить этотTN_ListItem: работает гораздо дольше, нужно больше памяти, cache locality хуже.И за замечание спасибо, поправил. Про PIC32MZ — рад слышать, если что, пишите!
Один, да, но, на самом деле, это не такая уж большая работа: взгляните на исходники, проект-то сравнительно маленький.
На тесты уходило очень много времени: наверное, около 30% всего времени, потраченного на ядро — это тесты. Но без них нельзя.
Насчет «слова» — этот термин, к сожалению, обозначает разные вещи. Здесь имеется в виду платформозависимая величина, равная разрядности регистров процессора. Процессор может обрабатывать значения размером в 1 слово за один такт (32-битный процессор может, например, за один такт сложить два 32-битных числа, в то время как 8-битному потребуется для этого гораздо больше тактов)
Wikipedia: Word (computer architecture)
Ну а распространяться о том, «как платят», все-таки, наверное, ни к чему, и, насколько я знаю, это не очень зависит от того, в какой области работать. Хороших специалистов мало во всех областях.
Выделенный курсивом текст — это не то, что я имел в виду: But what if we define nested function and return it (or store somewhere outside)?
Т.е. я имел в виду следующее:
Извините уж, ребята, что сам не добрался.
Я просто пишу «cdg» (от «cd global»), печатаю 3-4 буквы из нужной закладки (или перемещаюсь вверх-вниз по списку), и жму Enter. Очень быстро и удобно, пользуюсь больше года, очень доволен.
Подробная статья: Fuzzy bookmarks for your shell