ENUNCIADO

Codificad una función que se llame nuevosleep a la que se le pase el número de segundos que tiene el programa que esperar e imprima una cuenta atrás de esos segundos por la salida estándar. Así:
nuevosleep(3);
Y sale:
3, [[un segundo...]] 2, [[un segundo...]], 1, [[un segundo...]] 0.
No se puede usar la función de biblioteca sleep. Podéis suponer que existe en el programa una función ya definida llamada void nonada(int), que no hace nada.

COMENTARIOS

Aunque no se os especificaba prototipo de la función nuevosleep, de ser un poco aguilillas, deberíais haber mirado el de la función sleep. La función debe tener alguna manera de señalar los posibles errores de su funcionamiento al programa. Para ello, ha de devolver un int. La práctica con 0.25 es la de "Master of Puppets". Es la única que cumple con cuatro condiciones irrenunciables:
  1. Se registra la señal SIGALRM.
  2. Se guarda y restaura la señal vieja.
  3. Se rellenan todos los campos de la estructura sigaction.
  4. El prototipo y el tratamiento de errores es el correcto para una función de estas características.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
[...]
int nuevosleep(int segundos)
   {
    sigset_t conjunto_vacio;
    struct sigaction accion_nueva, accion_vieja;
        
    sigemptyset(&conjunto_vacio);
    accion_nueva.sa_handler=nonada;
    accion_nueva.sa_mask=conjunto_vacio;
    accion_nueva.sa_flags=0;
    if(sigaction(SIGALRM,&accion_nueva,&accion_vieja)==-1) return -1;

    while(segundos>=0)
       {alarm(1);
        pause();
        printf("%d\n",segundos);
        segundos--;}

    if(sigaction(SIGALRM,&accion_vieja,NULL)==-1) return -1;

    return 0; /* Ejecución satisfactoria */}


void nonada()
   {}    


REVISIÓN (2006)

"Master of Puppets" de seguro no aprobaría esta pregunta si la hiciera en el examen ahora así. Afortunadamente, las prácticas entregadas mal son como los delitos fiscales y prescriben al cabo del tiempo. Haré, no obstante, penitencia por ello.

El problema radica en el conjunto formado por alarm() y pause(). Será un problema recurrente en esta asignatura. Al ser dos instrucciones diferentes su ejecución no es atómica, es decir, puede ser interrumpida y que el sistema cambie. De otro modo, dependiendo del reparto de CPU podría ocurrir que:
  1. Se ejecutara alarm(). Se le quitara la CPU al proceso.
  2. Se recibiera la señal encargada, SIGALRM justo entre las dos instrucciones.
  3. Se saltara a la manejadora nonada.
  4. Al volver de la manejadora, el proceso cae en el pause() pero, al haberse producido ya la señal, se quedará ahí para siempre.
Bien es cierto que la probabilidad de que esto ocurra es muy, muy baja. No obstante, hemos de buscar la perfección y la práctica se debe hacer así:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
[...]
void nonada()
   {} 

int nuevosleep(int segundos)
   {
    sigset_t conjunto_vacio, conjunto_SIGALRM, conjunto_viejo,
             conjunto_sin_SIGALRM;
    struct sigaction accion_nueva, accion_vieja;
        
    sigemptyset(&conjunto_SIGALRM);
    sigaddset(&conjunto_SIGALRM,SIGALRM);
    if (sigprocmask(SIG_BLOCK,&conjunto_SIGALRM,&conjunto_viejo)==-1)
        return -1;

    conjunto_sin_SIGALRM=conjunto_viejo;
    sigdelset(&conjunto_sin_SIGALRM,SIGALRM);

    sigemptyset(&conjunto_vacio);
    accion_nueva.sa_handler=nonada;
    accion_nueva.sa_mask=conjunto_vacio;
    accion_nueva.sa_flags=0;
    if(sigaction(SIGALRM,&accion_nueva,&accion_vieja)==-1) return -1;


    while(segundos>=0)
       {alarm(1);
        sigsuspend(&conjunto_sin_SIGALRM); /* ¿Y el error? */
        printf("%d\n",segundos);
        segundos--;}

    if(sigaction(SIGALRM,&accion_vieja,NULL)==-1) return -1;
    if(sigprocmask(SIG_SETMASK,&conjunto_viejo,NULL)==-1) return -1;

    return 0; /* Ejecución satisfactoria */
    }
Algunos os preguntaréis por qué no se controla el error de sigsuspend. La razón es que al recibir SIGALRM, sigsuspend da un error. Si hiciéramos el tratamiento de errores estándar, aparecería este error sin en realidad haberlo. Habría que distinguir entre los verdaderos errores y estos falsos errores. Para los más exquisitos gourmets de la programación, no obstante, el tratamiento de errores podría ser:
    if (sigsuspend(&conjunto_sin_SIGALRM)==-1 && errno!=EINTR) return -1;
Los errores que pueda dar sigsuspend son escasos y tampoco es tan crítico controlarlos.

MENCIONES ESPECIALES



ERRATA



LPEs


© 2006 Guillermo González Talaván.