Thread (и irq) безопасный обработчик динамической памяти в C

Я ищу подсказки по использованию безопасного обработчика динамической памяти в многопоточной системе. Подробности проблемы:

  1. написанный на C, будет работать на процессоре cortex-M3 с RTOS (ОС CooCox),
  2. Будет использоваться распределитель памяти TLSF (можно использовать и другие распределители, если я сочту их более подходящими, и они будут бесплатными). и с открытым исходным кодом),
  3. Решение, которое я ищу, - это использование распределителя памяти, защищенного от задач и прерываний ОС.

До сих пор думал о двух возможных подходах, у обоих есть несколько неизвестных мне подробностей:

  1. отключать и разрешать прерывания при вызове функций распределителя. Проблема - если я не ошибаюсь, я не могу играть с отключением и включением прерываний в обычном режиме, только в привилегированном режиме (так что, если я не ошибаюсь, это только в прерываниях), мне нужно сделать это также из среды выполнения - для предотвращения прерываний и переключения задач во время работы обработчика памяти.
  2. распределитель вызовов от SWI. Это еще очень непонятно для меня. 1-й - SWI такой же, как FIQ (если так, то правда ли, что код FIQ нужно писать на ассемблере - так как аллокатор написан на C). Тогда все еще есть небольшие сомнения по поводу вызова FIQ из IRQ (такой сценарий будет происходить, хотя и не часто), но, скорее всего, эта часть не вызовет проблем.

Итак, какие есть идеи о возможных решениях этой ситуации?


person Laurynas Klimavicius    schedule 07.05.2015    source источник
comment
Единственный способ, которым я смог справиться с этим, - вообще не использовать выделение непосредственно в прерываниях. Вместо этого я использую два набора из трех очередей, один набор для IRQ, один для FIQ. В каждом наборе одна очередь поставляет буферные индексы/указатели на прерывания tx, другая поставляет «пустые» буферы на прерывания rx, а третья — это очередь возврата для заполненных буферов rx и «используемых» буферов tx. Один поток управляет каждым набором очередей и запускается, когда прерывание сигнализирует семафору о том, что очереди «требуют внимания». FIQ сигнализирует своему семафору, запуская IRQ SWI, который запускается при выходе из FIQ.   -  person Martin James    schedule 07.05.2015
comment
@MartinJames: Это похоже не на Cortex-M, а на более старый ARM7/9/10/11 или Cortex-R (не уверен насчет -A). COretx-M имеет совершенно другую систему прерываний с полноценным контроллером прерываний (NVIC), который включает истинные векторы, приоритизацию (внутреннюю/внешнюю) и т. д. Этот механизм бросил бы вызов всей архитектуре.   -  person too honest for this site    schedule 07.05.2015
comment
Насколько я понимаю из короткого взгляда на код, прерывания будут заблокированы довольно долго (хотя это может иметь максимальную границу). Вы должны убедиться, что ваша система выдерживает это при всех обстоятельствах. SWI для таких не подходит (инструкции SWI на самом деле нет, она хотя бы переименована в SVC). Cortex-M имеет очень умную систему исключений; вы можете взглянуть на SVPend вместо SVC. И (как я уже говорил) у Cortex-M другая система прерываний, а не просто два вектора.   -  person too honest for this site    schedule 07.05.2015
comment
Сначала вы должны хорошо ознакомиться с системой исключений Cortex-M. Большинство вещей, которые вы указываете в своем вопросе, даже не существуют или кажутся мне бессмысленными (без обид). Вы можете начать с Cortex-M3 TRM и руководства по архитектуре, которое можно бесплатно загрузить с ARM (осторожно: есть два разных Arch-Guide). Кроме того, ваш поставщик MCU должен предоставить справочное руководство (также называемое руководством пользователя или аналогичным; это не техническое описание, но оно также может потребоваться).   -  person too honest for this site    schedule 08.05.2015


Ответы (1)


Что касается ваших предложений 1 и 2:

  1. В Cortex-M3 вы можете в любое время включать и отключать прерывания в коде уровня привилегированного через внутренние функции CMSIS __disable_irq/_enable_irq. привилегированный уровень не ограничивается режимом обработчика; Код режима потока также может выполняться на привилегированном уровне (и во многих небольших RTOS он используется по умолчанию).
  2. SWI и FIQ — это концепции устаревших архитектур ARM. Их нет в Cortex-M3.

