stringtranslate.com

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

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

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

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

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

Фон

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

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

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

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

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

Реентерабельный код не может содержать статические или глобальные непостоянные данные без синхронизации .
Реентерабельные функции могут работать с глобальными данными. Например, процедура обслуживания реентерабельного прерывания может получить часть состояния оборудования для работы (например, буфер чтения последовательного порта), которая не только глобальна, но и нестабильна. Тем не менее, типичное использование статических переменных и глобальных данных не рекомендуется, в том смысле, что, за исключением синхронизированных разделов кода , в этих переменных следует использовать только атомарные инструкции чтения-изменения-записи (это не должно быть возможно для прерывание или сигнал, который должен прийти во время выполнения такой инструкции). Обратите внимание, что в C даже чтение или запись не гарантированно являются атомарными; его можно разделить на несколько операций чтения или записи. [4] Стандарт C и SUSv3 предусматривают 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()можно без проблем вызывать разные потоки. Но если функция используется в обработчике реентерабельного прерывания и внутри функции возникает второе прерывание, вторая подпрограмма зависнет навсегда. Поскольку обслуживание прерываний может отключить другие прерывания, может пострадать вся система.

Примечания

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

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

Рекомендации

  1. ^ Керриск 2010, с. 657.
  2. ^ Ралстон 2000, с. 1514–1515.
  3. ^ «pthread_cond_init() — инициализировать переменную условия». Центр знаний IBM . Проверено 5 октября 2019 г.
  4. ^ Прешинг, Джефф (18 июня 2013 г.). «Атомарные и неатомарные операции». Прешинг по программированию . Архивировано из оригинала 3 декабря 2014 г. Проверено 24 апреля 2018 г.
  5. ^ Керриск 2010, с. 428.
  6. ^ Слосс и др. 2004, с. 342.
  7. ^ Регер, Джон (2006). «Безопасное и структурированное использование прерываний в реальном времени и встроенном программном обеспечении» (PDF) . Справочник по системам реального времени и встроенным системам . ЦРК Пресс . Архивировано (PDF) из оригинала 24 августа 2007 г. - на веб-сайте автора в Школе вычислительной техники Университета Юты.

Цитируемые работы

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