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():

int tmp ; void swap ( int * x , int * y ) { tmp = * x ; * x = * y ; /* Аппаратное прерывание может вызвать 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 ; * x = * y ; /* Аппаратное прерывание может вызвать isr() здесь. */ * y = tmp ; }               void isr () { int x = 1 , y = 2 ; поменять местами ( & x , & y ); }          

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

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

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

Обработчик прерываний с повторным входом

Реентерабельный обработчик прерываний — это обработчик прерываний , который повторно включает прерывания на ранней стадии в обработчике прерываний. Это может сократить задержку прерывания . [6] В общем, при программировании процедур обслуживания прерываний рекомендуется повторно включать прерывания как можно скорее в обработчике прерываний. Такая практика помогает избежать потери прерываний. [7]

Дополнительные примеры

В следующем коде ни 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 () { mutex_lock ();   // ... // тело функции // ...   mutex_unlock (); }

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

Примечания

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

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

Ссылки

  1. ^ Керриск 2010, стр. 657.
  2. ^ Ралстон 2000, стр. 1514–1515.
  3. ^ "pthread_cond_init()--Инициализация переменной условия". Центр знаний IBM . Получено 2019-10-05 .
  4. ^ Preshing, Jeff (2013-06-18). "Атомарные против неатомарных операций". Preshing on Programming . Архивировано из оригинала 2014-12-03 . Получено 2018-04-24 .
  5. ^ Керриск 2010, стр. 428.
  6. ^ Слосс и др. 2004, стр. 342.
  7. ^ Регер, Джон (2006). «Безопасное и структурированное использование прерываний в реальном времени и встроенном программном обеспечении» (PDF) . Справочник по системам реального времени и встроенным системам . CRC Press . Архивировано (PDF) из оригинала 2007-08-24 – через веб-сайт автора в Школе вычислений Университета Юты.

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

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