Señales CI-2400 Programación Avanzada en Sistemas de Tipo UNIX Prof. Braulio José Solano Rojas ECCI, UCR Señales ● ● Las señales son interrupciones de software que pueden ser enviadas a un proceso para informarle de algún evento asíncrono o situación especial. El término señal se emplea también para referirse al evento. Los procesos pueden enviarse señales unos a otros a través de la llamada kill y es bastante frecuente que, durante su ejecución, un proceso reciba señales procedentes del núcleo. 2 de 77 Señales ● Cuando un proceso recibe una señal puede reaccionar de tres formas diferentes: ● ● ● Ignorar la señal, con lo cual es inmune a la misma. Invocar a la rutina de tratamiento por defecto. Esta rutina no la codifica el programador, sino que la aporta el núcleo. Según el tipo de señal, la rutina de tratamiento por defecto realizará una acción u otra. Por lo general suele provocar la terminación del proceso mediante una llamada a exit. Invocar a una rutina propia que se encarga de tratar la señal. 3 de 77 Tipos de señales ● ● ● Señales relacionadas con la terminación de procesos. Señales relacionadas con las excepciones inducidas por los procesos. Señales relacionadas con los errores irrecuperables originados en el transcurso de una llamada al sistema. 4 de 77 Tipos de señales ● ● ● Señales originadas desde un proceso que se está ejecutando en modo usuario. Señales relacionadas con la interacción con el terminal. Señales para ejecutar un proceso paso a paso. Son usadas por los depuradores. 5 de 77 Señales UNIX System V ● ● Están definidas en el archivo de encabezados <signal.h>. Hay 19 señales definidas por UNIX System V. 6 de 77 SIGHUP ● Desconexión. Es enviada cuando un terminal se desconecta de todo proceso del que es terminal de control. También se envía a todos los procesos de un grupo cuando el líder del grupo termina su ejecución. La acción por defecto de esta señal es terminar la ejecución del proceso que la recibe. 7 de 77 SIGINT ● Interrupción. Se envía a todo proceso asociado con un terminal de control cuando se pulsan la teclas de interrupción Ctrl+c. Su acción por defecto es terminar la ejecución del proceso que la recibe. 8 de 77 SIGQUIT ● Salir. Similar a SIGINT, pero es generada al pulsar la tecla de salida Control+\. Su acción por defecto es generar un fichero core y terminar el proceso. 9 de 77 SIGILL ● Instrucción ilegal. Es enviada cuando el procesador detecta una instrucción que no forma parte de su repertorio. En los programas escritos en C suele producirse este tipo de error cuando manejamos punteros a funciones que no han sido correctamente inicializados. Su acción por defecto es generar, un fichero core y terminar el proceso. 10 de 77 SIGTRAP ● Trace trap. Cuando un proceso se está ejecutando paso a paso, esta señal es enviada después de ejecutar cada instrucción. Es empleada por los pro­gramas depuradores. Su acción por defecto es generar un fichero core -terminar el proceso. 11 de 77 SIGIOT ● I/O trap instruction. Se envía cuando se da un fallo de hardware. La naturaleza de este fallo depende de la máquina. También es enviada cuando llamamos a la función abort. que provoca el suicidio del proceso generando: un fichero core. 12 de 77 SIGEMT ● Emulator trap instruction. También indica un fallo de hardware. Raras veces se utiliza. Su acción por defecto es generar un fichero core y terminar el proceso. 13 de 77 SIGFPE ● Error en coma flotante. Es enviada cuando el hardware detecta un error coma flotante, como el uso de número en coma flotante con un formato desconocido, errores de desbordamiento, etc. Su acción por defecto es generar un fichero core y terminar el proceso. 14 de 77 SIGKILL ● Terminación abrupta. Esta señal provoca irremediablemente la terminación del proceso. No puede ser ignorada y siempre que se recibe se ejecuta su acción por defecto, que consiste en generar un fichero core y terminar el proceso. 15 de 77 SIGBUS ● Error de bus. Se produce cuando se da un error de acceso a memoria. Las dos situaciones típicas que la provocan suelen ser intentar acceder a una dirección que físicamente no existe o intentar acceder a una dirección impar, violando así las reglas de alineación que impone el procesador. Su acción por defecto es generar un fichero core y terminar el proceso. 16 de 77 SIGSEGV ● Violación de segmento. Es enviada a un proceso cuando intenta acceder a datos que se encuentran fuera de su segmento de datos. Su acción por defecto es generar un fichero core y terminar el proceso. 17 de 77 SIGSYS ● Argumento erróneo en una llamada al sistema. No se usa. 18 de 77 SIGPIPE ● Intento de escritura en una tubería de la que no hay nadie leyendo. Esa suele ocurrir cuando el proceso de lectura termina de una forma anormal. Su acción por defecto es terminar el proceso. 19 de 77 SIGALRM ● Despertador. Es enviada a un proceso cuando alguno de sus temporizadores descendentes llega a cero. Su acción por defecto es terminar el proceso. 20 de 77 SIGTERM ● Finalización controlada. Es la señal utilizada para indicarle a un proceso que debe terminar su ejecución. Esta señal no es tajante como SIGKILL y puede ser ignorada. Lo correcto es que la rutina de tratamiento de esta señal se encargue de tomar las acciones necesarias para dejar al proceso un estado coherente y a continuación finalizar su ejecución con una llamada a exit. Esta señal es enviada a todos los procesos cuando se emiten las órdenes shutdown o reboot. Su acción por defecto es terminar el proceso. 21 de 77 SIGUSR1 ● Señal número 1 de usuario. Esta señal está reservada para uso del programador. Ninguna aplicación estándar va a utilizarla y su significado es el que le quiera dar el programador en su aplicación. Su acción por defecto es terminar el proceso. 22 de 77 SIGUSR2 ● Señal número 2 de usuario. Su significado es idéntico al de SIGUSR1. 23 de 77 SIGCLD ● Terminación del proceso hijo. Es enviada al proceso padre cuando alguno de sus procesos hijo termina. Esta señal es ignorada por defecto. 24 de 77 SIGPWR ● Fallo de alimentación. Esta señal tiene diferentes interpretaciones. En algunos sistemas es enviada cuando se detecta un fallo de alimentación y le indica al proceso que dispone tan sólo de unos instantes antes de que se produzca la caída del sistema. En otros sistemas, esta señal es enviada, después de recuperarse de un fallo de alimentación, a todos aquellos procesos que estaban en ejecución y que se han podido rearrancar. En estos casos, los procesos deben disponer de mecanismos para restaurar las posibles pérdidas producidas durante la caída de la alimentación. 25 de 77 Envío de señales ● Para enviar una señal desde un proceso a otro o a un grupo de procesos, emplearemos la llamada kill: #include <signal.h> int kill (pid_t pid, int sig); donde pid identifica el conjunto de procesos al que queremos enviarle la señal. 26 de 77 pid ● Este parámetro es un número entero y los distintos valores que puede tomar tienen los siguientes significados: ● ● pid > 0, es el PID del proceso al que le enviamos la señal. pid = 0, la señal es enviada a todos los procesos que pertenecen al mismo grupo que el proceso que la envía. 27 de 77 pid ● ● pid = -1, la señal es enviada a todos aquellos procesos cuyo identificador real es igual al identificador efectivo del proceso que la envía. Si el proceso que la envía tiene identificador efectivo de superusuario, la señal es enviada a todos los procesos, excepto al proceso 0 —swapper— y al proceso 1 —init—. pid < -1, la señal es enviada a todos los procesos cuyo identificador de grupo coincide con el valor absoluto de pid. 28 de 77 sig ● El parámetro sig es el número de la señal que queremos enviar. Si sig vale 0 —señal nula— se efectúa una comprobación de errores, pero no se envía ninguna señal. Esta opción se puede utilizar para verificar la validez del identificador pid. 29 de 77 Ejemplo: kill-1.c raise ● Si queremos que un proceso se envíe señales a sí mismo, podemos usar la llamada raise: #include <signal.h> int raise (int sig) ; donde sig es el número de la señal que queremos enviar; raise se puede codificar a partir de kill de la siguiente forma: int raise (int sig) { return kill (getpid (), sig); } 31 de 77 Tratamiento de señales ● Para especificar qué tratamiento debe realizar un proceso al recibir una señal, se emplea la llamada signal: #include <signal.h> void (*signal (int sig, void (*action) ())) (); Ejemplo: signal-1.c En espera de señales ● En ocasiones puede interesar que un proceso suspenda su ejecución en espera de que ocurra algún evento exterior a él. Por ejemplo, al ejecutar una entrada/salida. Para estas situaciones nos valemos de la llamada a pause: #include <unistd.h> int pause (void); 34 de 77 Ejemplo: pause-1.c Saltos globales ● La rutina de tratamiento de una señal puede hacer que el proceso vuelva a alguno de los estados por los que ha pasado con anterioridad. Esto no sólo es aplicable a las rutinas de tratamiento de señales sino que se puede extender a cualquier función. Para realizar esto nos valemos de las funciones estándar setjmp y longjmp: #include <setjmp.h> int set jmp (jmp_ buf env); void longjmp (jmp_buf env, int val); 36 de 77 Saltos globales ● La función setjmp guarda el entorno de pila en env para un uso posterior por de longjmp; setjmp devuelve el valor 0 en su primera llamada. El tipo de env, jmp_buf, está definido en el archivo de encabezado <setjmp.h>. 37 de 77 Saltos globales ● La función longjmp restaura el entorno guardado en env por una llamada setjmp. Después de haberse ejecutado la llamada a longjmp, el flujo de la ejecución del programa vuelve al punto donde se hizo la llamada a setjmp, pero en este caso setjmp devuelve el valor val que hemos pasado mediante longjmp. Ésta es la forma de averiguar si setjmp está saliendo de una llamada para guardar el entorno o de una llamada de longjmp. longjmp no puede hacer que setjmp devuelva 0, ya que en el caso de que val tome el valor 0, setjmp devolverá 1. 38 de 77 Saltos globales jmp_buf entorno; int valor; … valor=setjmp(entorno); (valor=0) (valor=10) … longjmp(entorno,10); 39 de 77 Saltos globales ● Las funciones setjmp y longjmp se pueden ver como una forma elaborada de implementar una sentencia goto no local, capaz de saltar desde una función a etiquetas que están en la misma o en otra función. Las etiquetas serían los entornos guardados por setjmp en la variable env. 40 de 77 Ejemplo: setjmp-1.c Señales en 4.3BSD ● ● La interfaz para el manejo de señales fue diseñada por la Universidad de California en Berkeley. Las llamadas kill, signal y pause están disponibles y se incorporan otras para hacer más versátil el manejo de señales. Además, el UNIX de Berkeley añade nuevas señales a las existentes en la interfaz System V del UNIX de AT&T. De las señales que implementa el UNIX System V, SIGPWR no está implementada en 4.3BSD y SIGCLD pasa a llamarse SIGCHLD. 42 de 77 SIGVTALRM ● Alarma de un temporizador en tiempo virtual. Indica que un temporizador descendente en tiempo virtual ha llegado a cero. Su acción por defecto es terminar el proceso que la recibe. 43 de 77 SIGPROF ● Alarma de un temporizador. Indica que un temporizador descendente que cuenta tanto en tiempo real como virtual, ha llegado a 0. Su acción por defecto es terminar el proceso que la recibe. 44 de 77 SIGIO ● Señal de entrada/salida asíncrona. Indica que un dispositivo o archivo está listo para una operación de entrada/salida. Su acción por defecto es ignorar la señal. 45 de 77 SIGWINCH ● Cambio del tamaño de una ventana. Se usa en las interfaces gráficas orientadas a ventanas como X-WINDOW. Su acción por defecto es ignorar la señal. 46 de 77 SIGSTOP ● Señal de parada de un proceso. Esta señal no proviene de un terminal de control. La señal no puede ser ignorada ni capturada y su acción por defecto es parar el proceso. 47 de 77 SIGTSTP ● Señal de parada procedente de un terminal. Es generada por el teclado. Su acción por defecto es parar el proceso. 48 de 77 SIGCONT ● Continuar. Señal para reanudar las ejecución de un proceso. Su acción por defecto es ignorar la señal. 49 de 77 SIGTTIN ● La reciben los procesos que se ejecutan en segundo plano y que intentan leer datos de un terminal de control. Su acción por defecto es parar el proceso. 50 de 77 SIGTTOU ● La reciben los procesos que se ejecutan en segundo plano y que intentan escribir en un terminal de control. Su acción por defecto es parar el proceso. 51 de 77 SIGURG ● Indica que ha llegado un dato urgente a través de un canal de entrada/sa­lida. Su acción por defecto es ignorar la señal. 52 de 77 SIGXCPU ● Le indica al proceso que la recibe que ha superado su tiempo de CPU asignado. Su acción por defecto es terminar el proceso. 53 de 77 SIGXFSZ ● Le indica al proceso que la recibe que ha superado el tamaño máximo del fichero que puede manejar. Su acción por defecto es terminar el proceso. 54 de 77 Tratamiento de señales ● La llamada signal sigue disponible en el sistema 4.3BSD, pero además disponemos de otra llamada para especificar la forma de tratar una señal. Esta llamada es sigvec y su declaración es la siguiente: #include <signal.h> sigvec (int sig, struct sigvec *vec, struct sigvec *ovec); 55 de 77 Tratamiento de señales ● Tanto vec como ovec son punteros a estructuras del tipo sigvec. Esta estructura está definida con los siguientes campos: struct sigvec { void (*sv_handler) (); long sv_mask; long sv_flags; } 56 de 77 Ejemplo: sigvec-1.c Ejemplo: sigvec-2.c Ejemplo: sigvec-3.c Protección de zonas críticas ● En ocasiones puede interesarnos proteger determinadas zonas de código contra la llegada de alguna señal. Para realizar esto nos valdremos de las llamadas sigsetmask y sigblock: #include <signal.h> long sigsetmask (long mask); long sigblock (long mask); Protección de zonas críticas ● La función sigsetmask fija la máscara de señales actual. Esta máscara indica qué señales tienen bloqueada su recepción. La señal número i está bloqueada si el i-ésimo bit de mask vale 1. Este bit lo podemos fijar con la macro sigmask(i). Naturalmente, las señales que no pueden ser ignoradas ni capturadas tampoco van a poder ser bloqueadas. Protección de zonas críticas ● ● La función sigblock permite añadir a la máscara de señales actual aquellas señales que se especifiquen con el parámetro mask. Así, con sigblock podemos modificar la máscara fijada por sigsetmask. La diferencia entre sigsetmask y sigblock es que la primera fija la máscara de forma absoluta y, la segunda, de forma relativa. Las siguientes líneas de código aclaran este concepto. 62 de 77 En espera de señales ● La llamada sigpause permite parar un proceso hasta que se reciba alguna de las señales deseadas. Si con pause podíamos parar un proceso en espera de la primera señal que la reciba, con sigpause podemos seleccionar la señal por la que esperamos. La declaración de esta función es: #include <signal.h> long sigpause (long mask); 63 de 77 Gestión de señales POSIX ● Además de los gestores de señales descritos, hay otros estándares adoptados por muchos fabricantes de sistemas UNIX. En concreto, el estándar POSIX 1003.1 propone un gestor de señales con una funcionalidad muy similar a la de 4.3BSD y que ha sido adoptada por Linux, 4.4BSD y FreeBSD. 64 de 77 Tratamiento de señales ● Con la llamada sigaction asociamos señales con acciones para indicar cómo responderá el proceso actual cuando reciba una señal. La declaración de sigaction es: #include <signal.h> int sigaction (int sig, const struct sigaction *act, struct sigaction *oact); 65 de 77 Estructura sigaction struct sigaction { // Si el indicador SA_SIGINFO no está activo. void (*sa_handler) (int signo); // Si el indicador SA_SIGINFO está activo. void (*sa_sigaction) (int signo, siginfo_t *info, void *context); sigset_t sa_mask; // Máscara de señales. Indica las señales adicionales // que serán bloqueadas durante la ejecución del // manejador. int sa_flags; // Indicadores. } 66 de 77 Estructura siginfo_t typedef struct { int si_signo; // Número de la señal. int si_errno; // Número de error asociado con la señal. int si_code; // Código que especifica la causa de la señal. pid_t si_pid; // PID del proceso que envía la señal —señal SIGCHLD—. uid_t si_uid; // UID del proceso que envía la señal —señal SIGCHLD—. void *si_addr; // Dirección de la instrucción -señales SIGILL y SIGFPE// o del dato -señales SIGBUS y SIGSEGV- ilegal que // origina la señal. int si_status; // Valor de salida o número de señal del poceso hijo // -señal SIGCHLD-. long si_band; // Datos del evento -señal SIGPOLL-. unión sigval si_value; // Datos de la señal —señales de // tiempo real [SIGRTMIN, SIGRTMAX]-. } siginfo_t; 67 de 77 Envío de señales ● ● En POSIX también podemos usar la llamada kill para enviar señales. Los sistemas que implementan las extensiones de tiempo real disponen de la llamada sigqueue para enviar señales acompañadas de información: #include <signal.h> int sigqueue (pid_t pid, int signo, const union sigval value) 68 de 77 Envío de señales ● Para realizar envío de señales entre hilos de un mismo proceso se emplea la función pthread_kill: #include <signal.h> int pthread_kill (pthread_t thread, int sig); donde thread es el hilo al que le enviamos la señal y sig es el número de la señal enviada. Nuevamente se tendrá en cuenta que SIGKILL afecta a todo el proceso y, por lo tanto. para terminar la ejecución de un hilo concreto utilizaremos pthread_cancel. 69 de 77 Máscara de señales ● Para manipular conjuntos de señales hay que empezar inicializándolos con sigfillset y sigemptyset: #include <signal.h> int sigfillset (sigset_ t *set); int sigemptyset (sigset_t *set); 70 de 77 Máscara de señales ● Una vez que se ha inicializado un conjunto podemos manipularlo para incluir una señal — sigaddset—, excluirla —sigdelset— o preguntar si está incluida —sigismember—: #include <signal.h> int sigaddset (sigset_t *set, int signo); int sigdelset (sigset__t *set, int signo); int sigismember (const sigset_ t *set, int signo); 71 de 77 Máscara de señales ● A partir de un conjunto podemos modificar la máscara de señales bloqueadas en un proceso —sigprocmask— o en un hilo — pthread_sigmask—: #include <signal.h> int sigprocmask (int how, const sigset_ t * set, sigset_ t *oset); int pthread_sigmask (int how, const sigset_t *set, sigset_t *oset); 72 de 77 Parámetro how SIG_BLOCK La máscara resultante es el resultado de la unión de la máscara actual y el conjunto. SIG_SETMASK La máscara resultante es la indicada en el conjunto. SIG_UNBLOCK La máscara resultante es la intersección de la máscara actual y el com­plementario del conjunto. Es decir, las señales incluidas en el conjunto quedarán desbloqueadas en la nueva máscara de señales. 73 de 77 Ejemplo: exception.h, exception.c y excep.c Resumen de gestores de señales 4.3BSD SYSTEM V POSIX signal() signal() signal() kill() kill() kill() pause() sigpause() sigsuspend() signal() sigvector() ó sigvec() sigaction() - sigblock() sigprogmask() - sigsetmask() sigprogmask() - sigstack() - 75 de 77 Ejemplos de aplicaciones que utilizan señales ● Software tolerante a fallos ● Sincronización de procesos 76 de 77 ¡Gracias por su atención! ¿Preguntas?