Реентерабельность (reentrancy)
Основной цикл вызвал функцию форматирования строки, в середине её работы пришло прерывание, и обработчик вызвал ту же функцию. Если она хранит промежуточный результат в статической переменной, второй вызов испортит первый. Функции, которые такое переживают, называют реентерабельными.
Обновлено Редакция GAW
Реентерабельность (reentrancy) — свойство функции правильно работать, если её выполнение прервано, и она вызвана снова до того, как закончился первый вызов. В микроконтроллерах так бывает постоянно: основной цикл находится внутри функции, приходит прерывание, и его обработчик вызывает ту же функцию.
Что делает функцию нереентерабельной
- Статические и глобальные переменные, в которых хранится промежуточное состояние: второй вызов перезапишет то, что не успел дочитать первый.
- Скрытое статическое состояние.
strtokсохраняет указатель на продолжение строки между вызовами (C11, разд. 7.24.5.8), и вызов из прерывания собьёт разбор в основной программе. - Возврат указателя на статический буфер — как у
asctimeиlocaltime: по разделу 7.27.3 стандарта они возвращают указатель на статический объект, который следующий вызов перезаписывает. - Вызов нереентерабельных функций — свойство передаётся вверх по цепочке.
- Работа с общей аппаратурой, например последовательность «записать адрес, прочитать данные» в паре регистров.
Стандарт C прямо не обещает реентерабельности библиотеки: в разделе 7.1.4 сказано, что функции стандартной библиотеки не обязаны быть реентерабельными и могут менять объекты со статической длительностью хранения. Для обработчиков сигналов (разд. 7.14.1.1, п. 5) стандарт ещё строже: если сигнал вызван не через abort или raise, из статических объектов можно трогать только lock-free атомарные, а в остальные — лишь присваивать значение объекту типа volatile sig_atomic_t; из библиотеки разрешены единицы функций вроде abort. Это правило о сигналах C, а не об аппаратных прерываниях микроконтроллера: стандарт их не описывает, но смысл ограничения тот же, и им полезно руководствоваться.
Библиотека на микроконтроллере
printf, malloc и функции работы с потоками используют общее состояние. Newlib, распространённая на Cortex-M, для этого держит реентерабельные обёртки системных подпрограмм с суффиксом _r — _write_r, _sbrk_r и другие, которые получают указатель на структуру состояния. Но и они реентерабельны только при правильной настройке блокировок, а динамическая память в обработчике прерывания — плохая идея в любом случае.
Реентерабельность и гонки
Нереентерабельная функция, вызванная из двух мест, — частный случай состояния гонки. Если без общего состояния не обойтись, вызовы из основного цикла защищают критической секцией, а переменные, которые разделяют с прерыванием, объявляют volatile. Правила разделения данных между прерыванием и основным циклом на AVR и Cortex-M разобраны в статье о прерываниях и конкурентности.
Частые вопросы
Можно ли вызывать printf из обработчика прерывания?
Как правило, нет. Стандарт C (разд. 7.1.4) не гарантирует реентерабельность библиотечных функций, а printf работает с общим буфером потока и может вызывать malloc. Если прерывание придёт в момент, когда основной цикл сам внутри printf, состояние потока испортится. Обработчик должен положить данные в буфер или выставить флаг, а печатать — основной цикл.
Чем реентерабельность отличается от потокобезопасности?
Потокобезопасная функция может защищать общие данные блокировкой. Но если её прервал обработчик на том же ядре и захотел ту же блокировку, он будет ждать задачу, которую сам же прервал, — взаимная блокировка. Реентерабельная функция не нуждается в блокировке вовсе: у неё нет общего изменяемого состояния. Для прерываний важна именно реентерабельность.
Как сделать функцию реентерабельной?
Хранить всё состояние в локальных переменных или в структуре, которую передаёт вызывающий; не возвращать указатели на статические буферы; не вызывать нереентерабельные функции. По этому принципу устроены _r-версии функций newlib: состояние передаётся указателем на структуру reent.
Источники
- ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — разд. 7.24.5.8: strtok сохраняет указатель на продолжение строки для следующих вызовов; разд. 7.27.3: функции преобразования времени, кроме strftime, возвращают указатель на статический объект, который могут перезаписать следующие вызовы; разд. 7.1.4, п. 4: функции стандартной библиотеки не обязаны быть реентерабельными и могут изменять объекты со статической или потоковой длительностью хранения; разд. 7.14.1.1, п. 5: если сигнал возник не в результате вызова abort или raise, поведение не определено, если обработчик сигнала обращается к объекту со статической или потоковой длительностью хранения, который не является lock-free атомарным, иначе чем присваиванием объекту volatile sig_atomic_t, или вызывает функции библиотеки, кроме abort, _Exit, quick_exit и signal для своего сигнала
- Red Hat newlib C Library documentation: System Calls, Reentrant covers for OS subroutines — разд. 2.2: реентерабельные обёртки системных подпрограмм _close_r, _read_r, _write_r, _sbrk_r и другие получают дополнительный аргумент — указатель на структуру состояния; разд. 3.24: malloc требует системной подпрограммы sbrk, а функции с суффиксом _r принимают указатель reent
Нашли ошибку?
Смотрите также
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- Состояние гонкиОшибка, зависящая от момента срабатывания прерывания
- Критическая секцияУчасток кода под запретом прерываний
- ПрерываниеЗапрос периферии, вектор и обработчик прерывания
- Куча (heap)ОЗУ для malloc между .bss и стеком
- volatileКвалификатор C для регистров и переменных прерываний