1. Un programa, dos ejecuciones
Empecemos con un programa en C muy corto. Dice quién es, y después incrementa tres veces una variable global, contador, mostrando su valor y la dirección de memoria donde está guardada, con una pausa de un segundo entre cada paso:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
int contador = 0;
int main(void)
{
printf("Soy el proceso %d, hijo de %d\n", (int) getpid(), (int) getppid());
for (int i = 0; i < 3; i++) {
contador++;
printf("[%d] contador = %d, guardado en %p\n",
(int) getpid(), contador, (void *) &contador);
sleep(1);
}
return 0;
}
Hay dos funciones nuevas, las dos de <unistd.h>: getpid devuelve el número que identifica al proceso que la llama, y getppid, el del proceso que lo creó. Devuelven un pid_t, un tipo entero cuyo tamaño exacto no fija el estándar; por eso se convierte a int antes de mostrarlo con %d. Las veremos en detalle en el tema 3. Lo compilamos y lo ejecutamos una vez:
$ gcc -std=c17 -Wall -Wextra -o proceso proceso.c
$ ./proceso
Soy el proceso 460, hijo de 331
[460] contador = 1, guardado en 0x579055cbb014
[460] contador = 2, guardado en 0x579055cbb014
[460] contador = 3, guardado en 0x579055cbb014
Nada sorprendente. Pero ahora lo lanzamos dos veces a la vez, las dos en segundo plano con &, y le pedimos a la shell que espere a ambas con wait:
$ echo $$
579
$ ./proceso & ./proceso & wait
Soy el proceso 580, hijo de 579
[580] contador = 1, guardado en 0x5b3e5f4d6014
Soy el proceso 581, hijo de 579
[581] contador = 1, guardado en 0x5fc139064014
[580] contador = 2, guardado en 0x5b3e5f4d6014
[581] contador = 2, guardado en 0x5fc139064014
[580] contador = 3, guardado en 0x5b3e5f4d6014
[581] contador = 3, guardado en 0x5fc139064014
Mira con atención. Hay un solo archivo, proceso, pero se están ejecutando dos cosas distintas, con números distintos, 580 y 581. Las dos avanzan al mismo tiempo, con sus líneas intercaladas. Y cada una tiene su propio contador: ninguna llega a 6, las dos cuentan 1, 2, 3 por su cuenta, aunque el código dice que contador es una única variable global. Las dos, además, son «hijas» de 579, que según $$ es la propia shell.
Esas dos cosas que se ejecutan son procesos. El resto del curso trata de que son exactamente, como los crea el sistema, como se reparten la máquina y como terminan.
2. Programa y proceso
Un programa es un archivo que contiene instrucciones y datos iniciales, guardado en el disco. Un proceso es un programa en ejecución: una instancia de ese programa, con su propia memoria, su propio estado de la CPU y los recursos que el sistema operativo le asignó, que el núcleo identifica con un número, el PID (process identifier).
La diferencia es la misma que hay entre una receta y alguien cocinándola. La receta es un texto fijo, que puede estar guardado durante años sin que pase nada, y del que pueden hacerse muchas copias. Cocinar es una actividad: tiene ingredientes concretos sobre la mesada, va por un paso determinado, ocupa la cocina durante un tiempo y en algún momento termina. Dos personas pueden seguir la misma receta a la vez en dos cocinas, y cada una tendrá su propia olla.
| Programa | Proceso | |
|---|---|---|
| Que es | Un archivo, como /usr/bin/bash o ./proceso | Una actividad en curso, que el núcleo administra |
| Dónde vive | En el disco | En la memoria principal y en la CPU |
| Cuánto dura | Hasta que alguien borra el archivo | Desde que se crea hasta que termina |
| Cambia | No: es siempre el mismo | Sí: sus variables, su posición en el código y su estado cambian a cada instante |
| Cuántos hay | Uno por archivo | Tantos como ejecuciones simultáneas de ese archivo |
| Se identifica por | Su ruta en el sistema de archivos | Su PID |
Por eso se dice que un programa es una entidad pasiva y un proceso una entidad activa. El archivo proceso del ejemplo sigue en el disco, idéntico, después de que los procesos 580 y 581 terminaron; ellos, en cambio, ya no existen.
3. Lo que el sistema agrega a un programa
Para convertir el archivo en algo que se ejecuta, el núcleo tiene que preparar muchas cosas que el archivo no contiene. Este esquema muestra el mismo programa y los dos procesos del ejemplo:
Cada proceso recibe del sistema, como mínimo:
- Un espacio de memoria propio, con una copia de las zonas del programa y otras que el archivo no tiene: el montículo, de donde sale la memoria que pide
malloc, y la pila, donde viven las variables locales y las llamadas a funciones. Es el tema 2. - Un estado de la CPU: los valores de los registros del procesador, entre ellos el contador de programa, que indica cuál es la próxima instrucción a ejecutar. Es lo que permite que dos procesos estén en puntos distintos del mismo código.
- Recursos del sistema: los archivos abiertos, empezando por la entrada, la salida y el error estándar, el directorio de trabajo, el usuario con cuyos permisos se ejecuta, las variables de entorno. Es el tema 5.
- Una identidad y un lugar en el sistema: el PID, el PID del padre que lo creó, y un estado, que dice si está ejecutándose, esperando algo o terminado. Son los temas 3, 6 y 7.
El núcleo guarda toda esa información sobre cada proceso en una estructura llamada bloque de control de proceso, o PCB, que veremos en el tema 4. Para el sistema operativo, en cierto sentido, un proceso es su PCB: la ficha donde está escrito todo lo que hace falta para detenerlo y reanudarlo exactamente donde estaba.
4. Cada proceso tiene su propia memoria
En la ejecución doble había un detalle llamativo: las direcciones de contador eran distintas en cada proceso, 0x5b3e5f4d6014 y 0x5fc139064014. Podría pensarse que por eso cada uno tiene su propio contador: están en lugares distintos. Pero las direcciones cambian por ASLR, la protección que coloca la memoria de cada proceso en un lugar al azar, que conociste en el curso de C. La orden setarch -R ejecuta un programa con ASLR desactivado, y permite hacer el experimento definitivo:
$ setarch -R ./proceso & setarch -R ./proceso & wait
Soy el proceso 536, hijo de 535
[536] contador = 1, guardado en 0x555555558014
Soy el proceso 537, hijo de 535
[537] contador = 1, guardado en 0x555555558014
[536] contador = 2, guardado en 0x555555558014
[537] contador = 2, guardado en 0x555555558014
[537] contador = 3, guardado en 0x555555558014
[536] contador = 3, guardado en 0x555555558014
Ahora los dos procesos guardan contador en la misma dirección, 0x555555558014, y aun así cada uno ve su propio valor. Dos datos distintos en la misma dirección solo son posibles porque esa dirección no es una posición física de la memoria RAM, sino una dirección dentro del espacio de direcciones de cada proceso: una vista de la memoria privada, que el núcleo y el hardware traducen, por separado para cada proceso, a posiciones físicas diferentes.
Cada proceso se ejecuta como si tuviera toda la memoria de la máquina para él solo, empezando en las mismas direcciones que cualquier otro. No puede ver, ni mucho menos modificar, la memoria de los demás procesos: para él, sencillamente, no existe. Esa es la base del aislamiento entre programas. Como se logra, con la memoria virtual y las tablas de páginas, es el tema del curso de Gestión de memoria; aquí nos alcanza con saber que cada proceso tiene su propio espacio de direcciones.
Observa también el orden de las últimas dos líneas: en esta ejecución el 537 terminó su tercer paso antes que el 536, al revés que en las líneas anteriores. Ninguno de los dos procesos decide cuándo se ejecuta; lo decide el sistema, y el orden puede cambiar de una vez a otra. Un programa nunca debe depender de que otro proceso vaya más rápido o más lento que él.
5. Una CPU para muchos procesos
La máquina de los ejemplos tiene 16 núcleos de CPU, de modo que los dos procesos podían ejecutarse literalmente al mismo tiempo, cada uno en un núcleo. Pero un sistema tiene casi siempre muchos más procesos que núcleos. ¿Como se reparten? Para verlo, este programa no hace otra cosa que usar la CPU, en un bucle que nunca termina:
#define _POSIX_C_SOURCE 200809L
int main(void)
{
volatile unsigned long vueltas = 0;
for (;;) /* nunca espera nada: solo usa la CPU */
vueltas++;
}
El volatile impide que el compilador elimine el bucle por inútil. Lanzamos tres copias, y con taskset -c 0 las obligamos a usar todas el mismo núcleo, el número 0. Después de cinco segundos le preguntamos a ps en que núcleo está cada una (PSR) y que porcentaje de CPU usó (%CPU):
$ gcc -std=c17 -Wall -Wextra -o giro giro.c
$ taskset -c 0 ./giro & taskset -c 0 ./giro & taskset -c 0 ./giro &
$ sleep 5; ps -o pid,psr,pcpu,stat,comm -C giro
PID PSR %CPU STAT COMMAND
476 0 33.2 R+ giro
477 0 33.2 R+ giro
478 0 33.2 R+ giro
$ grep ctxt /proc/476/status
voluntary_ctxt_switches: 3
nonvoluntary_ctxt_switches: 419
$ pkill giro
Cada proceso cree estar ejecutándose sin parar, pero recibió exactamente un tercio de la CPU. Lo que ocurrió es que el núcleo los fue alternando: ejecutó uno durante unos milisegundos, lo detuvo, guardó su estado, cargó el estado de otro y lo dejó avanzar, y así sucesivamente. La línea nonvoluntary_ctxt_switches: 419 cuenta las veces que el proceso 476 fue detenido de esa forma, sin pedirlo, en esos cinco segundos: unas ochenta y cuatro veces por segundo.
Esa operación de guardar el estado de un proceso y cargar el de otro se llama cambio de contexto, y tiene su tema propio, el 9. Decidir a quién le toca el turno, y durante cuánto tiempo, es la tarea del planificador, y es el tema del curso siguiente, planificación de la CPU.
Igual que con la memoria, cada proceso se escribe como si tuviera un procesador propio, que ejecuta sus instrucciones una tras otra sin interrupciones. En realidad comparte los núcleos con todos los demás procesos, y puede ser detenido entre dos instrucciones cualesquiera. El sistema guarda y restaura su estado con tanto cuidado que el proceso no puede notarlo, salvo porque el reloj avanzó más de lo esperado.
6. Por que el sistema necesita procesos
Los primeros computadores ejecutaban un solo programa a la vez, de principio a fin, y el programa era dueño de la máquina entera. La idea de proceso aparece cuando se quiere tener varios programas en marcha al mismo tiempo, y resuelve, de una vez, varios problemas:
| Problema | Cómo lo resuelve el proceso |
|---|---|
| Aprovechar la CPU. Un programa que espera el disco, la red o el teclado deja la CPU sin hacer nada durante millones de ciclos. | Mientras un proceso espera, el sistema ejecuta otro. Es la multiprogramación: tener varios procesos cargados para que la CPU siempre tenga trabajo. |
| Atender a varios usuarios y tareas a la vez. Editar, compilar y escuchar música al mismo tiempo; o cien usuarios conectados a un servidor. | El tiempo compartido alterna los procesos tan rápido que cada uno responde como si estuviera solo. |
| Proteger a unos programas de otros. Un error en un programa no debería romper a los demás, ni un programa debería poder leer los datos de otro. | Cada proceso tiene su propio espacio de direcciones y sus propios permisos: el aislamiento. Si un proceso falla, el sistema lo termina a él solo. |
| Llevar la cuenta de los recursos. Saber quién usa cuánta memoria y cuánta CPU, y poder ponerle límites. | El proceso es la unidad a la que se asignan recursos, se cobran tiempos y se aplican límites. Es lo que top muestra, fila por fila. |
| Controlar lo que se ejecuta. Poder detener, reanudar o terminar un programa concreto. | Cada proceso tiene un PID con el que se lo puede nombrar, por ejemplo para enviarle una señal con kill. |
Visto así, un sistema operativo es, en buena parte, un administrador de procesos: los crea, les reparte la CPU y la memoria, los protege unos de otros, les presta servicios a través de las llamadas al sistema y limpia lo que dejan al terminar. El proceso es la abstracción que hace todo eso posible: oculta los detalles de la máquina real y le ofrece a cada programa una máquina más simple, propia y protegida.
7. Ver los procesos del sistema
En el curso de terminal usaste ps y top para ver procesos. Ahora conviene mirarlos con otros ojos. Si mientras se ejecutan dos copias de proceso pedimos a ps los procesos de esta terminal, con su PID, el PID de su padre y su estado:
$ ./proceso > /dev/null & ./proceso > /dev/null &
$ ps -o pid,ppid,stat,comm
PID PPID STAT COMMAND
331 330 Ss+ bash
468 331 S+ proceso
469 331 S+ proceso
471 331 R+ ps
Aparecen cuatro procesos: la shell, bash, con PID 331; las dos copias de proceso; y el propio ps, que también es un proceso mientras se ejecuta. Los tres últimos tienen como padre a 331: la shell los creó a todos. La columna STAT muestra el estado: S es sleeping, dormido, porque los dos proceso pasan casi todo el tiempo dentro de sleep(1); R es running, en ejecución, porque ps estaba trabajando cuando se miró a sí mismo. Los estados son el tema del bloque 2.
Si se pide la lista completa, con ps -e, aparecen todos los procesos del sistema, y pstree los dibuja como lo que son, un árbol en el que cada proceso cuelga de su padre:
$ ps -e --no-headers | wc -l
27
$ ps -p 1 -o pid,ppid,comm
PID PPID COMMAND
1 0 systemd
$ pstree -p
systemd(1)-+-cron(151)
|-dbus-daemon(152)
|-login(332)---bash(402)
|-rsyslogd(184)-+-{rsyslogd}(217)
| |-{rsyslogd}(218)
| `-{rsyslogd}(219)
|-systemd-journal(44)
|-systemd-logind(169)
|-systemd-resolve(65)
|-systemd-udevd(88)
…
Esta máquina, un ubuntu mínimo dentro de WSL, tiene 27 procesos; un escritorio con un navegador abierto tiene fácilmente varios cientos. En la raíz del árbol está el proceso 1, systemd, el primero que crea el núcleo al arrancar y el antepasado de todos los demás. Los nombres entre llaves, como {rsyslogd}, no son procesos sino hilos dentro del proceso rsyslogd: el servicio de registro trabaja con cuatro hilos que comparten su memoria. Los hilos son el tema del bloque 7.
8. El directorio /proc
¿De dónde saca ps esa información? En linux, el núcleo publica los datos de cada proceso como si fueran archivos, en un sistema de archivos virtual montado en /proc. Cada proceso tiene un directorio con el nombre de su PID. Para mirarlo con calma, compilamos una versión de proceso.c con el bucle alargado a 30 pasos, proceso30, y la dejamos corriendo en segundo plano:
$ ./proceso30 > /dev/null &
[1] 440
$ ls /proc
1 221 428 acpi dma kallsyms meminfo scsi tty
171 224 429 buddyinfo driver kcore misc self uptime
172 233 430 bus dynamic_debug key-users modules slabinfo version
173 294 431 cgroups execdomains keys mounts softirqs vmallocinfo
177 332 440 cmdline fb kmsg mpt stat vmstat
…
$ ls /proc/440
arch_status cpuset ksm_merging_pages mountinfo oom_score_adj sessionid syscall
attr cwd ksm_stat mounts pagemap setgroups task
auxv environ latency mountstats patch_state smaps timens_offsets
cgroup exe limits net personality smaps_rollup timers
clear_refs fd loginuid ns projid_map stack timerslack_ns
cmdline fdinfo map_files numa_maps root stat uid_map
comm gid_map maps oom_adj sched statm wchan
coredump_filter io mem oom_score schedstat status
$ grep -E '^(Name|State|Pid|PPid|Threads|VmRSS)' /proc/440/status
Name: proceso30
State: S (sleeping)
Pid: 440
PPid: 427
VmRSS: 1792 kB
Threads: 1
$ ls -l /proc/440/exe /proc/440/cwd
lrwxrwxrwx 1 ana ana 0 oct 15 23:06 /proc/440/cwd -> /home/ana/procesos
lrwxrwxrwx 1 ana ana 0 oct 15 23:06 /proc/440/exe -> /home/ana/procesos/proceso30
$ grep -E '^(Name|Pid|PPid)' /proc/self/status
Name: grep
Pid: 448
PPid: 427
En /proc conviven los directorios numerados, uno por proceso, con archivos de información general del sistema, como cpuinfo, meminfo o uptime. Ninguno existe en el disco: cada vez que se los lee, el núcleo genera su contenido a partir de lo que sabe en ese instante. Dentro del directorio de un proceso, status resume su nombre, su estado, su PID, el de su padre, la memoria que ocupa y su número de hilos; cwd apunta a su directorio de trabajo; fd contiene sus archivos abiertos; maps, su mapa de memoria. Y exe es un enlace al archivo del programa que el proceso está ejecutando: el puente entre el proceso y su programa. Si hubiera dos copias de proceso30 en marcha, las dos tendrían su exe apuntando al mismo archivo.
La última orden usa un nombre especial, /proc/self, que para cada proceso que lo lee apunta a su propio directorio. Por eso el resultado no habla de la shell sino de grep, el proceso 448, que es quien abrió el archivo. Un programa en C puede así consultar sus propios datos abriendo /proc/self/status como cualquier archivo. Usaremos /proc a lo largo de todo el curso para mirar dentro de los procesos que creemos.
9. Cuando programa y proceso no coinciden
En el ejemplo del principio, cada proceso ejecutaba un programa y cada ejecución creaba un proceso. Esa es la situación más común, pero no la única, y el curso recorre todas las demás:
| Situación | Ejemplo | Temas |
|---|---|---|
| Un programa, muchos procesos | Dos terminales abiertas son dos procesos bash del mismo archivo, cada uno con sus variables y su historial. | 1, 11 |
| Un proceso que crea otro igual a sí mismo | fork duplica al proceso que la llama: el hijo empieza con una copia de la memoria del padre. | 11–15 |
| Un proceso que cambia de programa | exec reemplaza el programa de un proceso por otro, sin cambiar su PID. Así ejecuta la shell cada orden. | 16–19 |
| Un proceso con varios hilos | El rsyslogd de antes: un solo proceso, con una sola memoria, y cuatro hilos de ejecución dentro. | 28–36 |
| Una aplicación hecha de muchos procesos | Un navegador moderno separa cada sitio en procesos distintos, para que un error en una pestaña no afecte a las demás. | 39 |
| Un proceso terminado que todavía existe | Un hijo que terminó, pero cuyo padre aún no recogió su código de salida: un zombi. | 24, 25 |
Por eso conviene no confundir nunca las dos palabras. Una aplicación, como el navegador o un servidor web, es lo que el usuario ve; un programa es un archivo; y un proceso es lo que el sistema operativo administra.
10. Cómo trabajaremos
Este curso se apoya en Programación en C para sistemas operativos, y sigue sus mismas costumbres. Todos los ejemplos están probados en linux, con gcc 15 y glibc 2.43, y sirven igual en una máquina real, una máquina virtual o WSL. Además de las herramientas de desarrollo, usaremos algunas para observar procesos:
$ sudo apt install build-essential gdb strace procps psmisc
# procps trae ps, top y pgrep; psmisc trae pstree y killall
Y cuatro reglas que valen para todos los programas del curso:
- Compilar con el estándar y las advertencias:
gcc -std=c17 -Wall -Wextra, y-g -fsanitize=address,undefinedmientras se desarrolla. gcc - Pedir la interfaz POSIX: casi todas las funciones del curso, como
getpid,forkowaitpid, son de POSIX y no del estándar de C. Por eso cada programa empieza con#define _POSIX_C_SOURCE 200809L, antes de cualquier#include, como aprendiste en el curso de C. POSIX - Comprobar cada llamada al sistema:
fork,exec,waitypthread_createpueden fallar, y un programa que no lo comprueba se comporta de forma muy difícil de entender. Los ejemplos mínimos del principio omiten algunas comprobaciones para que se lea la idea; a partir del bloque 3 las haremos siempre. errores - Observar desde afuera: cada programa que cree procesos o hilos se mira con
ps,pstree,/procystracemientras se ejecuta. Ver lo que el sistema hizo es la mejor forma de entender por que lo hizo. observar
Los PID, las direcciones de memoria y el orden en que se intercalan las líneas de procesos simultáneos son distintos en cada ejecución y en cada máquina. Cuando repitas los ejemplos no obtendrás exactamente las mismas salidas, y eso es parte de lo que se aprende: un programa con varios procesos o hilos no se ejecuta siempre en el mismo orden.
11. El mapa del curso
Los 43 temas avanzan desde la idea de proceso hasta la decisión de usar procesos o hilos en un programa real. Cada bloque responde una pregunta:
| Bloque | Temas | La pregunta que responde |
|---|---|---|
| 1. El concepto de proceso | 1–5 | ¿Que es un proceso, que tiene en su memoria y que sabe el núcleo de él? |
| 2. Estados y cambio de contexto | 6–10 | ¿Por que estados pasa un proceso, y como se reparten la CPU? |
| 3. Crear procesos con fork | 11–15 | ¿Como nace un proceso nuevo, y que recibe de su padre? |
| 4. Ejecutar programas con exec | 16–19 | ¿Como pasa un proceso a ejecutar otro programa? |
| 5. Esperar y terminar | 20–23 | ¿Como termina un proceso, y como se entera su padre? |
| 6. Zombis, huérfanos y demonios | 24–27 | ¿Que pasa cuando padre e hijo no terminan en el orden esperado? |
| 7. Hilos y la biblioteca pthreads | 28–33 | ¿Como se divide un proceso en varios hilos que comparten la memoria? |
| 8. Hilos en profundidad | 34–36 | ¿Que problemas trae compartir la memoria, y que datos son de cada hilo? |
| 9. Procesos frente a hilos en la práctica | 37–40 | ¿Cuándo conviene cada modelo en servidores, navegadores y videojuegos? |
| 10. Integración y cierre | 41–43 | ¿Como se usa todo junto en una shell y en un programa paralelo? |
Los bloques 1 y 2 son sobre todo conceptuales, aunque con muchos experimentos; desde el bloque 3 en adelante, cada tema se construye alrededor de programas en C que crean y controlan procesos e hilos.
12. Resumen
- Un programa es un archivo pasivo en el disco; un proceso es un programa en ejecución, una entidad activa que el núcleo administra e identifica con un PID.
- Un mismo programa puede ejecutarse en varios procesos a la vez, y cada uno tiene sus propias variables: los dos
procesocontaban por separado. - Para ejecutar un programa, el sistema le agrega un espacio de memoria con montículo y pila, un estado de la CPU con su contador de programa, recursos como los archivos abiertos, un padre y un estado.
- El núcleo guarda todo lo que sabe de un proceso en su bloque de control de proceso, el PCB.
- Cada proceso tiene su propio espacio de direcciones: la misma dirección, en dos procesos, guarda datos distintos.
- Cuando hay más procesos que núcleos, el sistema los alterna con cambios de contexto: tres procesos en un núcleo recibieron un tercio de la CPU cada uno.
- El proceso resuelve la multiprogramación, el tiempo compartido, el aislamiento, la contabilidad de recursos y el control de lo que se ejecuta.
ps,pstreeytopmuestran los procesos; todos forman un árbol cuya raíz es el proceso 1,systemd./proc/PIDexpone los datos de cada proceso como archivos;/proc/PID/exeapunta al programa que ejecuta, y/proc/selfal propio proceso.- Programa y proceso no siempre coinciden:
forkduplica procesos,execcambia el programa de un proceso, y un proceso puede tener varios hilos.
13. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Que diferencia hay entre un programa y un proceso?
Un programa es un archivo con instrucciones y datos iniciales, guardado en el disco; no cambia ni hace nada por sí mismo. Un proceso es una ejecución concreta de ese programa: tiene su propia memoria, su estado de la CPU, sus recursos y un PID, cambia a cada instante y deja de existir cuando termina.
2. Al ejecutar dos copias de proceso a la vez, ninguna llegó a contar hasta 6. ¿Por que, si contador es una variable global?
Porque cada proceso tiene su propia memoria. La variable es global dentro del programa, pero cada proceso tiene su propia copia de ella, en su propio espacio de direcciones, y lo que uno modifica no afecta al otro.
3. Con setarch -R, los dos procesos mostraron la misma dirección para contador pero valores distintos. ¿Como es posible?
Porque esas direcciones no son posiciones físicas de la RAM, sino direcciones dentro del espacio de direcciones de cada proceso. El núcleo y el hardware traducen la misma dirección a posiciones físicas distintas para cada proceso.
4. Nombra cuatro cosas que el sistema le agrega a un programa al convertirlo en proceso.
Por ejemplo: un espacio de memoria propio, con montículo y pila; el estado de los registros de la CPU, incluido el contador de programa; recursos como los archivos abiertos, el directorio de trabajo y el usuario; un PID, un padre y un estado.
5. ¿Que es el PCB, y por que se dice que para el núcleo un proceso «es» su PCB?
El bloque de control de proceso es la estructura donde el núcleo guarda toda la información de un proceso: su PID, su estado, sus registros, su memoria, sus archivos abiertos. Con esa información el núcleo puede detener al proceso y reanudarlo después exactamente donde estaba, así que todo lo que el sistema sabe y hace con un proceso pasa por ella.
6. Tres procesos giro en un mismo núcleo recibieron un 33 % de CPU cada uno. ¿Que hizo el sistema para repartirla?
Los alternó: ejecutó cada uno durante unos milisegundos, lo detuvo, guardó su estado y cargó el de otro. Esa operación es el cambio de contexto, y nonvoluntary_ctxt_switches en /proc/PID/status cuenta las veces que un proceso fue detenido así sin pedirlo.
7. ¿Que es la multiprogramación, y que problema resuelve?
Es tener varios procesos cargados en memoria para que, cuando uno espera una operación lenta como leer el disco o recibir datos de la red, la CPU pueda ejecutar otro en lugar de quedar sin trabajo.
8. En la salida de ps, ¿por que los dos proceso aparecían con estado S y el propio ps con R?
Porque los dos proceso pasaban casi todo el tiempo dentro de sleep(1), dormidos esperando que pasara el segundo, mientras que ps estaba ejecutándose en el momento en que leyó su propio estado.
9. ¿Que es /proc/PID/exe, y que muestra sobre la relación entre programa y proceso?
Es un enlace, generado por el núcleo, al archivo del programa que el proceso está ejecutando. Muestra que el proceso y el programa son cosas distintas pero relacionadas: varios procesos pueden tener su exe apuntando al mismo archivo.
10. En pstree aparecían nombres entre llaves, como {rsyslogd}. ¿Que son?
Son hilos, no procesos: líneas de ejecución dentro del proceso rsyslogd que comparten su memoria. El servicio es un único proceso con cuatro hilos.
Para practicar
- Compila
proceso.cy ejecuta tres copias a la vez con./proceso & ./proceso & ./proceso & wait. Comprueba que los tres tienen el mismo padre y que ese padre es el valor deecho $$. - Cambia el bucle de
proceso.cpara que dure 30 pasos. Mientras se ejecutan dos copias, compara desde otra terminalls -l /proc/PID/exede cada una y el valor deVmRSSen/proc/PID/status. - Repite el experimento de
girocon dos copias en el núcleo 0, y después con cuatro. ¿Que porcentaje recibe cada una? ¿Y si lanzas dos copias sintaskset? Observa la columnaPSR. - Ejecuta
topy busca los procesosgiromientras se ejecutan. Pulsa1para ver cada núcleo por separado. Termínalos al final conpkill giro. - Cuenta los procesos de tu sistema con
ps -e --no-headers | wc -ly dibuja su árbol conpstree -p. Encuentra el camino desde el proceso 1 hasta la shell en la que estás trabajando. - Escribe un programa que abra
/proc/self/statusconfopeny muestre solo las líneas que empiezan conName,Pid,PPidyState. Ejecútalo dos veces y compara los resultados.