В идеале вы не хотели бы выполнять выделение памяти в обработчике прерываний - даже если распределитель детерминирован, это все равно может занять значительное количество времени; Я могу придумать несколько причин, по которым вы хотели бы это сделать.

Наилучший подход — изменить код tlsf, чтобы использовать мьютекс ОСРВ для каждого вызова с внешней связью. Другие библиотеки, которые я использовал, уже содержат заглушки в библиотеке, которые обычно ничего не делать, но который вы можете переопределить своей собственной реализацией, чтобы сопоставить его с любой RTOS.

Теперь вы, конечно, не можете использовать мьютекс в ISR, но, как я уже сказал, вам, вероятно, не следует выделять память и там. Если вам действительно необходимо выполнить распределение в обработчике прерываний, то включение/отключение прерываний — ваш единственный вариант, но тогда вы сбиваете с толку все детерминированное поведение в реальном времени, которое обеспечивает RTOS. Лучшим решением для этого является то, чтобы ваш ISR не делал ничего, кроме выдачи флага события или семафора обработчику контекста потока. Это позволяет использовать все службы RTOS и планирование, а время переключения контекста с ISR на поток с высоким приоритетом будет незначительным по сравнению со временем выделения памяти.

Другой возможностью было бы вообще не использовать этот распределитель, а вместо этого использовать распределитель с фиксированным блоком, использующий очереди RTOS. Вы предварительно выделяете блоки памяти (статически или динамически), отправляете указатели на начало каждого блока в очередь, затем для выделения вы просто получаете указатель из очереди, а для освобождения отправляете обратно в очередь. Если память исчерпана (очередь пуста), вы можете отказаться или заблокировать очередь (но не блокировать в ISR). Вы можете создать несколько очередей для блоков разного размера и использовать ту, которая соответствует вашим потребностям (конечно, при условии, что вы отправляете сообщения обратно в одну и ту же очередь!)

