← Arquitectura Cliente-Servidor

Clase 05

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).

En corto

Estados de un proceso

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:

  1. Ejecución en modo de usuario.
  2. Ejecución en modo del núcleo.
  3. Listo para ejecutar y en memoria — listo para ejecutarse tan pronto como el núcleo lo planifique.
  4. Dormido y en memoria — no dispuesto para ejecutar hasta que se produzca un suceso; el proceso está en memoria principal.
  5. Listo para ejecutar y descargado — listo, pero debe cargarse en memoria principal antes de que el núcleo pueda planificarlo.
  6. Dormido y descargado — esperando un suceso y expulsado al almacenamiento secundario.
  7. Expulsado — retorna del modo núcleo al modo usuario, pero el núcleo lo expulsa y hace un cambio de contexto para planificar otro proceso.
  8. Creado — recién creado (vía fork), aún no listo para ejecutarse.
  9. Zombie — el proceso ya no existe, pero deja un registro para que lo recoja el proceso padre.

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.

Señales (signals)

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ñalNúm.Descripción / acción por defecto
SIGHUP1Hangup — se envía cuando el terminal se desconecta, o a todo el grupo cuando el líder termina. Termina el proceso.
SIGINT2Interrupción — al pulsar la tecla de interrupción (Ctrl+C) en un proceso con terminal de control. Termina el proceso.
SIGQUIT3Salir — similar a SIGINT pero con la tecla de salida (Ctrl+\). Genera core y termina.
SIGILL4Instrucción ilegal — hardware detecta instrucción inválida (común con punteros a función mal inicializados en C). Genera core y termina.
SIGTRAP5Trace trap — enviada tras cada instrucción en ejecución paso a paso. Genera core y termina.
SIGIOT6I/O trap — fallo de hardware; se envía al llamar abort() ("suicidio" del proceso, genera core).
SIGEMT7Emulator trap — fallo de hardware, poco usada. Genera core y termina.
SIGFPE8Error en coma flotante — formato desconocido, overflow/underflow. Genera core y termina.
SIGKILL9Kill — termina el proceso irremediablemente; no puede ser ignorada. Genera core y termina.
SIGBUS10Bus error — acceso a dirección inexistente o desalineada. Genera core y termina.
SIGSEGV11Violación de segmento — acceso fuera del segmento de datos. Genera core y termina.
SIGSYS12Argumento erróneo en llamada al sistema. No se usa.
SIGPIPE13Escritura en tubería sin lector — el proceso lector terminó de forma anormal. Termina el proceso.
SIGALRM14Alarm clock — un temporizador del proceso llega a cero. Termina el proceso.
SIGTERM15Finalizació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.
SIGUSR116Señal de usuario 1 — reservada para el programador, sin uso estándar. Termina el proceso por defecto.
SIGUSR217Señal de usuario 2 — idéntica en propósito a SIGUSR1.
SIGCLD/SIGCHLD18*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).
SIGPWR19Fallo de alimentación — avisa de una caída inminente, o notifica tras recuperarse de una; el proceso debe poder restaurar pérdidas.
La función 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:

#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).

Procesos zombie

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.

Terminación de procesos: 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.

Pendiente — cuestionario de la Práctica 3