Введение
Концепция в компьютерном программировании
Реентерабельность (или повторный вход) – это концепция программирования, при которой функция или подпрограмма может быть прервана, а затем возобновлена до завершения её выполнения. Это означает, что функция может быть вызвана повторно до того, как она завершит предыдущее выполнение. Реентерабельный код разрабатывается таким образом, чтобы быть безопасным и предсказуемым при одновременном или быстром последовательном вызове нескольких экземпляров одной и той же функции. Компьютерная программа или подпрограмма называется реентерабельной, если несколько вызовов могут безопасно выполняться одновременно на нескольких процессорах, или если в системе с одним процессором её выполнение может быть прервано и безопасно начато новое выполнение (то есть, в неё можно "вернуться"). Прерывание может быть вызвано внутренним действием, таким как переход или вызов, или внешним действием, таким как прерывание или сигнал, в отличие от рекурсии, где новые вызовы могут быть вызваны только внутренним вызовом. Это определение возникло в многозадачных средах, где несколько процессов могут быть активны одновременно, и где поток управления может быть прерван прерыванием и передан в подпрограмму обработки прерывания (ISR) или "обработчик". Любая подпрограмма, используемая обработчиком, которая потенциально могла выполняться в момент возникновения прерывания, должна быть реентерабельной. Аналогично, код, совместно используемый двумя процессорами для доступа к общим данным, должен быть реентерабельным. Часто подпрограммы, доступные через ядро операционной системы, не являются реентерабельными. Следовательно, подпрограммы обработки прерываний ограничены в выполняемых действиях; например, им обычно запрещен доступ к файловой системе и иногда даже выделение памяти. Реентерабельность не является ни необходимой, ни достаточной для потокобезопасности в многопоточных средах. Другими словами, реентерабельная подпрограмма может быть потокобезопасной, но, наоборот, потокобезопасный код не обязательно должен быть реентерабельным (см. примеры ниже). Другие термины, используемые для реентерабельных программ, включают "совместно используемый код". Реентерабельные подпрограммы иногда помечаются в справочных материалах как "безопасные для сигналов". Реентерабельные программы часто являются "чистыми процедурами".
Предыстория
Повторное вхождение – это не то же самое, что идемпотентность, когда функция может быть вызвана несколько раз, но при этом генерирует точно такой же результат, как если бы она была вызвана только один раз. В общем случае функция производит выходные данные на основе каких-либо входных данных (хотя оба варианта необязательны). Доступ к общим данным может осуществляться любой функцией в любое время. Если данные могут быть изменены любой функцией (и никто не отслеживает эти изменения), то нет гарантии для тех, кто использует эти данные, что они останутся неизменными. Данные обладают характеристикой, называемой областью видимости, которая определяет, где в программе эти данные могут быть использованы. Область видимости данных может быть глобальной (вне области действия любой функции и с неопределенным временем жизни) или локальной (создается каждый раз при вызове функции и уничтожается при ее завершении). Локальные данные не передаются между функциями, независимо от того, являются они повторно входящими или нет; следовательно, они не влияют на повторное вхождение. Глобальные данные определяются вне функций и могут быть доступны нескольким функциям, либо в виде глобальных переменных (данные, общие для всех функций), либо в виде статических переменных (данные, общие для всех вызовов одной и той же функции). В объектно-ориентированном программировании глобальные данные определяются в области видимости класса и могут быть приватными, что делает их доступными только функциям этого класса. Существует также понятие переменных экземпляра, когда переменная класса привязана к экземпляру класса. По этим причинам в объектно-ориентированном программировании это различие обычно применяется к данным, доступным вне класса (публичным), и к данным, независимым от экземпляров класса (статическим). Повторное вхождение отличается от потокобезопасности, но тесно с ней связано. Функция может быть потокобезопасной, но при этом не быть повторно входящей. Например, функция может быть обернута мьютексом (что позволяет избежать проблем в многопоточной среде), но если эта функция используется в обработчике прерываний, она может оказаться в состоянии ожидания, пока первое выполнение не освободит мьютекс. Ключ к избежанию путаницы заключается в том, что повторное вхождение относится только к одному потоку выполнения. Это концепция, возникшая во времена, когда не существовало многозадачных операционных систем.
Правила повторного въезда
Код, допускающий повторный вход, не может содержать статические или глобальные неконстантные данные без синхронизации. Функции, допускающие повторный вход, могут работать с глобальными данными. Например, подпрограмма обработки прерывания, допускающая повторный вход, может захватить часть состояния оборудования для работы (например, буфер чтения последовательного порта), который является не только глобальным, но и изменчивым. Тем не менее, типичное использование статических переменных и глобальных данных не рекомендуется, в том смысле, что, за исключением участков кода, которые синхронизированы, в этих переменных должны использоваться только атомарные инструкции чтения-модификации-записи (не должно быть возможности возникновения прерывания или сигнала во время выполнения такой инструкции). Следует отметить, что в C даже чтение или запись не гарантируется атомарным; оно может быть разделено на несколько операций чтения или записи. Стандарт C и SUSv3 предоставляют тип `sig_atomic_t` для этой цели, хотя и с гарантиями только для простых операций чтения и записи, а не для инкремента или декремента. Более сложные атомарные операции доступны в C11, который предоставляет `stdatomic.h`.
Reentrant code may not modify itself without synchronization. The operating system might allow a process to modify its code. There are various reasons for this (e. g., blitting graphics quickly) but this generally requires synchronization to avoid problems with reentrancy.<p>It may, however, modify itself if it resides in its own unique memory. That is, if each new invocation uses a different physical machine code location where a copy of the original code is made, it will not affect other invocations even if it modifies itself during execution of that particular invocation (thread). Reentrant code may not call non reentrant computer programs or routines without synchronization. Multiple levels of user, object, or process priority or multiprocessing usually complicate the control of reentrant code. It is important to keep track of any access or side effects that are done inside a routine designed to be reentrant. Reentrancy of a subroutine that operates on operating system resources or non local data depends on the atomicity of the respective operations. For example, if the subroutine modifies a 64 bit global variable on a 32 bit machine, the operation may be split into two 32 bit operations, and thus, if the subroutine is interrupted while executing, and called again from the interrupt handler, the global variable may be in a state where only 32 bits have been updated. The programming language might provide atomicity guarantees for interruption caused by an internal action such as a jump or call. Then the function in an expression like (global:=1) + (f ), where the order of evaluation of the subexpressions might be arbitrary in a programming language, would see the global variable either set to 1 or to its previous value, but not in an intermediate state where only part has been updated. (The latter can happen in C, because the expression has no sequence point.) The operating system might provide atomicity guarantees for signals, such as a system call interrupted by a signal not having a partial effect. The processor hardware might provide atomicity guarantees for interrupts, such as interrupted processor instructions not having partial effects.
Код, допускающий повторный вход, не может изменять сам себя без синхронизации. Операционная система может разрешить процессу изменять свой код. Существуют различные причины для этого (например, быстрое копирование графики), но это обычно требует синхронизации, чтобы избежать проблем с повторным входом. Однако он может изменять себя, если он находится в своей собственной уникальной области памяти. То есть, если каждый новый вызов использует другое физическое местоположение машинного кода, где создается копия исходного кода, это не повлияет на другие вызовы, даже если он изменяется во время выполнения данного конкретного вызова (потока). Код, допускающий повторный вход, не может вызывать нереинтерентные компьютерные программы или подпрограммы без синхронизации. Множественные уровни приоритетов пользователя, объекта или процесса или многопроцессорность обычно усложняют контроль за кодом, допускающим повторный вход. Важно отслеживать любые побочные эффекты, которые происходят внутри подпрограммы, предназначенной для повторного входа. Повторный вход подпрограммы, которая работает с ресурсами операционной системы или нелокальными данными, зависит от атомарности соответствующих операций. Например, если подпрограмма изменяет 64-битную глобальную переменную на 32-битной машине, операция может быть разделена на две 32-битные операции, и, следовательно, если подпрограмма прерывается во время выполнения и вызывается снова из обработчика прерываний, глобальная переменная может находиться в состоянии, когда обновлено только 32 бита. Язык программирования может предоставлять гарантии атомарности для прерываний, вызванных внутренним действием, таким как переход или вызов. Тогда функция в выражении вроде `(global := 1) + (f)`, где порядок вычисления подвыражений может быть произвольным в языке программирования, увидит глобальную переменную либо установленной в 1, либо в ее предыдущее значение, но не в промежуточном состоянии, где обновлена только часть (последнее может произойти в C, потому что выражение не имеет точки последовательности). Операционная система может предоставлять гарантии атомарности для сигналов, например, системный вызов, прерванный сигналом, не имеющий частичного эффекта. Аппаратное обеспечение процессора может предоставлять гарантии атомарности для прерываний, например, прерванные инструкции процессора не имеют частичного эффекта.
Reentrant code may not modify itself without synchronization. The operating system might allow a process to modify its code. There are various reasons for this (e. g., blitting graphics quickly) but this generally requires synchronization to avoid problems with reentrancy.<p>It may, however, modify itself if it resides in its own unique memory. That is, if each new invocation uses a different physical machine code location where a copy of the original code is made, it will not affect other invocations even if it modifies itself during execution of that particular invocation (thread). Reentrant code may not call non reentrant computer programs or routines without synchronization. Multiple levels of user, object, or process priority or multiprocessing usually complicate the control of reentrant code. It is important to keep track of any access or side effects that are done inside a routine designed to be reentrant. Reentrancy of a subroutine that operates on operating system resources or non local data depends on the atomicity of the respective operations. For example, if the subroutine modifies a 64 bit global variable on a 32 bit machine, the operation may be split into two 32 bit operations, and thus, if the subroutine is interrupted while executing, and called again from the interrupt handler, the global variable may be in a state where only 32 bits have been updated. The programming language might provide atomicity guarantees for interruption caused by an internal action such as a jump or call. Then the function in an expression like (global:=1) + (f ), where the order of evaluation of the subexpressions might be arbitrary in a programming language, would see the global variable either set to 1 or to its previous value, but not in an intermediate state where only part has been updated. (The latter can happen in C, because the expression has no sequence point.) The operating system might provide atomicity guarantees for signals, such as a system call interrupted by a signal not having a partial effect. The processor hardware might provide atomicity guarantees for interrupts, such as interrupted processor instructions not having partial effects.
Примеры
Для иллюстрации повторного входа, в этой статье в качестве примера используется C-функция, которая принимает два указателя и меняет их значения местами, а также подпрограмма обработки прерываний, которая также вызывает функцию swap.
Управляющий перерывом с возвратом
Реинтеррантный обработчик прерываний – это обработчик прерываний, который повторно включает прерывания на ранних этапах работы обработчика. Это может снизить задержку обработки прерываний. В целом, при программировании подпрограмм обслуживания прерываний рекомендуется как можно скорее повторно включать прерывания в обработчике прерываний. Это помогает избежать потери прерываний.