person Clifford    schedule 07.05.2015
comment
Инструкции для __enable/disable_irq защищены по уважительной причине. Они недоступны из непривилегированного режима. Выделение памяти из обработчика прерываний является обычной практикой и упрощает проектирование системы. Это особенно при использовании уровней приоритета. Однако вы правы, распределитель не должен занимать больше заданного времени. Фактическое время зависит от приемлемого времени прерывания и задержки. В любом случае стандартные распределители в большинстве случаев не подходят для этого. - person too honest for this site; 08.05.2015
comment
Что касается __enable_irq - моя ошибка, в простых средах RTOS обычно все программное обеспечение работает в привилегированном режиме - поэтому я никогда не сталкивался с ограничением на своих проектах Cortex-M3, но в любом случае, пока режим обработчика всегда является привилегированным, режим потока может работать как на привилегированном, так и на непривилегированном уровне. Что касается распределения памяти в ISR, обычная практика где!? Нет, если он по своей сути не является потокобезопасным или блокируется механизм безопасности потоков. В какой среде встроенных систем реального времени вы видите такую ​​распространенную практику? - person Clifford; 08.05.2015
comment
Зависит от. Я согласен, что стандартный malloc&co не используется - по причинам, которые вы указали. Однако в небольших встроенных системах, таких как системы на базе Cortex-M, они в любом случае не являются хорошим выбором. На самом деле довольно часто для получения/освобождения буферов и т. д. используется специальный распределитель блоков. - person too honest for this site; 08.05.2015
comment
Что касается защиты: это зависит. Я сейчас работаю на такой системе. Не совсем просто заблокировать систему только с 8 регионами, но это работает при правильном подходе. О, и мой распределитель безопасен для потоков (и прерываний). И неблокирующий. Но (на несколько часов) блокировка. - person too honest for this site; 08.05.2015
comment
@Olaf Действительно; Я кратко описываю такой распределитель блоков в своем ответе. Я бы не назвал это динамическим выделением памяти в том смысле, что оно не включает управление кучей, а скорее пулом. - person Clifford; 08.05.2015
comment
Использование предварительно выделенных пулов памяти в качестве псевдодинамического распределителя в моем случае не вариант (по моему опыту, этот способ обычно добавляет большие накладные расходы, когда вам нужна возможность получить память, которая велика по сравнению со всем размером пула). А для SWI, если я не ошибаюсь, он называется SVC, ну по крайней мере SVC вызывается по инструкции SWI. - person Laurynas Klimavicius; 08.05.2015
comment
И об использовании в IRQ. Этого я хочу из-за возможности упростить код, и он будет использоваться только в нескольких прерываниях (пока я думаю только о периферийном TX DMA irq - поэтому после завершения tx IRQ может удалить пул памяти, из которого он отправлялся). Также я узнал, что есть 2-й способ отключить IRQ - через BASEPRI, который отключит только прерывания с более низким приоритетом, чем запрошено. Поэтому я думаю, что с ним можно было бы отключить только прерывания, которые могут использовать alocator и systick (чтобы ОС не переключала контент). - person Laurynas Klimavicius; 08.05.2015
comment
@LaurynasKlimavicius ОС не должна переключать контекст на систике до тех пор, пока не будет завершен контекст прерывания, так что это не проблема. - person Clifford; 08.05.2015
comment
да. Но он может переключать контент, не находясь в IRQ. Таким образом, отключение systick и IRQ, которые могут использовать alocator, сделало бы alocator безопасным в использовании, если бы я правильно все обдумал. - person Laurynas Klimavicius; 08.05.2015
comment
@LaurynasKlimavicius: я понимаю, что вы имеете в виду - это все еще плохая идея. Если ваша система не является действительно жестким режимом реального времени, вам это сойдет с рук, но если вам нужен детерминизм времени отклика порядка микросекунд, ваше решение сломается. Это просто не универсальное решение. - person Clifford; 08.05.2015
comment
@Clifford: Назовите это пулом, но это все еще механизм динамического выделения / освобождения памяти. Как я уже писал, большинству встраиваемых систем на самом деле не требуется полноценное управление кучей с разделением/объединением блоков и т. д. Это связано с тем, что существует очень высокая вероятность фрагментации кучи, так что распределение в конечном итоге со временем перестанет работать, хотя памяти осталось достаточно. Проблема в том, что это может произойти в критический момент, когда перезапуск системы может привести к повреждению внешних компонентов. О, и я говорю не о пулах с одним размером блока, а о более общем. - person too honest for this site; 08.05.2015
comment
@Olaf: Мы можем быть в насильственном соглашении и просто спорить о неточной терминологии. Традиционная куча является частью среды выполнения (и, по крайней мере, в С++ встроена в язык с помощью операторов создания/удаления по умолчанию). Если бы мне нужно было статически выделить блок памяти и поместить указатели в очередь, я бы реализовал механизм управления памятью, независимый от кучи. Итак, все, что я говорю, это то, что вы должны убедиться, что у вас есть полный контроль или знание поведения и производительности вашего механизма распределения. - person Clifford; 08.05.2015
comment
@Clifford: на самом деле я говорю о механизме распределения дневных блоков. Очень похоже на кучу, но без алгоритма фрагментации/слияния. Да, у него может быть что-то вроде пула, так как он управляет блоками фиксированного размера. Однако он используется как полноценная замена классическому аллокатору (с некоторыми ограничениями на максимальный размер). С другим аспектом я действительно согласен. Но на самом деле ТО хотел использовать свой собственный алгоритм. Я только что мельком взглянул, но, кажется, нет неопределённых циклов, так что, возможно, можно сохранить ограничения в реальном времени. Просто для освобождения блоков может быть достаточно отложенного обработчика int. - person too honest for this site; 08.05.2015
comment
@Clifford: Проблема в том, что во встроенной системе с «голым железом» на самом деле нет стандартного алгоритма кучи. Вот почему FreeRTOS, например, предоставляет четыре разных реализации (ни одна из которых не подходила для моих нужд, поэтому мне пришлось использовать две собственные параллельно). На самом деле это автономная среда, для которой стандарт C даже не требует существования stdlib (наряду с большинством других заголовков). (Не уверен насчет С++). Таким образом, нельзя предполагать, что у вас есть куча/бассейн/чайник/сумка/... вообще. - person too honest for this site; 08.05.2015