Реентерабельность (вычисления) - Reentrancy (computing)

В вычисление, а компьютерная программа или подпрограмма называется повторно въезжающий если несколько вызовов могут безопасно выполняться одновременно в однопроцессорной системе, где повторно входимая процедура может быть прервана в середине ее выполнения, а затем безопасно вызвана снова («повторно введена») до того, как ее предыдущие вызовы завершат выполнение. Прерывание может быть вызвано внутренним действием, таким как прыжок или вызов, или внешним действием, например, прервать или сигнал, в отличие рекурсия, где новые вызовы могут быть вызваны только внутренним вызовом.

Это определение происходит из среды мультипрограммирования, где поток управления может быть прерван прервать и переведен в процедура обслуживания прерывания (ISR) или подпрограмма "обработчик". Любая подпрограмма, используемая обработчиком, которая потенциально могла выполняться при срабатывании прерывания, должна быть реентерабельной. Часто подпрограммы, доступные через операционную систему ядро не реентерабельны. Следовательно, подпрограммы обслуживания прерываний ограничены в действиях, которые они могут выполнять; например, им обычно запрещен доступ к файловой системе, а иногда и выделение памяти.

Это определение повторного входа отличается от определения потокобезопасность в многопоточных средах. Повторяющаяся подпрограмма может обеспечить потокобезопасность,[1] но одной реентерабельности может быть недостаточно для обеспечения многопоточности во всех ситуациях. И наоборот, потокобезопасный код не обязательно должен быть реентерабельным (примеры см. Ниже).

Другие термины, используемые для реентерабельных программ, включают «чистую процедуру».[2] или «общий код».[3] Подпрограммы с повторным входом иногда отмечаются в справочных материалах как «безопасные для сигналов».[4]

Задний план

Реентерабельность - это не то же самое, что идемпотентность, в котором функция может вызываться более одного раза, но генерировать точно такой же результат, как если бы она была вызвана только один раз. Вообще говоря, функция производит выходные данные на основе некоторых входных данных (хотя оба они, как правило, необязательны). К общим данным может получить доступ любая функция в любое время. Если данные могут быть изменены с помощью какой-либо функции (и ни одна из них не отслеживает эти изменения), для тех, кто разделяет данные, нет гарантии, что эти данные такие же, как и когда-либо ранее.

Данные имеют характеристику, называемую объем, который описывает, где в программе могут использоваться данные. Объем данных либо Глобальный (вне объем любой функции и на неопределенный срок) или местный (создается каждый раз при вызове функции и уничтожается при выходе).

Локальные данные не используются ни в каких процедурах, повторно вводимых или нет; следовательно, это не влияет на повторный вход. Глобальные данные определяются вне функций и могут быть доступны более чем одной функции в виде глобальные переменные (данные используются всеми функциями) или как статические переменные (данные используются всеми одноименными функциями). В объектно-ориентированного программирования, глобальные данные определены в области действия класса и могут быть частными, что делает их доступными только для функций этого класса. Также существует понятие переменные экземпляра, где переменная класса привязана к экземпляру класса. По этим причинам в объектно-ориентированном программировании это различие обычно зарезервировано для данных, доступных вне класса (общедоступные), и для данных, не зависящих от экземпляров класса (статические).

Повторная входимость отличается от, но тесно связана с потокобезопасность. Функция может быть потокобезопасный и все еще не реентерабелен. Например, функцию можно обернуть вокруг мьютекс (что позволяет избежать проблем в многопоточных средах), но, если бы эта функция использовалась в подпрограмме обслуживания прерывания, она могла бы голодать в ожидании первого выполнения, чтобы освободить мьютекс. Ключом к недопущению путаницы является то, что реентерабельность относится только к один выполнение потока. Это концепция тех времен, когда еще не существовало многозадачных операционных систем.

Правила повторного входа

