Reentrancia (informática)

De Wikipedia, la enciclopedia libre

En informática, un programa informático o subrutina se llama reentrante si puede ser interrumpido en medio de su ejecución y volver a llamarse de forma segura ("re-entrar") antes de que las invocaciones anteriores completen su ejecución. La interrupción puede ser causada por una acción interna como un salto o llamada, o por una acción externa como una interrupción o señal. Una vez que la invocación re-entrante completa, las invocaciones anteriores reanudarán su ejecución de forma correcta.

Esta definición se origina en los entornos de un único subproceso de programación en el que el flujo de control podría ser interrumpido por una alarma de proceso y se transfiere a una rutina de servicio de interrupción (ISR). Cualquier subrutina utilizada por el ISR que podría potencialmente se han ejecutado cuando la alarma se activó debe ser de reentrada. A menudo, las subrutinas accesibles a través del sistema operativo del kernel no son reentrantes. Por lo tanto, las rutinas de servicio de interrupción son limitados en las acciones que pueden realizar; por ejemplo, que suelen limitarse el acceso al sistema de archivos y, a veces incluso de asignar memoria.

Una subrutina que es directa o indirectamente recursiva debe ser de reentrada. Esta directiva se aplica parcialmente por los Lenguajes de programación estructurados. Sin embargo una subrutina puede dejar de ser reentrante si se basa en una variable global a permanecer sin cambios, pero esa variable se modifica cuando se invoca recursivamente la subrutina.

Esta definición de reentrada difiere de la de hilo de seguridad en entornos multi-hilo. Una subrutina reentrada puede lograr hilo de seguridad,[1]​ pero al ser de reentrada por sí solo podría no ser suficiente para ser flujos seguros en todas las situaciones. Por el contrario, el código flujos seguros no necesariamente tiene que ser de reentrada (ver más abajo para los ejemplos).

Otros términos utilizados para los programas de reentrada incluyen "procedimiento puro" o "código compartible".[2]

Ejemplo[editar]

Este es un ejemplo de un swap() función que no sea reentrante (así como dejar de ser seguro para subprocesos). Como tal, no debería haber sido utilizado en el servicio de interrupción rutina isr():

int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!
    *y = t;
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

swap() podría hacerse flujos seguros al hacer t hilo local . Se sigue sin ser de reentrada, y esto seguirá causando problemas si isr() es llamado en el mismo contexto que un hilo ya la ejecución de swap().

La siguiente (un tanto artificial) la modificación de la función de intercambio, que es cuidado de dejar los datos globales en un estado coherente en el momento de que salga, es perfectamente reentrada; Sin embargo, no es seguro para subprocesos, ya que no garantiza los datos globales se encuentra en un estado coherente durante la ejecución:

int t;

