stringtranslate.com

Reentrada (informática)

La reentrada es un concepto de programación en el que una función o subrutina se puede interrumpir y luego reanudar antes de que termine de ejecutarse. Esto significa que se puede volver a llamar a la función antes de que complete su ejecución anterior. El código reentrante está diseñado para ser seguro y predecible cuando se llaman varias instancias de la misma función simultáneamente o en rápida sucesión. Un programa de computadora o subrutina se llama reentrante si se pueden ejecutar múltiples invocaciones de manera segura al mismo tiempo en múltiples procesadores , o si en un sistema de un solo procesador su ejecución se puede interrumpir y se puede iniciar de manera segura una nueva ejecución (se puede "reentrar" "). La interrupción podría ser causada por una acción interna como un salto o una llamada, o por una acción externa como una interrupción o una señal , a diferencia de la recursividad , donde las nuevas invocaciones solo pueden ser causadas por una llamada interna.

Esta definición se origina en entornos de multiprogramación , donde múltiples procesos pueden estar activos simultáneamente y donde el flujo de control podría ser interrumpido por una interrupción y transferido a una rutina de servicio de interrupción (ISR) o subrutina "controladora". Cualquier subrutina utilizada por el controlador que podría haberse estado ejecutando cuando se activó la interrupción debe ser reentrante. De manera similar, el código compartido por dos procesadores que acceden a datos compartidos debería ser reentrante. A menudo, las subrutinas accesibles a través del núcleo del sistema operativo no son reentrantes. Por lo tanto, las rutinas de servicio de interrupción están limitadas en las acciones que pueden realizar; por ejemplo, generalmente tienen restringido el acceso al sistema de archivos y, a veces, incluso la asignación de memoria .

La reentrada no es necesaria ni suficiente para la seguridad de los subprocesos en entornos de subprocesos múltiples. En otras palabras, una subrutina reentrante puede ser segura para subprocesos, [1] pero no se garantiza que sea [ cita necesaria ] . Por el contrario, el código seguro para subprocesos no necesita ser reentrante (consulte los ejemplos a continuación).

Otros términos utilizados para programas reentrantes incluyen "código compartible". [2] Las subrutinas reentrantes a veces están marcadas en el material de referencia como "seguras para señales". [3] Los programas reentrantes son a menudo [un] "procedimiento puro".

Fondo

La reentrada no es lo mismo que la idempotencia , en la que la función se puede llamar más de una vez y aún así generar exactamente el mismo resultado como si solo se hubiera llamado una vez. En términos generales, una función produce datos de salida basados ​​en algunos datos de entrada (aunque ambos son opcionales, en general). Cualquier función puede acceder a los datos compartidos en cualquier momento. Si los datos se pueden cambiar mediante cualquier función (y ninguna realiza un seguimiento de esos cambios), no hay garantía para quienes comparten un dato de que ese dato sea el mismo que en cualquier momento anterior.

Los datos tienen una característica llamada alcance , que describe en qué parte de un programa se pueden utilizar los datos. El alcance de los datos es global (fuera del alcance de cualquier función y con una extensión indefinida) o local (se crea cada vez que se llama a una función y se destruye al salir).

Los datos locales no son compartidos por ninguna rutina, reingresando o no; por lo tanto, no afecta el reingreso. Los datos globales se definen fuera de las funciones y pueden acceder a ellos más de una función, ya sea en forma de variables globales (datos compartidos entre todas las funciones) o como variables estáticas (datos compartidos por todas las invocaciones de la misma función). En la programación orientada a objetos , los datos globales se definen en el ámbito de una clase y pueden ser privados, lo que los hace accesibles sólo para funciones de esa clase. También existe el concepto de variables de instancia , donde una variable de clase está vinculada a una instancia de clase. Por estas razones, en la programación orientada a objetos, esta distinción suele reservarse para los datos accesibles fuera de la clase (públicos) y para los datos independientes de las instancias de la clase (estáticos).

La reentrada es distinta de la seguridad de subprocesos , pero está estrechamente relacionada con ella . Una función puede ser segura para subprocesos y aun así no ser reentrante. Por ejemplo, una función podría estar envuelta con un mutex (lo que evita problemas en entornos de subprocesos múltiples), pero, si esa función se usara en una rutina de servicio de interrupción, podría morir esperando a que la primera ejecución libere el mutex. La clave para evitar confusiones es que reentrante se refiere a que solo se ejecuta un subproceso. Es un concepto de la época en la que no existían sistemas operativos multitarea.

Reglas para el reingreso

El código reentrante no puede contener datos estáticos o globales no constantes sin sincronización .
Las funciones reentrantes pueden funcionar con datos globales. Por ejemplo, una rutina de servicio de interrupción reentrante podría capturar un estado de hardware con el que trabajar (por ejemplo, el búfer de lectura del puerto serie) que no sólo es global, sino también volátil. Aún así, no se recomienda el uso típico de variables estáticas y datos globales, en el sentido de que, excepto en secciones de código que estén sincronizadas , en estas variables solo se deben usar instrucciones atómicas de lectura-modificación-escritura (no debería ser posible para un interrupción o señal que se producirá durante la ejecución de dicha instrucción). Tenga en cuenta que en C, ni siquiera se garantiza que una lectura o escritura sea atómica; se puede dividir en varias lecturas o escrituras. [4] El estándar C y SUSv3 prevén sig_atomic_teste propósito, aunque con garantías solo para lecturas y escrituras simples, no para incrementos o decrementos. [5] Hay operaciones atómicas más complejas disponibles en C11 , que proporciona stdatomic.h.
El código reentrante no puede modificarse sin sincronización.
El sistema operativo podría permitir que un proceso modifique su código. Hay varias razones para esto (por ejemplo, borrar los gráficos rápidamente), pero generalmente requiere sincronización para evitar problemas con la reentrada.

Sin embargo, puede modificarse si reside en su propia y única memoria. Es decir, si cada nueva invocación utiliza una ubicación física diferente del código de máquina donde se realiza una copia del código original, no afectará a otras invocaciones incluso si se modifica durante la ejecución de esa invocación (hilo) en particular.

El código reentrante no puede llamar a programas o rutinas informáticas no reentrantes sin sincronización.
Múltiples niveles de prioridad de usuario, objeto o proceso o multiprocesamiento generalmente complican el control del código reentrante. Es importante realizar un seguimiento de cualquier acceso o efecto secundario que se realice dentro de una rutina diseñada para ser reentrante.

La reentrada de una subrutina que opera con recursos del sistema operativo o datos no locales depende de la atomicidad de las operaciones respectivas. Por ejemplo, si la subrutina modifica una variable global de 64 bits en una máquina de 32 bits, la operación puede dividirse en dos operaciones de 32 bits y, por lo tanto, si la subrutina se interrumpe mientras se ejecuta y se vuelve a llamar desde el controlador de interrupciones , la variable global puede estar en un estado en el que solo se han actualizado 32 bits. El lenguaje de programación puede proporcionar garantías de atomicidad para la interrupción causada por una acción interna como un salto o una llamada. Entonces, la función fen una expresión como (global:=1) + (f()), donde el orden de evaluación de las subexpresiones podría ser arbitraria en un lenguaje de programación, vería la variable global establecida en 1 o en su valor anterior, pero no en un estado intermedio donde solo se ha agregado una parte. actualizado. (Esto último puede suceder en C , porque la expresión no tiene punto de secuencia ). El sistema operativo podría proporcionar garantías de atomicidad para las señales , como una llamada al sistema interrumpida por una señal que no tiene un efecto parcial. El hardware del procesador puede proporcionar garantías de atomicidad para las interrupciones , como que las instrucciones del procesador interrumpidas no tengan efectos parciales.

Ejemplos

Para ilustrar la reentrada, este artículo utiliza como ejemplo una función de utilidad de Cswap() , que toma dos punteros y transpone sus valores, y una rutina de manejo de interrupciones que también llama a la función de intercambio.

Ni reentrante ni seguro para subprocesos

Este es un ejemplo de función de intercambio que no es reentrante ni segura para subprocesos. Dado que la tmpvariable se comparte globalmente, sin sincronización, entre cualquier instancia concurrente de la función, una instancia puede interferir con los datos en los que confía otra. Como tal, no debería haberse utilizado en la rutina del servicio de interrupción isr():

int tmp ; intercambio vacío ( int * x , int * y ) { tmp = * x ; * x = * y ; /* La interrupción de hardware podría invocar isr() aquí. */ * y = tmp ; }               vacío isr () { int x = 1 , y = 2 ; intercambiar ( & x , & y ); }          

Seguro para subprocesos pero no reentrante

La función swap()del ejemplo anterior se puede hacer segura para subprocesos haciendo que sea tmp local para subprocesos . Todavía no puede ser reentrante y esto seguirá causando problemas si isr()se llama en el mismo contexto que un hilo que ya se está ejecutando swap():

_Thread_local int tmp ;  intercambio vacío ( int * x , int * y ) { tmp = * x ; * x = * y ; /* La interrupción de hardware podría invocar isr() aquí. */ * y = tmp ; }               vacío isr () { int x = 1 , y = 2 ; intercambiar ( & x , & y ); }          

Reentrante y seguro para subprocesos

Una implementación swap()que asigna tmpen la pila en lugar de globalmente y que se llama solo con variables no compartidas como parámetros [b] es segura para subprocesos y reentrante. Es seguro para subprocesos porque la pila es local para un subproceso y una función que actúa solo sobre datos locales siempre producirá el resultado esperado. No hay acceso a datos compartidos, por lo tanto no hay carrera de datos.

intercambio vacío ( int * x , int * y ) { int tmp ; tmp = * x ; * x = * y ; * y = tmp ; /* La interrupción de hardware podría invocar isr() aquí. */ }                vacío isr () { int x = 1 , y = 2 ; intercambiar ( & x , & y ); }          

Manejador de interrupciones reentrantes

Un controlador de interrupciones reentrante es un controlador de interrupciones que vuelve a habilitar las interrupciones en las primeras etapas del controlador de interrupciones. Esto puede reducir la latencia de interrupción . [6] En general, al programar rutinas de servicio de interrupciones, se recomienda volver a habilitar las interrupciones lo antes posible en el controlador de interrupciones. Esta práctica ayuda a evitar perder interrupciones. [7]

Más ejemplos

En el siguiente código, ni las funciones fni gson reentrantes.

int v = 1 ;   intf ( ) { v += 2 ; volver v ; }      int g () { return f () + 2 ; }     

En lo anterior, f()depende de una variable global no constante v; por lo tanto, si f()es interrumpido durante la ejecución por un ISR que modifica v, el reingreso f()devolverá el valor incorrecto de v. El valor de vy, por lo tanto, el valor de retorno de f, no se pueden predecir con confianza: variarán dependiendo de si se modificó una interrupción vdurante fla ejecución de . Por tanto, fno es reentrante. Tampoco lo es g, porque llama a f, que no es reentrante.

Estas versiones ligeramente modificadas son reentrantes:

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

A continuación, la función es segura para subprocesos, pero no (necesariamente) reentrante:

función int () { mutex_lock ();   // ... // cuerpo de la función // ...   mutex_unlock (); }

En lo anterior, function()pueden ser llamados por diferentes hilos sin ningún problema. Pero, si la función se usa en un controlador de interrupciones reentrantes y surge una segunda interrupción dentro de la función, la segunda rutina se bloqueará para siempre. Como el servicio de interrupciones puede desactivar otras interrupciones, todo el sistema podría verse afectado.

Notas

  1. ^ Un programa que serializa la automodificación puede ser reentrante, y un procedimiento puro que actualiza datos globales sin una serialización adecuada puede no ser reentrante.
  2. ^ Si isr() llama a swap() con una o dos variables globales como parámetros, entonces swap() no sería reentrante

Ver también

Referencias

  1. ^ Kerrisk 2010, pag. 657.
  2. ^ Ralston 2000, pag. 1514-1515.
  3. ^ "pthread_cond_init()--Inicializar variable de condición". Centro de conocimiento de IBM . Consultado el 5 de octubre de 2019 .
  4. ^ Preshing, Jeff (18 de junio de 2013). "Operaciones atómicas versus no atómicas". Presionando la programación . Archivado desde el original el 3 de diciembre de 2014 . Consultado el 24 de abril de 2018 .
  5. ^ Kerrisk 2010, pag. 428.
  6. ^ Sloss y col. 2004, pág. 342.
  7. ^ Regehr, John (2006). "Uso seguro y estructurado de interrupciones en software integrado y en tiempo real" (PDF) . Manual de sistemas integrados y en tiempo real . Prensa CRC . Archivado (PDF) desde el original el 24 de agosto de 2007, a través del sitio web del autor en la Escuela de Computación de la Universidad de Utah.

Trabajos citados

Otras lecturas