Реентерабельный код не может содержать статические или глобальные непостоянные данные.
Реентерабельные функции могут работать с глобальными данными. Например, подпрограмма обслуживания повторного прерывания может захватывать часть состояния оборудования для работы (например, буфер чтения последовательного порта), которая является не только глобальной, но и изменчивой. Тем не менее, типичное использование статических переменных и глобальных данных не рекомендуется в том смысле, что только атомный читать-изменять-писать в этих переменных должны использоваться инструкции (прерывание или сигнал не должны возникать во время выполнения такой инструкции). Обратите внимание, что в C даже чтение или запись не гарантируется атомарностью; он может быть разделен на несколько операций чтения или записи.[5] Стандарт C и SUSv3 обеспечивают sig_atomic_t для этой цели, хотя с гарантиями только для простых операций чтения и записи, но не для увеличения или уменьшения.[6] Более сложные атомарные операции доступны в C11, которая обеспечивает stdatomic.h.
Повторный код не может изменить себя.
Операционная система может позволить процессу изменять свой код. Для этого есть разные причины (например, болтовня графики), но это может вызвать проблемы с повторным входом, поскольку в следующий раз код может быть другим.
Однако он может модифицироваться, если находится в собственной уникальной памяти. То есть, если каждый новый вызов использует другое место физического машинного кода, в котором создается копия исходного кода, он не повлияет на другие вызовы, даже если он изменится во время выполнения этого конкретного вызова (потока).
Реентерабельный код не может вызывать неповторяющихся компьютерные программы или распорядки.
Несколько уровней пользователя, объекта или процесса приоритет или многопроцессорность обычно усложняют контроль повторно входимого кода. Важно отслеживать любой доступ или побочные эффекты, которые происходят в рамках процедуры, предназначенной для повторного входа.

Повторный вход подпрограммы, которая работает с ресурсами операционной системы или нелокальными данными, зависит от атомарность соответствующих операций. Например, если подпрограмма изменяет 64-битную глобальную переменную на 32-битной машине, операция может быть разделена на две 32-битные операции, и, таким образом, если подпрограмма прерывается во время выполнения и вызывается снова из обработчика прерывания , глобальная переменная может находиться в состоянии, в котором были обновлены только 32 бита. Язык программирования может предоставлять гарантии атомарности для прерывания, вызванного внутренним действием, таким как переход или вызов. Тогда функция ж в таком выражении, как (глобально: = 1) + (f ()), где порядок оценки подвыражений может быть произвольным в языке программирования, глобальная переменная будет либо установлена ​​в 1, либо в свое предыдущее значение, но не в промежуточном состоянии, когда была обновлена ​​только часть. (Последнее может произойти в C, потому что в выражении нет точка последовательности.) Операционная система может предоставлять гарантии атомарности для сигналы, например, системный вызов, прерванный сигналом, не имеющим частичного эффекта. Аппаратное обеспечение процессора может обеспечивать гарантии атомарности для прерывает, например, прерванные инструкции процессора, не имеющие частичного эффекта.

Примеры

Чтобы проиллюстрировать повторный вход, в этой статье в качестве примера используется C вспомогательная функция, своп(), который принимает два указателя и меняет их значения, а также подпрограмму обработки прерывания, которая также вызывает функцию подкачки.

Ни реентерабельность, ни потокобезопасность

Это пример функции подкачки, которая не может быть реентерабельной или поточно-ориентированной. Поскольку tmp переменная используется глобально, без сериализации, среди любых параллельных экземпляров функции, один экземпляр может мешать данным, на которые полагается другой. Таким образом, он не должен был использоваться в программе обслуживания прерывания. isr ():

int tmp;пустота своп(int* Икс, int* у){    tmp = *Икс;    *Икс = *у;    / * Аппаратное прерывание может вызвать здесь isr (). * /    *у = tmp;    }пустота isr(){    int Икс = 1, у = 2;    своп(&Икс, &у);}

Поточно-ориентированный, но не реентерабельный

Функция своп() в предыдущем примере можно сделать потокобезопасным, сделав tmp локальный поток. Он по-прежнему не может быть реентерабелен, и это будет продолжать вызывать проблемы, если isr () вызывается в том же контексте, что и поток, уже выполняющий своп():

_Thread_local int tmp;пустота своп(int* Икс, int* у){    tmp = *Икс;    *Икс = *у;    / * Аппаратное прерывание может вызвать здесь isr (). * /    *у = tmp;    }пустота isr(){    int Икс = 1, у = 2;    своп(&Икс, &у);}

Реентерабельность, но не потокобезопасная

Следующая (несколько надуманная) модификация функции подкачки, которая старается оставлять глобальные данные в согласованном состоянии на момент выхода, является реентерабельной; однако он не является потокобезопасным, поскольку не используются блокировки, его можно прервать в любой момент:

