Mtro. Carlos Alberto Román Zamitiz · 8 sep 2026 · Tema 2 — Estados de un proceso, señales de UNIX System V, función wait(), procesos zombie y terminación de procesos con kill()/abort().
⚠️ Nota: a esta clase no asistí por una entrevista. El grueso del contenido no viene de audio propio, sino que fue reconstruido a partir de la Práctica #3 "Procesos (continuación)" publicada en el sitio del profesor (Tema 2). No hay secciones de "para el examen" ni "contexto" propias porque no hay audio del que sacar el énfasis real del profesor ni sus anécdotas de ese día — solo el material escrito. Sí confirmado por audio (repaso que el profesor hizo al inicio de la Clase 06): que aquí arrancó formalmente el Tema 2, que dejó de tarea moral revisar man 7 signal (columna X86 ARM), que se demostró en vivo la adopción de huérfanos (PPID → 1), y que SIGCHLD es la señal 17 en X86/ARM (no 18 como en la tabla genérica de abajo, que es la numeración histórica de UNIX System V).
<signal.h>.wait(): bloquea al proceso padre hasta que el hijo termina; regresa un código de estado interpretable con macros (WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG, WCOREDUMP, WIFSTOPPED, WSTOPSIG).wait() para "recogerlo"; aparece en ps como Z+ <defunct>.SIGINT, SIGTERM vía kill, SIGABRT vía abort(), SIGKILL — no se puede bloquear ni manejar).Un proceso puede ejecutarse en modo usuario o en modo núcleo (supervisor). Su tiempo de vida se divide en un conjunto de estados con reglas de transición bien definidas, impuestas por la competencia entre procesos por un recurso escaso: la CPU.
Los 9 estados posibles:
fork), aún no listo para ejecutarse.Diagrama de transición (descrito): del estado 1 (modo usuario) se pasa al 2 (modo núcleo) por llamada al sistema o interrupción; del 2 se puede ir a 7 (cambio de contexto) y de ahí de vuelta a 1 (retorno al modo usuario), o a 9 (zombie) vía exit. Desde 2 también se puede pasar a "dormir" (4) o recibir "orden de ejecución" desde 3. El ciclo 8→3/5 corresponde a la creación con fork (según haya o no memoria suficiente), y 3↔4↔6↔5 son los movimientos entre memoria principal (MP) y secundaria (MS) mientras el proceso está dormido o listo.
Contexto de un proceso: su código, los valores de sus variables de usuario globales y sus estructuras de datos, el valor de los registros de la CPU, los valores almacenados en su entrada de la tabla de procesos y en su área de usuario, y el valor de sus pilas de usuario y núcleo.
Las señales son un mecanismo de comunicación y manipulación de procesos en Linux: un mensaje especial enviado a un proceso. Son asíncronas — cuando un proceso recibe una señal, la procesa de inmediato, sin terminar la función o línea de código en curso. Cada señal tiene un número, pero en los programas se referencian por nombre. Requieren la cabecera <signal.h>.
El sistema manda señales en respuesta a condiciones específicas: SIGBUS (error en el bus), SIGSEGV (violación de segmento) y SIGFPE (excepción de punto flotante) terminan el proceso y producen un archivo core. Un proceso también puede mandar una señal a otro: SIGTERM y SIGKILL terminan un proceso desde otro proceso; SIGHUP se usa para despertar un proceso. Un manejador de señales debe hacer el mínimo trabajo necesario y regresar el control (o terminar el programa); puede ser interrumpido por otra señal.
Clasificación de señales: terminación de procesos · excepciones inducidas (acceso fuera del espacio de direcciones virtuales, errores de punto flotante) · errores irrecuperables en una llamada al sistema · originadas desde un proceso en modo usuario (ej. kill, temporizador de alarma) · interacción con terminal (ej. Ctrl+C) · ejecución paso a paso (usadas por depuradores).
Las 19 señales de UNIX System V:
| Señal | Núm. | Descripción / acción por defecto |
|---|---|---|
SIGHUP | 1 | Hangup — se envía cuando el terminal se desconecta, o a todo el grupo cuando el líder termina. Termina el proceso. |
SIGINT | 2 | Interrupción — al pulsar la tecla de interrupción (Ctrl+C) en un proceso con terminal de control. Termina el proceso. |
SIGQUIT | 3 | Salir — similar a SIGINT pero con la tecla de salida (Ctrl+\). Genera core y termina. |
SIGILL | 4 | Instrucción ilegal — hardware detecta instrucción inválida (común con punteros a función mal inicializados en C). Genera core y termina. |
SIGTRAP | 5 | Trace trap — enviada tras cada instrucción en ejecución paso a paso. Genera core y termina. |
SIGIOT | 6 | I/O trap — fallo de hardware; se envía al llamar abort() ("suicidio" del proceso, genera core). |
SIGEMT | 7 | Emulator trap — fallo de hardware, poco usada. Genera core y termina. |
SIGFPE | 8 | Error en coma flotante — formato desconocido, overflow/underflow. Genera core y termina. |
SIGKILL | 9 | Kill — termina el proceso irremediablemente; no puede ser ignorada. Genera core y termina. |
SIGBUS | 10 | Bus error — acceso a dirección inexistente o desalineada. Genera core y termina. |
SIGSEGV | 11 | Violación de segmento — acceso fuera del segmento de datos. Genera core y termina. |
SIGSYS | 12 | Argumento erróneo en llamada al sistema. No se usa. |
SIGPIPE | 13 | Escritura en tubería sin lector — el proceso lector terminó de forma anormal. Termina el proceso. |
SIGALRM | 14 | Alarm clock — un temporizador del proceso llega a cero. Termina el proceso. |
SIGTERM | 15 | Finalización software — pide terminar la ejecución; a diferencia de SIGKILL, puede ignorarse o manejarse (ej. borrar temporales antes de llamar exit). Se envía a todos los procesos en el shutdown. |
SIGUSR1 | 16 | Señal de usuario 1 — reservada para el programador, sin uso estándar. Termina el proceso por defecto. |
SIGUSR2 | 17 | Señal de usuario 2 — idéntica en propósito a SIGUSR1. |
SIGCLD/SIGCHLD | 18* | Muerte del proceso hijo — se envía al padre cuando un hijo termina. Ignorada por defecto. *Confirmado en clase: en la arquitectura X86/ARM de man 7 signal es la señal 17, no 18 (18 es la numeración histórica genérica de UNIX System V que trae esta tabla). |
SIGPWR | 19 | Fallo de alimentación — avisa de una caída inminente, o notifica tras recuperarse de una; el proceso debe poder restaurar pérdidas. |
wait()Permite a un proceso padre esperar a que sus procesos hijos terminen. Bloquea al padre hasta que el hijo finaliza, y regresa un código de estado (entero) del que se extrae información con macros:
WIFEXITED — verdadero (≠0) si el proceso terminó con exit() o wait().WEXITSTATUS — si WIFEXITED es verdadero, regresa los 8 bits menos significativos que exit() pasó al padre.WIFSIGNALED — verdadero si el proceso terminó por una señal.WTERMSIG — si WIFSIGNALED es verdadero, regresa el número de la señal que causó la terminación.WCOREDUMP — verdadero si se generó un volcado de memoria (core).WIFSTOPPED — verdadero si el proceso está parado.WSTOPSIG — si WIFSTOPPED es verdadero, regresa el número de la señal que causó la parada.#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int spawn(char* program, char** arg_list) {
pid_t child_pid;
child_pid = fork();
if (child_pid != 0) /* proceso padre */
return child_pid;
else {
execvp(program, arg_list); /* solo regresa si hay error */
fprintf(stderr, "an error occurred in execvp\n");
abort();
}
}
int main() {
int child_status;
char* arg_list[] = {"ls", "-l", "/", NULL};
spawn("ls", arg_list);
wait(&child_status); /* espera al hijo */
if (WIFEXITED(child_status))
printf("the child process exited normally, with exit code %d\n",
WEXITSTATUS(child_status));
else
printf("the child process exited abnormally\n");
return 0;
}
Código 1 — uso de wait() (fork-exec.c).
Un proceso zombie ya terminó su ejecución pero no ha sido eliminado del sistema. Es responsabilidad del proceso padre eliminarlo: wait() revisa el estado de terminación del hijo y, si ya terminó, lo elimina y regresa el control al padre. Si el padre nunca llama wait(), el proceso queda como zombie.
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t child_pid;
child_pid = fork();
if (child_pid > 0) {
sleep(60); /* padre duerme 1 minuto */
} else {
exit(0); /* hijo termina de inmediato */
}
return 0;
}
Código 2 — proceso zombie (zombie.c). Al correrlo y revisar con ps -e -o pid,ppid,stat,cmd desde otra terminal, el hijo aparece como Z+ <defunct> mientras el padre sigue dormido.
kill() y abort()Un proceso termina de dos formas: completando su tarea y regresando el control al padre, o de forma anormal en respuesta a una señal (SIGINT con Ctrl+C, SIGTERM vía el comando kill, SIGABRT al llamar abort() — genera core —, o SIGKILL, la más poderosa: termina de inmediato y no puede bloquearse ni manejarse).
Desde la terminal: kill -SEÑAL PID. Desde código, con <sys/types.h> y <signal.h>: kill(child_pid, SIGTERM).
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
int main() {
pid_t pid;
if ((pid = fork()) == 0) {
while (1) {
printf("HIJO PID = %d\n", pid);
sleep(1);
}
}
sleep(10);
printf("PADRE Terminacion proceso %d\n", pid);
kill(pid, SIGTERM);
exit(0);
}
Código 3 — uso de kill() (kill.c): el hijo imprime su PID cada segundo hasta que, tras 10 segundos, el padre lo termina con SIGTERM.
#include <stdlib.h>
#include <stdio.h>
int main(void) {
abort();
exit(EXIT_SUCCESS);
}
Código 4 — invocando abort() (abort.c): el proceso se manda a sí mismo SIGABRT e imprime "Aborted".
Códigos de salida: por convención, 0 = sin problemas; otro valor = error. En Linux se consulta con $?. Un código entre 0 y 127 es un valor normal de salida; 128 o mayor indica que el proceso fue interrumpido por una señal.
kill.c, crear otro proceso hijo e indicar qué pasa con el programa.