Reentrada (informática)

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
Concepto en programación informática

En informática, un programa informático o subrutina se denomina reentrante si varias invocaciones pueden ejecutarse de manera segura al mismo tiempo en varios procesadores, o en un sistema de un solo procesador, donde un procedimiento de reentrada puede interrumpirse en el medio de su ejecución y luego ser llamado de nuevo de forma segura ("reingresado") antes de que sus invocaciones anteriores completen la ejecución. La interrupción puede 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 puede ser interrumpido por una interrupción y transferido a una rutina de servicio de interrupción (ISR) o "controlador" subrutina Cualquier subrutina utilizada por el controlador que potencialmente podría haberse estado ejecutando cuando se activó la interrupción debe volver a entrar. 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 kernel 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, normalmente tienen restringido el acceso al sistema de archivos y, a veces, incluso la asignación de memoria.

Esta definición de reentrada difiere de la de seguridad de subprocesos en entornos de subprocesos múltiples. Una subrutina reentrante puede lograr la seguridad de subprocesos, pero ser reentrante solo podría no ser suficiente para ser seguro para subprocesos en todas las situaciones. Por el contrario, el código seguro para subprocesos no necesariamente tiene que ser reentrante (consulte los ejemplos a continuación).

Otros términos utilizados para los programas de reingreso incluyen "código para compartir". Las subrutinas reentrantes a veces se marcan en el material de referencia como "señal segura". Los programas de reingreso suelen ser "procedimientos puros".

Antecedentes

La reentrada no es lo mismo que la idempotencia, en la que la función puede llamarse más de una vez y generar exactamente el mismo resultado que 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 pueden ser cambiados por cualquier función (y ninguno realiza un seguimiento de esos cambios), no hay garantía para aquellos que 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 usar 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 más de una función puede acceder a ellos, 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 solo para 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 suele reservarse 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 volver a entrar. Por ejemplo, una función podría envolverse completamente 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 pasar hambre esperando la primera ejecución para liberar el mutex. La clave para evitar confusiones es que la reentrada se refiere a la ejecución de solo un hilo. Es un concepto de la época en que no existían sistemas operativos multitarea.

Reglas para el reingreso

El código de referencia no puede contener datos estáticos o globales sin sincronización.
Las funciones de referencia pueden trabajar con datos globales. Por ejemplo, una rutina de servicio de interrupción reencarnante podría tomar un estado de hardware para trabajar con (por ejemplo, buffer de lectura de puerto serie) que no es sólo global, sino volátil. Sin embargo, no se aconseja el uso típico de variables estáticas y datos globales, en el sentido de que, excepto en secciones de código sincronizados, sólo se deben usar instrucciones de escritura-modificación atómica en estas variables (no debe ser posible que una interrupción o señal llegue durante la ejecución de tal instrucción). Tenga en cuenta que en C, incluso una lectura o escritura no está garantizada a ser atómico; puede dividirse en varias lecturas o escritos. El estándar C y SUSv3 proporcionan sig_atomic_t para este propósito, aunque con garantías sólo para lecturas y escritos simples, no para el aumento o el decremento. Existen operaciones atómicas más complejas en C11, que proporciona stdatomic.h.
El código de referencia no puede modificarse sin sincronización.
El sistema operativo podría permitir un proceso para modificar su código. Hay varias razones para esto (por ejemplo, gráficos blitting rápidamente) pero esto generalmente requiere sincronización para evitar problemas con la reentrancia.

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

El código de autor no puede llamar programas o rutinas de computadora no obligatorios.
Múltiples niveles de usuario, objeto o prioridad del proceso o multiprocesamiento suelen complicar el control del código reentrant. Es importante hacer un seguimiento de cualquier acceso o efectos secundarios que se hacen 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 durante la ejecución 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 podría proporcionar garantías de atomicidad para la interrupción causada por una acción interna como un salto o una llamada. Luego, la función f en una expresión como (global:=1) + (f()), donde el orden de evaluación de las subexpresiones podría 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 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 C, swap(), 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 segura para subprocesos

(feminine)

Este es un ejemplo de función de intercambio que no es reentrante ni segura para subprocesos. Dado que la variable tmp 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 de servicio de interrupción isr():

int tmp;vacío Swap()int* x, int* Sí.){} tmp = *x; *x = *Sí.; /* La interrupción del hardware puede invocar isr() aquí. */ *Sí. = tmp; }vacío isr(){} int x = 1, Sí. = 2; Swap()"x, "Sí.);}

Seguro para subprocesos pero no reentrante

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

_Thread_local int tmp;vacío Swap()int* x, int* Sí.){} tmp = *x; *x = *Sí.; /* La interrupción del hardware puede invocar isr() aquí. */ *Sí. = tmp; }vacío isr(){} int x = 1, Sí. = 2; Swap()"x, "Sí.);}

Reentrante y seguro para subprocesos

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

vacío Swap()int* x, int* Sí.){} int tmp; tmp = *x; *x = *Sí.; *Sí. = tmp; /* La interrupción del hardware puede invocar isr() aquí. */}vacío isr(){} int x = 1, Sí. = 2; Swap()"x, "Sí.);}

Manejador de interrupciones reentrantes

Un controlador de interrupciones reentrante es un controlador de interrupciones que vuelve a habilitar las interrupciones al principio del controlador de interrupciones. Esto puede reducir la latencia de interrupción. En general, al programar las 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 la pérdida de interrupciones.

Más ejemplos

En el siguiente código, ni las funciones f ni g son reentrantes.

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

En lo anterior, f() depende en una variable global no constante v; por lo tanto, si f() es interrumpido durante la ejecución por un ISR que modifica v, luego vuelve a entrar en f() devolverá el valor incorrecto de v. El valor de v y, por lo tanto, el valor de retorno de f, no se pueden predecir con confianza: lo harán varían dependiendo de si una interrupción modificó v durante f's ejecución. Por lo tanto, f no es reentrante. Tampoco 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:

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

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

Contenido relacionado

María (lenguaje de programación)

Compresión fractal

Equipo conjunto de instrucciones complejas

Más resultados...
Tamaño del texto:
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save