int tmp;пустота своп(int* Икс, int* у){    / * Сохраняем глобальную переменную. * /    int s;    s = tmp;    tmp = *Икс;    *Икс = *у;    *у = tmp;     / * Аппаратное прерывание может вызвать здесь isr (). * /    / * Восстанавливаем глобальную переменную. * /    tmp = s;}пустота isr(){    int Икс = 1, у = 2;    своп(&Икс, &у);}

Реентерабельность и потокобезопасность

Реализация своп() что выделяет tmp на стек вместо глобально, и это вызывается только с неразделенными переменными в качестве параметров[а] является одновременно потокобезопасным и реентерабельным. Поточно-ориентированный, потому что стек является локальным для потока, а функция, действующая только с локальными данными, всегда будет давать ожидаемый результат. Нет доступа к общим данным, поэтому нет гонки за данными.

пустота своп(int* Икс, int* у){    int tmp;    tmp = *Икс;    *Икс = *у;    *у = tmp;    / * Аппаратное прерывание может вызвать здесь isr (). * /}пустота isr(){    int Икс = 1, у = 2;    своп(&Икс, &у);}

Повторяющийся обработчик прерывания

Повторяющийся обработчик прерывания - это обработчик прерывания который повторно включает прерывания на ранней стадии обработчика прерывания. Это может уменьшить задержка прерывания.[7] В общем, при программировании подпрограмм обслуживания прерываний рекомендуется как можно скорее повторно включить прерывания в обработчике прерывания. Эта практика помогает избежать потери прерываний.[8]

Дальнейшие примеры

В следующем коде ни ж ни г функции реентерабельны.

int v = 1;int ж(){    v += 2;    вернуть v;}int г(){    вернуть ж() + 2;}

В приведенном выше описании f () зависит от непостоянной глобальной переменной v; таким образом, если f () прерывается во время выполнения ISR, который изменяет v, затем снова войдите в f () вернет неправильное значение v. Значение v и, следовательно, возвращаемое значение ж, нельзя с уверенностью предсказать: они будут варьироваться в зависимости от того, изменилось ли прерывание v в течение жисполнение. Следовательно, ж не реентерабельна. Ни то, ни другое г, потому что он вызывает ж, который не реентерабелен.

Эти слегка измененные версии находятся повторно въезжающий:

int ж(int я){    вернуть я + 2;}int г(int я){    вернуть ж(я) + 2;}

Далее функция является поточно-ориентированной, но не реентерабельной:

int функция(){    mutex_lock();    // ...    // тело функции    // ...    mutex_unlock();}

В приведенном выше описании функция () может вызываться разными потоками без каких-либо проблем. Но если функция используется в обработчике повторного прерывания и второе прерывание возникает внутри функции, вторая подпрограмма зависнет навсегда. Поскольку обслуживание прерываний может отключить другие прерывания, может пострадать вся система.

Заметки

  1. ^ Если isr ​​() вызвал swap () с одной или двумя глобальными переменными в качестве параметров, swap () не будет реентерабельным.

Смотрите также

использованная литература

  1. ^ Керриск 2010, п.657.
  2. ^ Бэррон, Дэвид Уильям (1968) [1967]. «3.2. Специальные методы». Написано в Кембридже, Великобритания. В Гилл, Стэнли (ред.). Рекурсивные методы в программировании. Компьютерные монографии Макдональда (1-е изд.). Лондон, Великобритания: Macdonald & Co. (Publishers) Ltd. п.35. SBN  356-02201-3. (viii + 64 страницы)
  3. ^ Ральстон 2000, п. 1514–1515.
  4. ^ "pthread_cond_init () - Инициализировать переменную условия". Центр знаний IBM. Получено 2019-10-05.
  5. ^ Прешинг, Джефф (18.06.2013). «Атомные и неатомные операции». Подготовка к программированию. В архиве из оригинала от 03.12.2014. Получено 2018-04-24.
  6. ^ Керриск 2010, п.428.
  7. ^ Sloss et al. 2004 г., п.342.
  8. ^ Регер, Джон (2006). «Безопасное и структурированное использование прерываний в реальном времени и встроенном ПО» (PDF). Справочник по системам реального времени и встроенным системам. CRC Press. В архиве (PDF) из оригинала от 24 августа 2007 г. - через сайт автора в Школе вычислительной техники Университета Юты.

Процитированные работы

дальнейшее чтение