Herramientas de usuario

Herramientas del sitio


senales

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
senales [2012/09/24 23:38] lmateusenales [2016/11/24 13:13] (actual) – [sigaction/sigprocmask] lmateu
Línea 74: Línea 74:
  
 <code> <code>
-  /* Leer una lnea del teclado dndole 10 segundos de plazo */+  /* Leer una línea del teclado dándole 10 segundos de plazo */
   #include <stdio.h>   #include <stdio.h>
-  #undef __USE_BSD /* para que el read pueda ser interrumpido por una seal */+  #undef __USE_BSD /* para que el read pueda ser interrumpido por una señal */
   #include <signal.h>   #include <signal.h>
   #include <unistd.h>   #include <unistd.h>
Línea 82: Línea 82:
   volatile int flag;    volatile int flag; 
                  
-  /* funcin para atrapar la seal de alarma */+  /* función para atrapar la señal de alarma */
   void ring() {   void ring() {
     flag=0;     flag=0;
   }   }
                  
-  /* funcin que lee con timeout */+  /* función que lee con timeout */
   int gettext(char *buf, int bufsize, int timeout) {   int gettext(char *buf, int bufsize, int timeout) {
     int nchars;     int nchars;
Línea 115: Línea 115:
   }   }
 </code> </code>
 +
 +==== Señales y setjmp/longjmp ====
 +
 +Supongamos que queremos llamar una función, pero si la función toma más tiempo que digamos 2 segungos queremos
 +abortar la llamada.  Para ello configuramos el timer, ¿pero cómo detenemos la función?  La solución es usar
 +longjmp desde la rutina de atención de la señal:
 +
 +<code>
 +#include <unistd.h>
 +#include <signal.h>
 +#include <setjmp.h>
 +#include <stdio.h>
 +
 +/*
 + * Ejemplo de uso de signal y longjmp para implementar un timeout
 + * Esta implementacion usualmente solo funciona para la primera invocacion
 + */
 +
 +static jmp_buf ring;
 +
 +void clock(int sig)
 +{
 +    longjmp(ring, 1);
 +}
 +
 +int call_with_timeout(int (*f)(void *), void *p, int timeout)
 +{
 +    int res;
 +    void (*hdlr)();
 +
 +    hdlr = signal(SIGALRM, clock);
 +
 +    if (setjmp(ring) != 0) {
 +        signal(SIGALRM, hdlr); /* si llegamos aqui es que ocurrio el timeout */
 +        fprintf(stderr, "timeout!\n");
 +        return 1;
 +    }
 +
 +    alarm(timeout); /* programamos el timer */
 +    res = f(p);
 +    printf("exito!\n");
 +    alarm(0);  /* apagamos el timer */
 +
 +    signal(SIGALRM, hdlr);
 +    return(res);
 +}
 +
 +int fun(void *p) {
 +  sleep(2);
 +  return 0;
 +}
 +
 +int main() {
 +  call_with_timeout(fun, NULL, 1);
 +  call_with_timeout(fun, NULL, 1);
 +  return 0;
 +}
 +</code>
 +
 +Este código funciona en Linux, pero debido a un problema de estandarización, podría no funcionar en algunos
 +Unix.  El problema es que en algunos Unix la rutina de atención del timer (ejemplo: clock) se llama con
 +la señal SIGALRM deshabilitada.  Al retorno de clock se vuelve a activar.  Pero si se usa longjmp, la
 +señal queda deshabilitada y por lo tanto la señal de un segundo timeout podría nunca recibirse.
 +
 +Este es el caso de Linux, pero afortunadamente hay otro aspecto que evita el problema el Linux.  La función
 +setjmp graba el estado de las señales que están activas/deshabilitas y longjmp restaura el mismo estado.
 +En buenas cuentas longjmp activa nuevamente la señal SIGALRM y por eso funciona.  Pero esto tampoco es
 +estándar en todos los Unix.  Sin embargo sí es estándar que las funciones sigsetjmp y siglongjmp graban
 +el estado de las señales o no de acuerdo a un parámetro de sigsetjmp.  Entonces lo más correcto es usar
 +estas funciones en el código de más arriba:
 +
 +<code>
 +  #define _POSIX_C_SOURCE 1
 +
 +  void clock(int sig) {   
 +    siglongjmp(ring, 1);
 +  }
 +  
 +  ...
 +  
 +  int call_with_timeout(int (*f)(void *), void *p, int timeout) {
 +    ...
 +    if (sigsetjmp(ring, 1) != 0) {
 +      ...
 +    }
 +    ...
 +  }
 +</code>
 +
 +Es necesario definir la macro _POSIX_C_SOURCE porque no es una función estándar de ansi-C.  Pero el estándar
 +POSIX sí la define.
 +
 +=== Discusión ===
 +
 +Otro punto importante que no se debe olvidar es lo que pasa con los recursos solicitados por la
 +función.  ¿Qué pasa con la memoria pedida o los archivos abiertos?  Estos recursos no se liberarán
 +jamás y por lo tanto se transforman en goteras.  Si se va a emplear este enfoque, hay que mantener
 +una traza de todos los recursos pedidos para así liberarlos cuando se cumpla el timeout.  Esto
 +no es trivial de hacer.
 +
 +También hay que considerar que la invocación de la rutina que atiende la señal puede provocar dataraces.
 +Por ejemplo supongamos que la función f usa malloc.  La señal del timeout puede gatillarse justo a la mitad
 +del malloc y por lo tanto el heap que maneja malloc queda en un estado inconsistente.  Si la función que atiende
 +la señal también invoca malloc, se puede producir una inconsistencia que gatille un segmentation fault o
 +que se entregue 2 veces el mismo pedazo de memoria.  Por lo tanto las acciones de la rutina de atención de la
 +señal deben ser simples como asignar una variable global por ejemplo.
 +==== sigaction/sigprocmask ====
 +
 +La función signal es la primera función que existió en Unix para atrapar señales.  Hoy se prefiere
 +usar la función sigaction para tener más control sobre donde y cómo se debe ejecutar la rutina de atención.
 +Por ejemplo:
 +
 +  * si la señal se deshabilita o no mientras se ejecuta la rutina de atención
 +  * en que pila se ejecuta la señal
 +  * si la señal vuelve a su estado por omisión una vez que se gatilla la señal
 +  * etc.
 +
 +Del mismo modo, sigprocmask permite deshabilitar/activar señales explícitamente.  Esta función es de utilidad por ejemplo
 +para resolver el datarace asociado al uso de malloc tanto en el código en donde puede ocurrir una señal como en la función
 +que atiende esa señal.  La solución está en invocar sigprocmask antes de invocar malloc para inhibir la señal en cuestión e
 +invocar nuevamente sigprocmask para reactivar nuevamente la señal.  De esta forma si se gatilla la señal mientras se ejecuta
 +malloc, la señal quedará pendiente y solo se invocará la rutina de atención cuando se reactive la señal en la
 +segunda llamada de sigprocmask.
 +
 +Consulte la página del manual para averiguar más sobre estas funciones.
 +
 +==== Ejercicio ====
 +
 +Resuelva la parte c de la pregunta 1 del [[http://users.dcc.uchile.cl/~lmateu/CC3301/controles/c3-142.pdf|control 3 de 2014/2]].  Pruebe su solución con el archivo [[http://users.dcc.uchile.cl/~lmateu/CC3301/download/largo.zip|largo.zip]].  En este problema la variable nodo podría apuntar a una dirección inválida.  Si trata de acceder a nodo->prox se gatillará la señal SIGSEGV que normalmente termina el proceso.  Para evitar que el proceso termine, Ud. debe capturar la señal SIGSEGV.  La dificultad está en que si la rutina que atiende la señal retorna, se volverá a hacer el acceso inválido.  Para eludir el acceso inválido Ud. debe usar sigsetjmp en la función largo e invocar siglongjmp en la rutina que atiende la señal.
senales.1348529939.txt.gz · Última modificación: 2012/09/24 23:38 por lmateu