Articulo de referencia

Reentrada (informática)

En programación, la reentrada es la propiedad de una función o subrutina que permite interrumpirla y reanudarla antes de que finalice su ejecución. Esto significa que la función...

En programación, la reentrada es la propiedad de una función o subrutina que permite interrumpirla y reanudarla antes de que finalice su ejecución. Esto significa que la función puede volver a llamarse 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 o subrutina se denomina reentrante si varias invocaciones pueden ejecutarse de forma segura simultáneamente en varios procesadores , o si en un sistema de un solo procesador su ejecución puede interrumpirse y se puede iniciar una nueva ejecución de forma segura (se puede "reingresar"). La interrupción puede ser causada por una acción interna, como un salto o una llamada (que podría ser una llamada recursiva ; reingresar a una función es una generalización de la recursión), o por una acción externa, como una interrupción o una señal .

Esta definición proviene de entornos de multiprogramación , donde varios procesos pueden estar activos simultáneamente y donde el flujo de control puede ser interrumpido por una interrupción y transferido a una rutina de servicio de interrupción (ISR) o subrutina "controladora". Cualquier subrutina utilizada por la controladora que potencialmente podría haber estado ejecutándose cuando se activó la interrupción debe ser reentrante. De manera similar, el código compartido por dos procesadores que acceden a datos compartidos debe 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 tienen limitaciones 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 ni necesaria ni suficiente para la seguridad de subprocesos en entornos multihilo. En otras palabras, una subrutina reentrante puede ser segura para subprocesos, [ 1 ] pero no se garantiza que lo sea. [ 2 ] Por el contrario, el código seguro para subprocesos no tiene por qué ser reentrante (véanse los ejemplos a continuación).

Otros términos utilizados para programas reentrantes incluyen "código compartible". [ 3 ] Las subrutinas reentrantes a veces se marcan en el material de referencia como "seguras para señales". [ 4 ] Los programas reentrantes suelen ser [ a ] ​​"procedimientos puros".

Fondo

La reentrada no es lo mismo que la idempotencia , en la que una función puede llamarse más de una vez y generar exactamente el mismo resultado que si se hubiera llamado solo una vez. En general, una función produce datos de salida a partir de datos de entrada (aunque ambos son opcionales). Cualquier función puede acceder a los datos compartidos en cualquier momento. Si cualquier función puede modificar los datos (y ninguna registra esos cambios), no hay garantía para quienes comparten un dato de que este sea el mismo que en cualquier momento anterior.

Los datos tienen una característica llamada ámbito , que describe dónde se pueden usar en un programa. El ámbito de los datos puede ser global (fuera del ámbito de cualquier función y de extensión indefinida) o local (se crea cada vez que se llama a una función y se destruye al finalizar).

Los datos locales no son compartidos por ninguna rutina, ni siquiera al reingresar; por lo tanto, no afectan el reingreso. Los datos globales se definen fuera de las funciones y pueden ser accedidos por 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 dentro del ámbito de una clase y pueden ser privados, lo que los hace accesibles solo a las 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 generalmente se reserva para los datos accesibles fuera de la clase (públicos) y para los datos independientes de las instancias de 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 protegida por un mutex (lo que evita problemas en entornos multihilo), pero si se utilizara en una rutina de servicio de interrupción, podría quedarse bloqueada esperando a que la primera ejecución libere el mutex. La clave para evitar confusiones es que reentrante se refiere a la ejecución de un solo subproceso. Es un concepto que data de la época en que no existían sistemas operativos multitarea.

Reglas para la reentrada

El código reentrante no puede contener datos estáticos o globales no constantes sin sincronización .
Las funciones reentrantes pueden trabajar con datos globales. Por ejemplo, una rutina de servicio de interrupción reentrante podría obtener un estado de hardware para trabajar con él (por ejemplo, el búfer de lectura del puerto serie), que no solo es global, sino también volátil. Sin embargo, 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 , solo se deben usar instrucciones atómicas de lectura-modificación-escritura en estas variables (no debería ser posible que llegue una interrupción o señal durante la ejecución de dicha instrucción). Tenga en cuenta que en C, incluso una lectura o escritura no está garantizada para ser atómica; puede dividirse en varias lecturas o escrituras. [ 5 ] El estándar C y SUSv3 proporcionan sig_atomic_testo para este propósito, aunque con garantías solo para lecturas y escrituras simples, no para incrementos o decrementos. [ 6 ] Hay operaciones atómicas más complejas disponibles en C11 , que proporciona stdatomic.h.
El código reentrante no puede modificarse a sí mismo sin sincronización.
El sistema operativo puede permitir que un proceso modifique su código. Existen diversas razones para ello (por ejemplo, para transferir gráficos rápidamente), pero generalmente esto requiere sincronización para evitar problemas de reentrada.

Sin embargo, puede modificarse a sí mismo si reside en su propia memoria exclusiva. Es decir, si cada nueva invocación utiliza una ubicación de código máquina física diferente donde se crea 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.
Los múltiples niveles de prioridad de usuario, objeto o proceso, o el procesamiento paralelo , suelen complicar el control del código reentrante. Es importante realizar un seguimiento de cualquier acceso o efecto secundario que se produzca dentro de una rutina diseñada para ser reentrante.