void swap(int *x, int *y)
{
    int s;

    s = t; // save global variable
    t = *x;
    *x = *y;

    // hardware interrupt might invoke isr() here!
    *y = t;
    t = s; // restore global variable
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

Antecedentes[editar]

Reentrada no es la misma cosa que idempotencia, en el que la función puede ser llamado más de una vez todavía generar exactamente la misma salida que si sólo había sido llamado una vez. En términos generales, una función produce datos de salida sobre la base de unos datos de entrada (aunque ambos son opcionales, en general). Los datos compartidos se podía acceder por cualquier persona en cualquier momento. Si los datos se pueden cambiar por nadie (y nadie hace un seguimiento de los cambios), entonces no hay ninguna garantía para los que comparten un dato si ese dato es el mismo que en cualquier momento antes.

Datos tiene una característica llamada alcance, que describe donde en un programa se pueden usar los datos. Ámbito de aplicación de datos es ya sea global (fuera del alcance de cualquier función y con una indefinida medida), o locales (creado cada vez que una función se llama y destruyó a la salida).

Los datos locales no son compartidas por los hubiere, de volver a entrar o no, las rutinas; por lo tanto, no afectan a re-entrada. Los datos globales se definen las funciones externas, y se puede acceder a más de una función, ya sea en forma de variable global (datos compartidos entre todas las funciones), o como variable estática (datos compartidos por todas las funciones del mismo nombre). En la programación orientada a objetos, datos globales se definen en el alcance de una clase y pueden ser privadas, por lo que es accesible sólo a las funciones de esa clase. También existe el concepto de variable de instancia, en una variable de clase está obligado a una instancia de clase. Por estas razones, en la programación orientada a objetos de esta distinción es generalmente reservado para los datos de acceso situados fuera de la clase (público), y por el independiente los datos de las instancias de clase (estática).

Reentrada es distinto de, pero estrechamente relacionado con, hilo de seguridad. Una función puede ser seguro para subprocesos y todavía no reentrante. Por ejemplo, una función podría ser envuelto por todas partes con un mutex (que evita problemas en entornos multiproceso), pero si esa función se usa en una rutina de servicio de interrupción, podría morir de hambre a la espera de la primera ejecución para liberar el mutex. La clave para evitar la confusión es que reentrada se refiere a un solo hilo de ejecución. Es un concepto desde el momento cuando no existían sistemas operativos multitarea.

Reglas para la reentrada[editar]

Código reentrante no podrá ocupar ningún dato estático no constante (o global).
Las funciones reentrada pueden trabajar con datos globales. Por ejemplo, una rutina de servicio de interrupción reentrada podría tomar un pedazo de estado del hardware para trabajar con (por ejemplo, puerto serial leer buffer) que no sólo es global, pero volátil. Aun así, no se aconseja el uso típico de las variables estáticas y datos global, en el sentido de que sólo atómicas instrucciones lectura-modificación-escritura se deben utilizar en estas variables (que no debería ser posible que una interrupción o señal para venir durante la ejecución de tales una instrucción).
Código reentrante no puede modificar su propio código.
El sistema operativo puede permitir a un proceso para modificar su código. Hay varias razones para ello (por ejemplo, blitting gráficos rápidamente) pero esto causaría un problema con reentrada, ya que el código podría no ser el mismo la próxima vez.
Puede, sin embargo, modificar en sí 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, que no afectará a otras invocaciones incluso si modifica en sí durante la ejecución de esa invocación especial (hilo).
Código reentrante no puede llamar a no reentrada programas de ordenador o rutinas .
Múltiples niveles de usuario/objeto/proceso de prioridad y/o multiprocesamiento generalmente complican el control de código reentrante. Es importante hacer un seguimiento de cualquier acceso y/o efectos secundarios que se hacen dentro de una rutina diseñada para ser de reentrada.

Manejador de interrupciones Reentrante[editar]

Un "controlador de interrupciones de reentrada" es un manejador de interrupciones que vuelve a habilitar las interrupciones temprano en el manejador de interrupciones. Esto puede reducir la latencia de interrupción.[3]​ En general, mientras que las rutinas de servicio de interrupción de programación, se recomienda volver a habilitar las interrupciones tan pronto como sea posible en el manejador de interrupciones. Esta práctica ayuda a evitar la pérdida de las interrupciones.[4]

Otros ejemplos[editar]

En el siguiente fragmento de código C, ni f ni g funciones son reentrantes.

int g_var = 1;

int f()
{
    g_var = g_var + 2;
    return g_var;
}

int g()
{
    return f() + 2;
}

En lo anterior, f depende de un no constante variable global g_var; por lo tanto, si dos hilos de ejecutarlo y acceso g_var simultáneamente, el resultado varía dependiendo del momento de la ejecución. Por lo tanto, f no es reentrante. Tampoco es g; que llama f, que no es reentrante.

Estas versiones ligeramente alterados son reentrantes:

int f(int i)
{
    return i + 2;
}

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

En el siguiente fragmento de código C, la función es seguro para subprocesos, pero no por reentrada.

int function()
{
    mutex_lock();
    // ...
    function body
    // ...
    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 manejador de interrupciones reentrante y una segunda interrupción surge dentro de la función, la segunda rutina se colgará para siempre. Como servicio de interrupción puede desactivar otras interrupciones, todo el sistema podría sufrir.

Véase también[editar]

Referencias[editar]

  1. Kerrisk, 2010, p. 657.
  2. Anthony Ralston Anthony Ralston, ed. (2000). «Reentrant program». Encyclopedia of Computer Science. Fourth edition. Nature publishing group. pp. 1514-1515. 
  3. Andrew N. Sloss, Dominic Symes, Chris Wright, John Rayfield (2004). «Guía del sistema de brazo desarrollador». p. 342. 
  4. John Regehr (2006). Uso seguro y estructurado de las interrupciones en tiempo real y software embebido (PDF). 
  • Kerrisk, Michael (2010). «La interfaz de programación de Linux (The Linux Programming Interface)». No Starch Press. 

Enlaces externos[editar]