1. Qué es el PCB
El bloque de control de proceso, o PCB (process control block), es la estructura de datos en la que el núcleo guarda toda la información que necesita para administrar un proceso: su identidad, su estado, su posición en la CPU, su memoria, sus archivos abiertos, sus señales pendientes, su padre y sus hijos, sus tiempos de ejecución y sus límites de recursos. Mientras el proceso vive, el núcleo mantiene un PCB para él; cuando el proceso termina, el PCB se libera.
La idea aparece en los primeros sistemas operativos por una necesidad práctica: el núcleo tiene que poder detener un proceso en cualquier instante y reanudarlo más tarde exactamente donde estaba. Para eso necesita recordar, en algún lugar, todo lo que el proceso estaba haciendo y todo lo que poseía. Ese lugar es el PCB. Sin él, el núcleo no sabría como retomar un proceso después de haberle quitado la CPU.
Por eso se dice que para el núcleo un proceso «es» su PCB: todo lo que el sistema hace con un proceso —ejecutarlo, suspenderlo, enviarle una señal, cobrarle tiempo de CPU, matarlo— pasa por leer o modificar la estructura que lo representa. El programa que corre en el espacio de usuario no ve el PCB directamente; solo ve los efectos de lo que el núcleo hace con él.
Piensa en el PCB como la ficha de un paciente en un hospital: el nombre, el estado, el tratamiento, la habitación, el médico asignado. El paciente es la persona, pero para el hospital lo que importa es la ficha, porque es lo que permite atenderlo, moverlo y darlo de alta. El PCB es la ficha del proceso en el sistema operativo.
2. Para que lo usa el núcleo
El núcleo consulta y modifica el PCB en muchos momentos de la vida de un proceso. Los principales son:
| Momento | Que hace el núcleo con el PCB |
|---|---|
| Creación (fork) | Reserva un PCB nuevo, le copia los datos del padre y le asigna un PID nuevo. |
| planificación | Lee el estado y la prioridad para decidir que proceso ejecutar a continuación. |
| Cambio de contexto | Guarda los registros de la CPU en el PCB del proceso que sale y los restaura desde el PCB del que entra. |
| Llamada al sistema | Usa el PCB para saber que proceso pide el servicio, con que permisos y que recursos tiene. |
| Señales | Anota las señales pendientes y los manejadores registrados en el PCB. |
| Memoria | Consulta las tablas de páginas y los límites de memoria asociados al proceso. |
| Terminación (exit) | Marca el estado como terminado, guarda el código de salida y avisa al padre. |
| Recogida (wait) | El padre lee el código de salida del PCB del hijo y el núcleo libera la estructura. |
Todos esos accesos ocurren en modo núcleo, mientras el proceso está ejecutándose o mientras el núcleo decide que proceso ejecutar. El programa del usuario nunca toca el PCB: solo ve las consecuencias, como el valor que devuelve getpid() o el hecho de que un malloc haya funcionado.
3. Los campos de un PCB
Aunque la implementación varía entre sistemas, todo PCB guarda, como mínimo, la siguiente información. Se puede agrupar en seis categorías:
Identidad
- PID: el número que identifica al proceso.
- PPID: el PID del padre.
- UID y GID: el usuario y el grupo con cuyos permisos se ejecuta.
- Nombre: el nombre corto del programa (
comm).
Estado y planificación
- Estado: si el proceso está en ejecución, listo, bloqueado, detenido o terminado (temas 6 y 7).
- Prioridad y nice: que tan urgente es ejecutarlo frente a otros.
- Punteros a colas: en que cola de planificación está (cola de listos, cola de espera de un evento).
- Tiempo de CPU usado: cuánto tiempo de procesador ha consumido.
Memoria
- Puntero a las tablas de páginas: la estructura que traduce las direcciones virtuales del proceso a direcciones físicas de la RAM.
- Límites de memoria: cuánta memoria puede usar (stack máximo, heap máximo, etc.).
- Punteros al código, datos, heap y stack: las direcciones de las zonas de su espacio de direcciones (tema 2).
Recursos y archivos
- Tabla de archivos abiertos: los descriptores de archivo (0, 1, 2, y los que abrió con
open). - Directorio de trabajo actual:
cwd. - Directorio raíz: para procesos enjaulados (
chroot). - Variables de entorno: el entorno que heredó al crearse.
- Límites de recursos:
rlimitpara número de archivos, tamaño de archivos, procesos por usuario, etc.
Señales
- Máscara de señales bloqueadas: cuáles no se pueden entregar ahora.
- Señales pendientes: las que llegaron pero aún no se manejaron.
- Manejadores registrados: que función se ejecuta cuando llega cada señal.
Jerarquía y sincronización
- Puntero al padre: para enviarle señales y notificarle cuando termina.
- Lista de hijos: para esperarlos con
waito recorrerlos. - Puntero al grupo de procesos y a la sesión: para el control de la terminal.
- Mutex y semáforos: cuando el proceso usa sincronización.
Es una lista larga, y es deliberadamente así: el PCB es la estructura más importante del núcleo en lo que respecta a la administración de procesos. Todo lo que el sistema necesita saber de un proceso está ahí o se alcanza desde ahí.
4. task_struct en linux
En linux el PCB se llama task_struct, y está definido en el código fuente del núcleo en include/linux/sched.h. Es una de las estructuras más grandes y más importantes del núcleo: tiene miles de líneas de definición y contiene punteros a decenas de otras estructuras. No es un bloque contiguo simple, sino un nodo central que apunta a subestructuras especializadas.
Esta es una versión muy simplificada, con los campos que más se mencionan. Los nombres reales en el código del núcleo son exactamente estos:
struct task_struct {
/* Identidad */
pid_t pid;
pid_t tgid; /* thread group id */
struct task_struct *parent; /* padre */
struct list_head children; /* lista de hijos */
struct list_head sibling; /* hermanos */
/* Estado y planificación */
volatile long state; /* TASK_RUNNING, TASK_INTERRUPTIBLE, ... */
int prio; /* prioridad dinámica */
int static_prio; /* prioridad estática (nice) */
struct sched_entity se; /* datos del planificador */
/* Memoria */
struct mm_struct *mm; /* espacio de direcciones */
struct mm_struct *active_mm; /* mm activo en la CPU */
/* Archivos y sistema de archivos */
struct files_struct *files; /* tabla de archivos abiertos */
struct fs_struct *fs; /* cwd, root, umask */
/* Señales */
struct signal_struct *signal; /* señales compartidas del grupo */
struct sighand_struct *sighand; /* manejadores de señales */
sigset_t blocked; /* señales bloqueadas */
sigset_t pending; /* señales pendientes */
/* Tiempos y contabilidad */
u64 utime; /* tiempo en modo usuario */
u64 stime; /* tiempo en modo núcleo */
struct timespec64 start_time; /* cuándo se creó */
/* Credenciales */
const struct cred *cred; /* UID, GID, capacidades */
/* Registros guardados (al hacer cambio de contexto) */
struct thread_struct thread; /* registros de la CPU */
/* ... cientos de campos más ... */
};
Varias observaciones sobre esta estructura:
- No guarda todo directamente. Campos como
mm,filesosignalson punteros a otras estructuras. Así, cuando dos hilos de un mismo proceso comparten memoria y archivos, apuntan a las mismas subestructuras en lugar de duplicarlas. - El estado es un número, no una cadena. Linux usa constantes como
TASK_RUNNING(0),TASK_INTERRUPTIBLE(1) oTASK_UNINTERRUPTIBLE(2). La letra que muestraps(R, S, D) es una traducción de ese número. - Los registros de la CPU viven en
thread. Cuando el proceso es desalojado, el núcleo guarda ahí el estado de los registros, incluido el contador de programa y el puntero de pila. - La estructura es enorme. En un núcleo moderno,
task_structocupa varios kilobytes. Por eso el núcleo no crea una para cada hilo si no es necesario, y por eso la asignación y liberación de procesos es una operación costosa.
En los libros de texto clásicos, el PCB es una estructura compacta y autocontenida. En linux, task_struct es el PCB, pero también es el descriptor de hilos, el descriptor de tareas del planificador y el ancla de muchas otras estructuras. La idea es la misma; la implementación es la de un sistema operativo real y complejo.
5. El puntero current y el acceso al PCB
Mientras un proceso se está ejecutando en modo núcleo, el código del núcleo necesita saber a que proceso corresponde la ejecución actual. Para eso existe el puntero current, una variable (en realidad una macro) que apunta al task_struct del proceso que está corriendo en la CPU actual.
Por eso, dentro del núcleo, obtener el PID del proceso actual es tan simple como current->pid, y obtener su padre es current->parent->pid. La función getpid() que usa un programa en C termina llamando al núcleo, que devuelve current->tgid (el PID del grupo de hilos, que es el que el usuario ve).
El puntero current se implementa de forma distinta según la arquitectura. En x86-64, se guarda en un registro especial del procesador (el gs base) o se deriva de la pila del núcleo. La idea es que, dada la pila actual, se puede calcular la dirección del task_struct restando un desplazamiento fijo, porque el núcleo reserva la estructura del proceso justo debajo de su pila de núcleo.
Desde el espacio de usuario no se puede acceder a current ni a task_struct. El único camino es pedirle al núcleo que exponga los datos, y eso es exactamente lo que hace /proc: cada archivo de /proc/PID es una ventana controlada a los campos del task_struct correspondiente.
6. El PCB y el cambio de contexto
El cambio de contexto, que viste en el tema 1, es la operación en la que el núcleo detiene un proceso, guarda su estado y carga el de otro. El PCB es el lugar donde se guarda y se restaura ese estado.
Cuando el núcleo desaloja un proceso, hace, entre otras cosas:
- Guarda los registros de la CPU en el campo
threaddeltask_structdel proceso que sale: el contador de programa, el puntero de pila, los registros generales. - Actualiza el estado del proceso en su PCB: de «en ejecución» a «listo» o «bloqueado», según corresponda.
- Cambia las tablas de páginas para que la CPU use las del nuevo proceso: así, las direcciones virtuales del nuevo proceso se traducen a su propia memoria física.
- Carga los registros desde el
task_structdel nuevo proceso. - Actualiza
currentpara que apunte al nuevo proceso. - Salta a la instrucción indicada por el contador de programa restaurado.
Todo esto ocurre en modo núcleo, en unos pocos microsegundos. El proceso que fue desalojado no se entera de nada: cuando vuelve a ejecutarse, sus registros están exactamente como los dejó, y continúa desde la misma instrucción. Esa ilusión de continuidad es la que hace posible el tiempo compartido.
Guardar y restaurar registros, cambiar las tablas de páginas y actualizar las cachés de la CPU (TLB) tiene un costo. Por eso los sistemas operativos intentan minimizar los cambios de contexto innecesarios, y por eso los hilos, que comparten el espacio de direcciones y no requieren cambiar las tablas de páginas, son más baratos de cambiar que los procesos. Es el tema 9.
7. El PCB y la creación de procesos
Cuando se llama a fork(), el núcleo crea un proceso nuevo. Lo que hace, en términos del PCB, es:
- Reserva un
task_structnuevo y un PID nuevo. - Copia los campos del padre al hijo: estado, prioridad, punteros a memoria, archivos abiertos, señales, credenciales. Es una copia superficial: los punteros apuntan a las mismas subestructuras.
- Marca al hijo como distinto: le cambia el PID, le pone el PPID igual al PID del padre, le pone el estado «listo» y le prepara un valor de retorno de
forkde 0. - Crea una copia del espacio de direcciones del padre para el hijo, usando copia en escritura (tema 13). El campo
mmdel hijo apunta a una nueva estructura que comparte las mismas páginas físicas hasta que uno de los dos las modifique. - Agrega el hijo a la lista de hijos del padre y lo pone en la cola de listos.
Por eso fork es relativamente barato: no copia toda la memoria, solo el PCB y las estructuras de control. La memoria se comparte hasta que se modifica. Y por eso el hijo «hereda» tantas cosas del padre: archivos abiertos, directorio de trabajo, variables de entorno, manejadores de señales. Todo eso está en el PCB o en las subestructuras a las que apunta.
Cuando el proceso termina con exit(), el núcleo no libera el PCB inmediatamente: lo marca como «zombi», guarda el código de salida y avisa al padre. El PCB se libera solo cuando el padre llama a wait() y lee ese código. Ese es el tema 24.
8. Consultar el PCB desde /proc
Cómo el usuario no puede tocar task_struct directamente, el núcleo expone sus campos a través de /proc/PID. Cada archivo de ese directorio es una ventana a una parte del PCB o de las subestructuras a las que apunta. Los más directos son:
| Archivo en /proc/PID | Campo del PCB que expone |
|---|---|
status | Nombre, PID, PPID, TGID, UID, GID, estado, hilos, memoria (Vmsize, VmRSS), señales. |
stat | Todos los campos en formato compacto: PID, comm, state, ppid, pgrp, session, tty, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime, cutime, cstime, priority, nice, num_threads, starttime, vsize, rss, rsslim, ... |
statm | Tamaño de memoria en páginas: tamaño total, residente, compartido, texto, datos. |
maps | Las regiones del espacio de direcciones (el campo mm). |
fd/ | La tabla de archivos abiertos (el campo files). |
cwd, root, exe | Directorio de trabajo, raíz y ejecutable (el campo fs). |
environ | Las variables de entorno. |
limits | Los límites de recursos (rlimit). |
sched | Estadísticas del planificador para este proceso. |
io | Estadísticas de entrada/salida. |
wchan | En que función del núcleo está dormido el proceso (si está bloqueado). |
El archivo stat es el más completo para programas: una sola línea con todos los campos numéricos, pensado para ser leído por scripts. El archivo status es el mismo contenido pero en formato legible por humanos, con etiquetas. La mayoría de las herramientas (ps, top) leen stat y status para mostrar la información.
$ cat /proc/self/stat
448 (cat) S 402 402 402 34816 448 4194304 107 0 0 0 0 0 0 0 20 0 1 0 12345 5652480 188 18446744073709551615 94000000000000 940000000000 7ffd... 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0
El primer campo es el PID, el segundo el nombre entre paréntesis, el tercero el estado, el cuarto el PPID. El resto son campos numéricos que corresponden a los campos de task_struct. Por eso ps y top no necesitan llamadas al sistema especiales: solo leen este archivo.
9. PCB en distintos sistemas
La idea de PCB es universal, pero cada sistema la implementa a su manera. Las diferencias principales:
| Sistema | Nombre de la estructura | Características |
|---|---|---|
| Linux | task_struct | Una sola estructura para procesos e hilos. Campos como punteros a subestructuras compartidas. Muy grande y flexible. |
| Windows | EPROCESS y KPROCESS | Dos estructuras separadas: KPROCESS para planificación y memoria, EPROCESS para recursos y seguridad. Los hilos tienen su propio ETHREAD y KTHREAD. |
| macOS / BSD | proc y uthread | Estructura proc para el proceso y uthread para cada hilo. Similar a linux en espíritu. |
| Sistemas clásicos (Tanenbaum) | PCB | Estructura compacta y autocontenida, con todos los campos en un solo bloque. Es el modelo didáctico. |
La diferencia más importante entre linux y windows es que linux usa una sola estructura para procesos e hilos (un hilo es solo un proceso que comparte memoria con otros), mientras que windows los trata como entidades distintas desde el principio. Por eso, en linux, fork y la creación de un hilo usan el mismo mecanismo (clone), y la diferencia está en que se comparte.
10. Un programa que lee su propio PCB
El siguiente programa muestra como un proceso puede consultar su propio PCB a través de /proc. No puede leer task_struct directamente, pero puede leer los archivos que el núcleo genera a partir de ella:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid = getpid();
printf("=== PCB del proceso %d (via /proc) ===\n\n", (int) pid);
/* status: identidad y estado */
char ruta[64];
snprintf(ruta, sizeof ruta, "/proc/%d/status", (int) pid);
FILE *f = fopen(ruta, "r");
if (f) {
char linea[256];
while (fgets(linea, sizeof linea, f)) {
if (strncmp(linea, "Name:", 5) == 0 ||
strncmp(linea, "Pid:", 4) == 0 ||
strncmp(linea, "PPid:", 5) == 0 ||
strncmp(linea, "State:", 6) == 0 ||
strncmp(linea, "Threads:", 8) == 0 ||
strncmp(linea, "Vmsize:", 7) == 0 ||
strncmp(linea, "VmRSS:", 6) == 0) {
fputs(linea, stdout);
}
}
fclose(f);
}
/* stat: tiempos de CPU */
snprintf(ruta, sizeof ruta, "/proc/%d/stat", (int) pid);
f = fopen(ruta, "r");
if (f) {
unsigned long utime, stime;
int p, pp;
char comm[64], state;
/* campos: pid comm state ppid ... utime stime ... */
fscanf(f, "%d %63s %c %d", &p, comm, &state, &pp);
/* saltar hasta utime (campo 14) y stime (campo 15) */
for (int i = 0; i < 9; i++) fscanf(f, "%*s");
fscanf(f, "%lu %lu", &utime, &stime);
printf("\ntiempo en modo usuario: %lu ticks\n", utime);
printf("Tiempo en modo núcleo: %lu ticks\n", stime);
fclose(f);
}
/* limits: límites de recursos */
printf("\n--- Límites de recursos (/proc/PID/limits) ---\n");
snprintf(ruta, sizeof ruta, "/proc/%d/limits", (int) pid);
f = fopen(ruta, "r");
if (f) {
char linea[256];
while (fgets(linea, sizeof linea, f)) {
if (strncmp(linea, "Max open files", 14) == 0 ||
strncmp(linea, "Max processes", 13) == 0 ||
strncmp(linea, "Max stack size", 14) == 0) {
fputs(linea, stdout);
}
}
fclose(f);
}
printf("\ncomprueba con: cat /proc/%d/status\n", (int) pid);
return 0;
}
El programa lee tres archivos de /proc: status para la identidad y el estado, stat para los tiempos de CPU y limits para los límites de recursos. Cada uno de esos datos vive en el task_struct del proceso, y el núcleo los formatea al leerlos. Es un ejemplo de como un programa puede inspeccionar su propio PCB sin privilegios especiales.
11. Resumen
- El PCB es la estructura donde el núcleo guarda todo lo que sabe de un proceso; para el sistema, un proceso «es» su PCB.
- Guarda identidad (PID, PPID, UID), estado y planificación, memoria (tablas de páginas, zonas), recursos (archivos, cwd, entorno, límites), señales y jerarquía (padre, hijos).
- En linux el PCB es
task_struct, una estructura grande que apunta a subestructuras compartidas entre hilos. - El puntero
currentapunta altask_structdel proceso que se ejecuta en la CPU actual. - El cambio de contexto guarda y restaura los registros en el campo
threaddel PCB. forkcopia el PCB del padre y crea un espacio de direcciones nuevo con copia en escritura./proc/PIDexpone los campos del PCB:status,stat,maps,fd,limits, entre otros.- Windows usa
EPROCESS/KPROCESS; Linux y los sistemas tipo unix usan una estructura unificada para procesos e hilos.
12. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Que es el bloque de control de proceso (PCB)?
Es la estructura de datos en la que el núcleo guarda toda la información que necesita para administrar un proceso: identidad, estado, memoria, archivos, señales, tiempos y jerarquía. Mientras el proceso vive, el núcleo mantiene un PCB para él.
2. ¿Por que se dice que para el núcleo un proceso «es» su PCB?
Porque todo lo que el sistema hace con un proceso —ejecutarlo, suspenderlo, enviarle señales, cobrarle tiempo, matarlo— pasa por leer o modificar la estructura que lo representa. El programa del usuario no ve el PCB, solo sus efectos.
3. Nombra los seis grupos de campos de un PCB.
Identidad (PID, PPID, UID), estado y planificación, memoria (tablas de páginas, zonas), recursos y archivos (fd, cwd, límites), señales (bloqueadas, pendientes, manejadores) y jerarquía (padre, hijos, grupo).
4. ¿Que es task_struct y en que sistema se usa?
Es la estructura que implementa el PCB en linux, definida en include/linux/sched.h. Es una de las estructuras más grandes del núcleo y apunta a subestructuras especializadas para memoria, archivos y señales.
5. ¿Que es el puntero current?
Es una variable (en realidad una macro) del núcleo que apunta al task_struct del proceso que se está ejecutando en la CPU actual. Permite al código del núcleo saber a que proceso corresponde la ejecución en curso.
6. ¿Que guarda el núcleo en el PCB durante un cambio de contexto?
Los registros de la CPU del proceso que sale: contador de programa, puntero de pila y registros generales, en el campo thread. Al reanudar el proceso, los restaura desde ahí.
7. ¿Que hace fork con el PCB del padre?
Copia sus campos al PCB del hijo (estado, prioridad, punteros a memoria, archivos, señales, credenciales), le asigna un PID nuevo y le pone como PPID el PID del padre. La memoria se comparte con copia en escritura.
8. ¿Que archivo de /proc expone los tiempos de CPU de un proceso?
/proc/PID/stat, en los campos utime (tiempo en modo usuario) y stime (tiempo en modo núcleo). /proc/PID/status también muestra algunos datos de memoria y estado.
9. ¿En que se diferencia el modelo de PCB de linux del de windows?
Linux usa una sola estructura (task_struct) para procesos e hilos, que comparten subestructuras. Windows separa el proceso (EPROCESS/KPROCESS) de los hilos (ETHREAD/KTHREAD).
10. ¿Por que el PCB no se libera inmediatamente cuando un proceso termina?
Porque el padre necesita leer el código de salida del hijo con wait(). Hasta entonces, el PCB queda como zombi (estado terminado) guardando ese código. Recién cuando el padre lo recoge se libera la estructura.
Para practicar
- Compila y ejecuta
leer_pcb.c, y compara su salida concat /proc/PID/statusycat /proc/PID/stat. - Mientras un programa está corriendo, mira
ls -l /proc/PID/fdycat /proc/PID/limits. - Usa
cat /proc/PID/wchanmientras un proceso está dormido (por ejemplo, dentro desleep) y observa el nombre de la función del núcleo. - Ejecuta
strace -e trace=process ./un_programay observa las llamadas al sistema que crean y modifican el PCB. - Compara el tamaño de
task_structen tu sistema con el comandogrep task_struct /proc/kallsymso revisando el código fuente del núcleo. - Escribe un programa que abra varios archivos y luego imprima el contenido de
/proc/self/fdpara ver la tabla de archivos abiertos.