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

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

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

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

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

Фон

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

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

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

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

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

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

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

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

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

Примеры

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

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

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

int tmp;

void swap(int* x, int* y)
{
    tmp = *x;
    *x = *y;
    /* Hardware interrupt might invoke isr() here. */
    *y = tmp;    
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

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

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

_Thread_local int tmp;

void swap(int* x, int* y)
{
    tmp = *x;
    *x = *y;
    /* Hardware interrupt might invoke isr() here. */
    *y = tmp;    
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

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

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

int tmp;

void swap(int* x, int* y)
{
    /* Save global variable. */
    int s;
    s = tmp;

    tmp = *x;
    *x = *y;      /*If hardware interrupt occurs here then it will fail to keep the value of tmp. So this is also not a reentrant example*/
    *y = tmp;     /* Hardware interrupt might invoke isr() here. */

    /* Restore global variable. */
    tmp = s;
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

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

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

void swap(int* x, int* y)
{
    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;    /* Hardware interrupt might invoke isr() here. */
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

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

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

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

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

int v = 1;

int f()
{
    v += 2;
    return v;
}

int g()
{
    return f() + 2;
}

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

Эти слегка измененные версии являются реентерабельными:

int f(int i)
{
    return i + 2;
}

int g(int i)
{
    return f(i) + 2;
}

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

int function()
{
    mutex_lock();

    // ...
    // function body
    // ...

    mutex_unlock();
}

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

Примечания

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

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

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

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