La reentrada de una subrutina que opera sobre 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 durante su ejecución y se vuelve a llamar desde el manejador de interrupciones, la variable global puede estar en un estado donde 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 puede ser arbitrario 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 actualizado una parte. (Esto último puede ocurrir en C , porque la expresión no tiene un punto de secuencia ). El sistema operativo puede proporcionar garantías de atomicidad para señales , como una llamada al sistema interrumpida por una señal que no tiene un efecto parcial. El hardware del procesador podría proporcionar garantías de atomicidad para las interrupciones , como por ejemplo 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 swap.

Ni reentrante ni seguro para subprocesos

Este es un ejemplo de función de intercambio que no cumple con los requisitos de reentrada ni de seguridad 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 utilizados por otra. Por lo tanto, no debería haberse utilizado en la rutina de servicio de interrupción isr().

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

Seguro para subprocesos pero no reentrante

La función swap()del ejemplo anterior se puede hacer segura para subprocesos al hacerla tmplocal para subprocesos . Sin embargo, sigue sin ser reentrante, y esto continuará causando problemas si isr()se llama en el mismo contexto que un subproceso que ya se está ejecutando swap():

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

Reentrante y seguro para subprocesos

Una implementación swap()que asigna memoria 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 segura para subprocesos porque la pila es local a 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 condición de carrera.

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

Controlador de interrupción reentrante

Un manejador de interrupciones reentrante es aquel que vuelve a habilitar las interrupciones al inicio del manejador. Esto puede reducir la latencia de las interrupciones . [ 7 ] En general, al programar rutinas de servicio de interrupción, se recomienda volver a habilitar las interrupciones lo antes posible en el manejador. Esta práctica ayuda a evitar la pérdida de interrupciones. [ 8 ]

Otros ejemplos

En el siguiente código, ninguna fde las gfunciones es reentrante.

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

En lo anterior, f()depende de una variable global no constante v; por lo tanto, si f()se interrumpe durante la ejecución por una ISR que modifica v, entonces la reentrada en f()devolverá un valor incorrecto de v. El valor de vy, por lo tanto, el valor de retorno de f, no se puede predecir con certeza: variarán dependiendo de si una interrupción modificó vdurante fla ejecución de . Por lo 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 ) { return i + 2 ; }int g ( int i ) { return f ( i ) + 2 ; }

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

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

En el ejemplo anterior, function()la función puede ser llamada por diferentes hilos sin ningún problema. Sin embargo, si se utiliza en un manejador de interrupciones reentrante y se produce una segunda interrupción dentro de la función, la segunda rutina se bloqueará indefinidamente. Dado que el servicio de interrupciones puede deshabilitar otras interrupciones, todo el sistema podría verse afectado.

Notas

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

Véase también

Referencias

  1. Kerrisk 2010 , pág. 657 . 
  2. "Escritura de código reentrante y seguro para subprocesos" . Programación para AIX . IBM . Consultado el 12 de mayo de 2025 .
  3. Ralston 2000 , págs. 1514–1515.
  4. "pthread_cond_init()--Inicializar variable de condición" . Centro de conocimiento de IBM . Consultado el 5 de octubre de 2019 .
  5. Preshing, Jeff (18 de junio de 2013). "Operaciones atómicas frente a operaciones no atómicas" . Preshing on Programming . Recuperado el 24 de abril de 2018 .{{cite web}}: CS1 maint: servicio de archivado obsoleto ( enlace )
  6. Kerrisk 2010 , pág. 428 . 
  7. ^ Sloss y col. 2004 , pág. 342 . 
  8. Regehr, John (2006). "Uso seguro y estructurado de interrupciones en software en tiempo real y embebido" (PDF) . Manual de sistemas en tiempo real y embebidos . CRC Press . Archivado (PDF) del original el 24 de agosto de 2007 , a través del sitio web del autor en la Facultad de Informática de la Universidad de Utah.

Obras citadas

Lecturas adicionales

  • Chen, Raymond (29 de junio de 2004). "La diferencia entre seguridad de subprocesos y reentrada" . The Old New Thing . Microsoft Developer Network . Recuperado el 24 de abril de 2018 .{{cite web}}: CS1 maint: servicio de archivado obsoleto ( enlace )
  • Ganssle, Jack (15 de marzo de 2001). "Introducción a la reingreso" . Embedded.com . Recuperado el 24 de abril de 2018 .{{cite web}}: CS1 maint: servicio de archivado obsoleto ( enlace )
  • IBM (2018). "Conceptos generales de programación" (PDF) . Manual de AIX versión 7.2 . págs. 636–641 . Recuperado el 24 de abril de 2018 . 
  • Jha, Dipak (2005-01-20). "Use Reentrant Functions for Safer Signal Handling" . IBM DeveloperWorks . Recuperado el 24 de abril de 2018 .{{cite web}}: CS1 maint: servicio de archivado obsoleto ( enlace )