Реентерабельность — это концепция программирования, при которой функция или подпрограмма может быть прервана, а затем возобновлена до завершения ее выполнения. Это означает, что функцию можно вызвать еще раз, прежде чем она завершит предыдущее выполнение. Реентерабельный код спроектирован так, чтобы быть безопасным и предсказуемым, когда несколько экземпляров одной и той же функции вызываются одновременно или в быстрой последовательности. Компьютерная программа или подпрограмма называется реентерабельной, если несколько ее вызовов могут безопасно выполняться одновременно на нескольких процессорах или если в однопроцессорной системе ее выполнение может быть прервано и безопасно запущено новое выполнение (ее можно «ввести заново»). "). Прерывание может быть вызвано внутренним действием, таким как переход или вызов, или внешним действием, таким как прерывание или сигнал , в отличие от рекурсии , где новые вызовы могут быть вызваны только внутренним вызовом.
Это определение происходит из мультипрограммных сред, где несколько процессов могут быть активны одновременно и где поток управления может быть прерван прерыванием и передан подпрограмме обслуживания прерываний (ISR) или подпрограмме «обработчика». Любая подпрограмма, используемая обработчиком, которая потенциально могла выполняться при срабатывании прерывания, должна быть повторно входимой. Аналогично, код, общий для двух процессоров, осуществляющих доступ к общим данным, должен быть реентерабельным. Часто подпрограммы, доступные через ядро операционной системы, не являются реентерабельными. Следовательно, подпрограммы обслуживания прерываний ограничены в действиях, которые они могут выполнять; например, им обычно ограничен доступ к файловой системе , а иногда даже к выделению памяти .
Повторный вход не является ни необходимым, ни достаточным для обеспечения потокобезопасности в многопоточных средах. Другими словами, реентерабельная подпрограмма может быть потокобезопасной [1] , но не гарантирована, что она будет [ нужна цитация ] . И наоборот, потокобезопасный код не обязательно должен быть реентерабельным (примеры см. ниже).
Другие термины, используемые для реентерабельных программ, включают «совместный код». [2] Реентерабельные подпрограммы иногда помечаются в справочных материалах как «сигналобезопасные». [3] Реентерабельные программы часто представляют собой «чистые процедуры».
Реентерабельность — это не то же самое, что идемпотентность , при которой функция может вызываться более одного раза, но при этом генерировать точно такой же результат, как если бы она была вызвана только один раз. Вообще говоря, функция создает выходные данные на основе некоторых входных данных (хотя, как правило, и то, и другое не является обязательным). К общим данным может получить доступ любая функция в любое время. Если данные могут быть изменены любой функцией (и ни одна из них не отслеживает эти изменения), для тех, кто использует общие данные, нет никакой гарантии, что эти данные такие же, как и в любой момент времени.
Данные имеют характеристику, называемую областью действия , которая описывает, где в программе могут использоваться данные. Область данных может быть либо глобальной (вне области действия любой функции и с неопределенным объемом), либо локальной (создается каждый раз при вызове функции и уничтожается при выходе).
Локальные данные не используются никакими подпрограммами, независимо от того, вводятся они повторно или нет; следовательно, это не влияет на повторный вход. Глобальные данные определяются вне функций и могут быть доступны более чем одной функции либо в форме глобальных переменных (данных, общих для всех функций), либо в виде статических переменных (данных, общих для всех вызовов одной и той же функции). В объектно-ориентированном программировании глобальные данные определяются в области класса и могут быть частными, что делает их доступными только для функций этого класса. Существует также концепция переменных экземпляра , когда переменная класса привязана к экземпляру класса. По этим причинам в объектно-ориентированном программировании это различие обычно зарезервировано для данных, доступных вне класса (общедоступных), и для данных, независимых от экземпляров класса (статических).
Реентерабельность отличается от потокобезопасности , но тесно связана с ней . Функция может быть потокобезопасной и при этом не реентерабельной. Например, функция может быть полностью обернута мьютексом ( что позволяет избежать проблем в многопоточных средах), но если эта функция используется в подпрограмме обслуживания прерываний, она может простоять в ожидании первого выполнения, которое освободит мьютекс. Ключом к избежанию путаницы является то, что реентерабельность означает выполнение только одного потока. Это концепция тех времен, когда еще не существовало многозадачных операционных систем.
sig_atomic_t
эту цель, хотя и с гарантиями только для простого чтения и записи, а не для увеличения или уменьшения. [5] Более сложные атомарные операции доступны в C11 , который предоставляет stdatomic.h
.Однако он может модифицировать себя, если находится в его собственной уникальной памяти. То есть, если каждый новый вызов использует другое физическое место машинного кода, где создается копия исходного кода, это не повлияет на другие вызовы, даже если оно изменится во время выполнения этого конкретного вызова (потока).
Повторный вход подпрограммы, которая работает с ресурсами операционной системы или нелокальными данными, зависит от атомарности соответствующих операций. Например, если подпрограмма изменяет 64-битную глобальную переменную на 32-битной машине, операция может быть разделена на две 32-битные операции, и, таким образом, если подпрограмма прерывается во время выполнения и вызывается снова из обработчика прерывания , глобальная переменная может находиться в состоянии, в котором были обновлены только 32 бита. Язык программирования может предоставлять гарантии атомарности для прерываний, вызванных внутренним действием, например переходом или вызовом. Тогда функция f
в выражении типа (global:=1) + (f())
, где порядок вычисления подвыражений может быть произвольным на языке программирования, увидит, что глобальная переменная либо установлена в 1, либо в ее предыдущее значение, но не в промежуточном состоянии, где была только часть. обновлено. (Последнее может произойти в C , поскольку выражение не имеет точки последовательности .) Операционная система может предоставлять гарантии атомарности для сигналов , таких как системный вызов, прерванный сигналом, не имеющим частичного эффекта. Аппаратное обеспечение процессора может обеспечивать гарантии атомарности прерываний , например прерываемые инструкции процессора, не имеющие частичных эффектов.
Чтобы проиллюстрировать повторный вход, в этой статье в качестве примера используется служебная функция Cswap()
, которая принимает два указателя и транспонирует их значения, а также процедуру обработки прерываний, которая также вызывает функцию подкачки.
Это пример функции подкачки, которая не может быть реентерабельной или потокобезопасной. Поскольку tmp
переменная является глобально общей, без синхронизации, среди любых одновременных экземпляров функции, один экземпляр может мешать данным, на которые опирается другой. По существу, его не следовало использовать в процедуре обслуживания прерываний isr()
:
ИНТ ТМП ; void swap ( int * x , int * y ) { tmp = * x ; * х = * у ; /* Здесь аппаратное прерывание может вызвать isr(). */ * y = tmp ; } void isr () { int x знак равно 1 , y знак равно 2 ; поменять местами ( & x , & y ); }
Функцию swap()
в предыдущем примере можно сделать потокобезопасной, сделав tmp
thread-local . Он по-прежнему не может быть реентерабельным, и это будет продолжать вызывать проблемы, если isr()
он вызывается в том же контексте, что и уже выполняющийся поток swap()
:
_Thread_local int tmp ; void swap ( int * x , int * y ) { tmp = * x ; * х = * у ; /* Здесь аппаратное прерывание может вызвать isr(). */ * y = tmp ; } void isr () { int x знак равно 1 , y знак равно 2 ; поменять местами ( & x , & y ); }
Реализация, swap()
которая распределяет ресурсы tmp
в стеке , а не глобально, и которая вызывается только с неразделяемыми переменными в качестве параметров [b], является одновременно потокобезопасной и реентерабельной. Потокобезопасен, поскольку стек является локальным по отношению к потоку, а функция, действующая только на локальные данные, всегда будет давать ожидаемый результат. Нет доступа к общим данным, поэтому нет гонки за данными.
void swap ( int * x , int * y ) { int tmp ; ТМП = * х ; * х = * у ; * у = ТМП ; /* Здесь аппаратное прерывание может вызвать isr(). */ } void isr () { int x знак равно 1 , y знак равно 2 ; поменять местами ( & x , & y ); }
Обработчик повторного прерывания — это обработчик прерываний , который повторно разрешает прерывания на ранних этапах обработчика прерываний. Это может уменьшить задержку прерывания . [6] В общем, при программировании процедур обслуживания прерываний рекомендуется как можно скорее повторно разрешить прерывания в обработчике прерываний. Эта практика помогает избежать потери прерываний. [7]
В следующем коде ни одна f
из g
функций не является реентерабельной.
интервал v = 1 ; int f () { v += 2 ; вернуть v ; } int g () { return f () + 2 ; }
В приведенном выше примере f()
зависит от непостоянной глобальной переменной v
; таким образом, если f()
во время выполнения прерывается ISR, который изменяет v
, то повторный вход f()
вернет неправильное значение v
. Значение v
и, следовательно, возвращаемое значение f
невозможно предсказать с уверенностью: они будут меняться в зависимости от того, было ли изменено прерывание v
во время f
выполнения. Следовательно, f
не является реентерабельным. И это не так g
, потому что он вызывает f
, который не является реентерабельным.
Эти слегка измененные версии являются реентерабельными:
int f ( int я ) { возвращение я + 2 ; } int g ( int я ) { возвращение ж ( я ) + 2 ; }
Ниже функция является потокобезопасной, но не (обязательно) реентерабельной:
int function () { mutex_lock (); // ... // тело функции // ... мьютекс_разблокировка (); }
В приведенном выше примере function()
можно без проблем вызывать разные потоки. Но если функция используется в обработчике реентерабельного прерывания и внутри функции возникает второе прерывание, вторая подпрограмма зависнет навсегда. Поскольку обслуживание прерываний может отключить другие прерывания, может пострадать вся система.