Nota de estudio: Esta primera parte cubre los fundamentos sobre los que se construye todo lo demás. Los conceptos aquí explicados aparecen directamente en preguntas como la Q1 (comandos Linux:
pwd,sudo,touch,chmod,kill), la Q2 (MFA y autenticación), la Q7 (malware y protección) y la Q9 (permisos NTFS). No son conceptos aislados: la gestión de procesos explicakill; la gestión de permisos explicachmodysudo; la memoria virtual explica cómo el SO aísla los procesos entre sí.
Un sistema operativo (SO) es el conjunto de programas que gestiona los recursos hardware de un ordenador y proporciona servicios comunes a los programas de aplicación. Es la capa de software que se sitúa entre el hardware físico y las aplicaciones que usan los usuarios.
Sin sistema operativo, cada programador tendría que gestionar directamente el hardware: controlar el disco sector a sector, programar las interrupciones del teclado, gestionar la memoria byte a byte. El SO elimina esa complejidad y ofrece abstracciones de alto nivel.
El SO tiene tres roles simultáneos e inseparables:
1. Gestor de recursos
Controla y distribuye de forma eficiente la CPU, la memoria RAM, el almacenamiento y los dispositivos de E/S entre todos los procesos que compiten por ellos. Evita conflictos, garantiza equidad y optimiza el uso del hardware. Sin esta gestión, dos programas podrían intentar escribir al mismo tiempo en el mismo sector del disco y corromperse mutuamente.
2. Máquina extendida (o máquina virtual)
Oculta la complejidad del hardware real y ofrece a los programadores una interfaz abstracta, uniforme y mucho más sencilla. Un programa no necesita saber si el disco es NVMe, SATA o una unidad de red montada con NFS; el SO siempre presenta la misma llamada read(). Un programador no necesita saber cuántos núcleos tiene el procesador; el SO gestiona la distribución de sus hilos automáticamente.
3. Árbitro
Resuelve conflictos entre procesos que quieren acceder al mismo recurso. Garantiza aislamiento (proceso A no puede leer la memoria de proceso B), seguridad (un proceso sin privilegios no puede modificar ficheros del sistema) y equidad (ningún proceso acapara la CPU indefinidamente).
Analogía para recordarlo: El SO es como el director de un aeropuerto. Los aviones (aplicaciones) no se comunican directamente con las pistas, el combustible ni el control de tráfico (hardware). El director (SO) coordina todo: asigna pistas, gestiona tiempos, resuelve conflictos. Un avión no sabe nada del hardware del aeropuerto; solo sabe que puede despegar y aterrizar.
Todos los temas del temario descansan sobre el SO:
- Las redes son servicios que el SO expone (sockets, pila TCP/IP en el kernel).
- La seguridad (MFA, permisos, cortafuegos) es en gran parte gestión de accesos del SO.
- El almacenamiento (NAS, RAID, backups) es gestión del sistema de archivos del SO.
- Los comandos Linux son programas que realizan llamadas al sistema (system calls) al kernel.
Entender el SO es entender el substrato sobre el que funciona todo lo demás.
Esta distinción es fundamental y puede aparecer directamente en el examen.
Un programa es un fichero ejecutable almacenado en disco (estático, pasivo). Por ejemplo, /usr/bin/firefox en Linux o C:\Program Files\Firefox\firefox.exe en Windows. El programa no hace nada por sí solo; es simplemente código e instrucciones guardadas.
Un proceso es ese programa cargado en memoria y en ejecución (dinámico, activo). Tiene estado propio: qué instrucción está ejecutando ahora mismo, qué valores tienen sus variables, qué ficheros tiene abiertos. Puedes tener un programa (un único fichero en disco) del que se estén ejecutando simultáneamente múltiples procesos. Por ejemplo, puedes abrir dos instancias del navegador Firefox al mismo tiempo: hay un solo programa pero dos procesos distintos, cada uno con su propio estado.
Componentes internos de un proceso en memoria:
Espacio de direcciones de un proceso (memoria virtual)
┌─────────────────────────────────┐ ← dirección alta
│ Stack (pila) │ Crece hacia abajo ↓
│ (variables locales, │ Al llamar a una función se añade
│ parámetros, dir. retorno) │ un "stack frame"; al retornar se elimina
├─────────────────────────────────┤
│ ↕ │ Espacio libre entre stack y heap
├─────────────────────────────────┤
│ Heap │ Crece hacia arriba ↑
│ (memoria dinámica: │ malloc() / new en C/C++
│ malloc, new, etc.) │ El programador la pide y la libera
├─────────────────────────────────┤
│ Datos (data segment) │ Variables globales e inicializadas
│ BSS (variables = 0) │ Variables globales no inicializadas
├─────────────────────────────────┤
│ Código (text segment) │ Instrucciones del programa
│ (solo lectura) │ Solo lectura para evitar automodificación
└─────────────────────────────────┘ ← dirección baja (0x0)
¿Por qué el stack crece hacia abajo y el heap hacia arriba? Ambos "crecen" hacia el espacio libre del medio. Esto maximiza el uso del espacio de direcciones: si pusieran ambos en el mismo extremo, colisionarían mucho antes.
PCB (Process Control Block): el SO mantiene por cada proceso una estructura de datos en el kernel llamada PCB (o descriptor de proceso) que contiene TODO lo necesario para pausar y reanudar un proceso:
Conexión directa con el examen: El comando
kill(pregunta 1.e) opera precisamente sobre procesos identificados por su PID. Cuando ejecutaskill 1234, el SO busca el PCB con PID=1234 y envía una señal a ese proceso.
Un proceso no siempre está ejecutando. Durante su vida pasa por varios estados bien definidos. Conocer estos estados y sus transiciones es esencial:
admitido (fork/exec)
[ NUEVO ] ──────────────────────────────► [ LISTO ]
│ ▲
despachado │ │ interrupción de tiempo
(scheduler) │ │ (quantum agotado) o
│ │ proceso de mayor prioridad
▼ │
[ EJECUTANDO ]
│ │
espera E/S │ │ llamada exit()
o evento │ │ o señal SIGKILL
▼ ▼
[ BLOQUEADO ] [ TERMINADO ]
│
E/S completada │
o evento ocurrido │
▼
[ LISTO ]
Explicación de cada transición:
| Transición | Quién la provoca | Qué ocurre |
|---|---|---|
| Nuevo → Listo | El SO acepta el proceso | El proceso se crea con fork() + exec() y se añade a la cola de listos |
| Listo → Ejecutando | El planificador (scheduler) | El dispatcher carga el contexto del proceso en la CPU |
| Ejecutando → Listo | Una interrupción de tiempo | El quantum del proceso se agotó; el SO lo expulsa y elige otro (multitarea preventiva) |
| Ejecutando → Bloqueado | El propio proceso | El proceso pide una operación de E/S (leer del disco, esperar entrada de red); no tiene sentido que ocupe la CPU si va a esperar |
| Bloqueado → Listo | Un evento externo | La operación de E/S completó (interrupción del dispositivo); el proceso puede continuar, pero debe esperar su turno en la CPU |
| Ejecutando → Terminado | El propio proceso o señal | El proceso llama a exit(), o el SO le envía SIGKILL |
Ejemplo concreto con el navegador web:
1. Abres Firefox → SO crea proceso (Nuevo → Listo).
2. El planificador le asigna CPU (Listo → Ejecutando).
3. Haces clic en un enlace; Firefox pide datos al servidor de red (Ejecutando → Bloqueado).
4. Mientras espera, la CPU ejecuta otros procesos.
5. Llega la respuesta del servidor (Bloqueado → Listo).
6. Firefox vuelve a ejecutar y renderiza la página (Listo → Ejecutando).
7. Cierras Firefox (Ejecutando → Terminado).
Un hilo (thread, también llamado proceso ligero) es la unidad mínima de ejecución dentro de un proceso. Mientras el proceso define el espacio de recursos (memoria, ficheros, conexiones), el hilo define el flujo de ejecución dentro de ese espacio.
Un proceso puede tener múltiples hilos ejecutando simultáneamente (en varios núcleos) o de forma concurrente (turnándose en un solo núcleo). Todos los hilos de un proceso comparten:
- El espacio de direcciones (código, heap, variables globales).
- Los ficheros abiertos.
- Los sockets de red.
- Las señales pendientes.
Pero cada hilo tiene lo suyo propio:
- Su propio stack (pila de llamadas): cada hilo está en un punto diferente de la ejecución, con sus propias funciones activas y variables locales.
- Sus propios registros de CPU (especialmente el contador de programa): cada hilo está en una instrucción diferente.
- Su propio estado (listo, ejecutando, bloqueado).
- Su propio TID (Thread ID).
Tabla comparativa proceso vs hilo:
| Aspecto | Proceso | Hilo |
|---|---|---|
| Espacio de memoria | Propio, completamente aislado | Compartido con todos los hilos del proceso |
| Recursos (ficheros, sockets) | Propios | Compartidos con el proceso |
| Coste de creación | Alto: hay que crear nuevo espacio de direcciones, copiar tablas de páginas, etc. | Bajo: solo hay que crear un nuevo stack (~KB) |
| Coste de cambio de contexto | Alto: hay que cambiar la MMU (tablas de páginas) | Bajo: las tablas de páginas son las mismas |
| Comunicación entre ellos | Costosa: requiere IPC (pipes, sockets, memoria compartida con syscalls) | Barata: acceden directamente a variables compartidas en el heap |
| Aislamiento ante fallos | Alto: un proceso que falla no afecta a otros | Bajo: un hilo que corrompe la memoria compartida puede tumbar todo el proceso |
| Ejemplo de uso | Procesos del sistema operativo independientes (nginx, sshd, cron) |
El navegador usa hilos para: un hilo renderiza, otro gestiona red, otro responde al usuario |
Ejemplo práctico: Un servidor web como Apache o Nginx recibe miles de peticiones simultáneas. En lugar de crear un proceso nuevo por cada petición (costoso), crea un hilo nuevo por cada petición. Todos los hilos comparten la configuración del servidor y el pool de conexiones a la base de datos, pero cada uno gestiona su petición de forma independiente.
Modelos de hilos (importante para entender la implementación):
Modelo 1:1: cada hilo de usuario tiene su propio hilo de kernel. Linux (pthreads) y Windows usan este modelo. El kernel puede planificar cada hilo en un núcleo diferente → aprovecha multiprocesamiento. Desventaja: crear muchos hilos es costoso porque implica crear estructuras en el kernel.
Modelo N:1: todos los hilos de usuario de un proceso se mapean a un único hilo de kernel. El SO solo ve un hilo. Ventaja: cambio de contexto entre hilos muy rápido (todo en espacio de usuario). Desventajas graves: si un hilo se bloquea en una syscall (p.ej. leer del disco), se bloquean todos los hilos del proceso; no aprovecha múltiples núcleos. Prácticamente en desuso.
Modelo N:M: N hilos de usuario se mapean a M hilos de kernel (M ≤ N). El SO puede planificar varios hilos en paralelo y si uno se bloquea en E/S, los demás continúan. El más flexible pero el más complejo de implementar. Usado en Solaris (histórico) y algunas implementaciones especializadas.
La planificación (scheduling) es el mecanismo por el cual el SO decide qué proceso o hilo usa la CPU en cada momento. Es uno de los componentes más críticos del SO porque afecta directamente al rendimiento percibido por el usuario y a la eficiencia del sistema.
En un sistema con un solo núcleo, solo puede ejecutar un proceso a la vez. Pero el sistema puede tener decenas o cientos de procesos en estado "listo". La planificación decide el orden y la duración de cada turno. En sistemas multinúcleo, hay que decidir además qué proceso va en qué núcleo.
Planificador a largo plazo (Job Scheduler o Admisión):
- Decide qué procesos de la cola de trabajos pendientes se admiten al sistema (pasan al estado "listo").
- Controla el grado de multiprogramación: cuántos procesos hay simultáneamente en memoria.
- Se ejecuta con poca frecuencia (cuando un proceso termina o se crea uno nuevo).
- En los SO modernos de escritorio este nivel es casi transparente; existe más claramente en sistemas batch de alto rendimiento (HPC).
Planificador a corto plazo (CPU Scheduler):
- Decide qué proceso de la cola de "listos" pasa a "ejecutando".
- Se ejecuta muy frecuentemente: cada vez que el proceso actual se bloquea, termina o agota su quantum (cada ~10-100 ms).
- Debe ser muy rápido: si tarda 10 ms en decidir y el quantum es 100 ms, el 10% del tiempo se pierde solo en planificación.
Dispatcher:
- Ejecuta físicamente el cambio de contexto: guarda los registros del proceso que sale (en su PCB), carga los registros del proceso entrante (desde su PCB), cambia el modo de protección de memoria.
- El tiempo que tarda el dispatcher en hacer esto se llama latencia de despacho y es overhead puro (tiempo en que ningún proceso útil está ejecutando).
- En hardware moderno es del orden de microsegundos.
Para comparar algoritmos de planificación se usan estas métricas:
| Métrica | Definición | Objetivo | Quién se beneficia |
|---|---|---|---|
| Utilización de CPU | % del tiempo que la CPU ejecuta código útil (no está ociosa ni en overhead) | Maximizar (idealmente ~100%) | Centro de datos (pagan por la CPU) |
| Throughput | Número de procesos completados por unidad de tiempo | Maximizar | Sistemas batch |
| Tiempo de retorno (turnaround) | Tiempo desde que el proceso se crea hasta que termina (incluye esperas) | Minimizar | Usuarios que esperan resultados |
| Tiempo de espera | Tiempo total que el proceso pasa en la cola de "listo" sin ejecutar | Minimizar | Todos los procesos |
| Tiempo de respuesta | Tiempo desde que el proceso hace una petición hasta que empieza a recibir respuesta | Minimizar | Sistemas interactivos (escritorio, web) |
Nota importante: estos objetivos a veces son contradictorios. Maximizar el throughput puede perjudicar el tiempo de respuesta interactivo. Los SO de escritorio priorizan el tiempo de respuesta; los servidores batch priorizan el throughput.
Funcionamiento: la cola de "listos" es una cola FIFO. El proceso que llegó primero es el primero en ejecutar. No preventivo: una vez que la CPU se asigna, el proceso la retiene hasta que termina o se bloquea voluntariamente.
Implementación: trivial. Una simple cola FIFO.
Ventajas:
- Simplicidad absoluta.
- Sin inanición: todo proceso ejecuta eventualmente.
- Predecible y justo en el sentido de "por orden de llegada".
Inconvenientes — El efecto convoy:
Si llega primero un proceso de ráfaga muy larga (proceso CPU-intensivo), todos los procesos cortos que lleguen después tienen que esperar. Esto es especialmente grave en sistemas interactivos.
Ejemplo numérico:
| Proceso | Llegada (ms) | Ráfaga CPU (ms) |
|---|---|---|
| P1 | 0 | 24 |
| P2 | 1 | 3 |
| P3 | 2 | 3 |
Diagrama de Gantt con FCFS:
| P1 (24ms) | P2 (3ms) | P3 (3ms) |
0 24 27 30
Tiempos de espera:
- P1: 0 ms (llegó el primero y ejecutó enseguida)
- P2: 23 ms (llegó en t=1, esperó hasta t=24)
- P3: 25 ms (llegó en t=2, esperó hasta t=27)
- Media: (0 + 23 + 25) / 3 = 16 ms ← muy alto, todo por el efecto convoy de P1.
Si el orden de llegada hubiera sido P2, P3, P1: tiempos de espera 0, 2, 4 → media de 2 ms. El mismo conjunto de procesos, resultado radicalmente diferente.
Funcionamiento: cuando la CPU queda libre, de todos los procesos en la cola de "listos" se elige el que tiene la ráfaga de CPU estimada más corta. No preventivo en su versión básica.
Propiedad óptima: SJF es óptimo en cuanto a tiempo de espera medio. No existe ningún otro algoritmo que minimice más el tiempo de espera medio que SJF. Esto es demostrable matemáticamente.
El problema fundamental: ¿cómo saber la duración de la próxima ráfaga?
En la práctica, el SO no puede saberlo con certeza. La solución es estimarla usando la historia del proceso: si un proceso ha tenido ráfagas cortas antes, probablemente la siguiente también lo sea. Se usa una media exponencial ponderada:
τ(n+1) = α × t(n) + (1 - α) × τ(n)
t(n) es la duración real de la ráfaga n, τ(n) es la estimación anterior, y α (0 ≤ α ≤ 1) pondera cuánto importa la historia reciente vs el pasado lejano.
Inconvenientes:
- Inanición de procesos largos: si continuamente llegan procesos cortos, los largos nunca ejecutan.
- La estimación puede ser incorrecta.
Variante preventiva: SRTF (Shortest Remaining Time First)
Cuando llega un nuevo proceso, si su ráfaga estimada es menor que el tiempo restante del proceso en ejecución, el proceso actual es expulsado y el nuevo empieza a ejecutar.
Funcionamiento: a cada proceso se le asigna un número de prioridad. La CPU siempre se da al proceso listo de mayor prioridad. Puede ser preventiva (si llega uno de mayor prioridad, expulsa al actual) o no preventiva.
¿Cómo se asignan las prioridades?
- Manualmente por el administrador: en Linux, el comando nice y renice ajustan la prioridad de un proceso (valores de -20 a +19; más negativo = más prioridad).
- Automáticamente por el SO según el comportamiento: procesos interactivos (que liberan la CPU frecuentemente para hacer E/S) reciben mayor prioridad; procesos batch (que usan la CPU continuamente) reciben menor prioridad.
El problema de la inanición (starvation):
Si los procesos de alta prioridad nunca dejan de llegar, los de baja prioridad nunca ejecutan. Hay un caso famoso del SO IBM del MIT donde un proceso de baja prioridad llevaba esperando desde 1973; cuando apagaron el sistema en 1983, aún no había ejecutado.
Solución: envejecimiento (aging)
La prioridad de un proceso aumenta gradualmente cuanto más tiempo lleva esperando en la cola. Así, aunque inicialmente tenga baja prioridad, con el tiempo subirá hasta ejecutar. Los SO modernos implementan esto automáticamente.
Conexión con Linux: El planificador CFS (Completely Fair Scheduler) de Linux no usa exactamente prioridades estáticas, sino que acumula "tiempo de CPU en deuda" por proceso. Los procesos que han recibido menos CPU tienen mayor urgencia. Es un sistema de envejecimiento continuo implícito.
Funcionamiento: la cola de "listos" es circular. Cada proceso recibe un quantum de tiempo (también llamado time slice) fijo, típicamente entre 10 y 100 ms. Cuando el quantum se agota, el proceso es expulsado automáticamente y puesto al final de la cola. El siguiente proceso de la cola recibe su quantum.
Es preventivo por diseño: el proceso no puede retener la CPU más de un quantum.
Garantía de tiempo de respuesta máximo: si hay N procesos y el quantum es Q, ningún proceso espera más de (N-1)×Q ms antes de recibir su próximo turno de CPU. Esto es crucial para sistemas interactivos.
La elección del quantum es crítica:
Si el quantum es demasiado pequeño (p.ej. 1 ms):
- Los procesos parecen ejecutar "simultáneamente" (ilusión de paralelismo convincente).
- Pero el cambio de contexto (guardar/restaurar registros, cambiar MMU) tarda quizás 0,1 ms.
- Overhead = 0,1/1,1 = ~9% del tiempo solo en cambios de contexto. Ineficiente.
Si el quantum es demasiado grande (p.ej. 1000 ms = 1 segundo):
- Muy poco overhead de cambio de contexto.
- Pero el sistema parece lento: si hay 10 procesos, puedes esperar hasta 9 segundos para tu turno. Inaceptable para interactividad.
Regla empírica: el quantum debe ser mayor que el 80% de las ráfagas de CPU típicas, para que la mayoría de los procesos interactivos (que solo necesitan unos pocos ms de CPU entre operaciones de E/S) terminen su ráfaga antes de que el quantum expire. Los SO modernos usan 10-100 ms.
Ejemplo con quantum = 4 ms:
| Proceso | Llegada | Ráfaga total |
|---|---|---|
| P1 | 0 | 10 ms |
| P2 | 0 | 4 ms |
| P3 | 0 | 5 ms |
Diagrama de Gantt:
| P1(4) | P2(4) | P3(4) | P1(4) | P3(1) | P1(2) |
0 4 8 12 16 17 19
Compara con FCFS para los mismos procesos (todos llegan en t=0, orden P1,P2,P3):
P1=0ms, P2=10ms, P3=14ms. Media = 8 ms.
RR mejora el tiempo medio y es mucho más justo.
Funcionamiento: la cola de "listos" se divide en varias colas independientes según el tipo de proceso, y cada cola tiene su propio algoritmo de planificación:
Cola 1 (prioridad más alta): Procesos del sistema → FCFS
Cola 2: Procesos interactivos → Round Robin (Q=8ms)
Cola 3: Procesos interactivos → Round Robin (Q=16ms)
por lotes
Cola 4 (prioridad más baja): Procesos batch → FCFS
El planificador siempre atiende la cola de mayor prioridad antes de bajar a la siguiente. Un proceso de la cola 4 solo ejecuta si las colas 1, 2 y 3 están vacías.
Limitación: los procesos no cambian de cola. Un proceso clasificado como "batch" al crearse siempre será batch, aunque su comportamiento cambie.
Funcionamiento: como Multilevel Queue pero los procesos pueden moverse entre colas según su comportamiento observado. Es el algoritmo más flexible y el que más se parece a lo que implementan los SO modernos en la práctica.
Reglas típicas:
- Un proceso nuevo empieza en la cola de mayor prioridad (se asume que es interactivo).
- Si agota su quantum sin bloquearse (comportamiento de proceso CPU-intensivo), baja a la siguiente cola (menor prioridad, quantum mayor).
- Si se bloquea antes de agotar su quantum (comportamiento interactivo: hace E/S frecuente), sube o mantiene alta prioridad.
- Los procesos que llevan mucho tiempo en colas bajas suben periódicamente (aging contra starvation).
¿Por qué funciona bien?
- Procesos interactivos (navegador, editor de texto): hacen pocas ms de CPU y luego esperan entrada del usuario → siempre están en colas altas → respuesta rápida.
- Procesos batch (compilación, cálculo científico): usan la CPU mucho rato sin interrupciones → bajan a colas bajas → ejecutan en segundo plano sin molestar.
El sistema se autoajusta sin que el administrador tenga que clasificar manualmente cada proceso.
Dato técnico: El planificador CFS (Completely Fair Scheduler) de Linux, introducido en la versión 2.6.23 (2007), no usa exactamente una multilevel feedback queue, sino un árbol rojo-negro ordenado por "tiempo virtual de CPU consumido". Pero el principio es el mismo: los procesos que han consumido menos CPU tienen prioridad sobre los que han consumido más, y se favorecen los procesos interactivos.
Los procesos son entidades aisladas por diseño (un proceso no puede acceder a la memoria de otro). Pero a veces necesitan colaborar: el navegador web comunica con el proceso de descarga; el servidor web comunica con la base de datos; la shell comunica con los comandos que ejecuta (ls | grep txt | wc -l).
El SO proporciona mecanismos de IPC (Inter-Process Communication) para esto:
Un pipe es un canal de comunicación unidireccional entre dos procesos, gestionado por el kernel. Tiene un extremo de escritura y uno de lectura. Los datos fluyen en un solo sentido como agua en una tubería.
El buffer del pipe vive en el kernel (no en la memoria de ningún proceso). El proceso escritor llama a write(); el proceso lector llama a read(). Si el buffer está lleno, el escritor se bloquea; si está vacío, el lector se bloquea.
# Ejemplo en bash: la salida de ls se convierte en la entrada de grep
ls -la | grep ".txt"
# El shell crea dos procesos (ls y grep) y conecta stdout de ls con stdin de grep mediante un pipe
Pipes con nombre (named pipes / FIFOs): los pipes normales solo conectan procesos padre-hijo (relacionados). Los named pipes tienen un nombre en el sistema de ficheros y cualquier proceso puede abrirlos.
Los procesos intercambian mensajes discretos (no un flujo continuo de bytes como los pipes). Cada mensaje tiene un tipo y un contenido. El receptor puede leer mensajes de un tipo específico sin importar el orden de llegada.
Ventaja sobre pipes: el receptor puede priorizar mensajes (leer primero los de tipo "urgente"). Los mensajes son unidades discretas, no hay riesgo de que el receptor lea medio mensaje.
Dos o más procesos mapean la misma región de RAM en sus respectivos espacios de direcciones. A partir de ese momento, escribir en esa región desde un proceso es inmediatamente visible para el otro.
Es el mecanismo IPC más rápido porque no hay llamadas al sistema en cada comunicación (una vez mapeada, leer/escribir es tan rápido como acceder a RAM normal). La desventaja es que la sincronización es responsabilidad del programador: si dos procesos escriben simultáneamente, puede haber condiciones de carrera. Se usa junto con semáforos.
Ejemplo de uso: las bases de datos como PostgreSQL usan memoria compartida entre su proceso principal y los procesos worker para compartir el buffer pool (caché de páginas de disco).
No sirven para transmitir datos, sino para sincronizar acceso a recursos compartidos. Un semáforo es una variable entera con dos operaciones atómicas:
- P / wait / down: decrementa el semáforo. Si el resultado es negativo, el proceso se bloquea.
- V / signal / up: incrementa el semáforo. Si había procesos bloqueados, despierta uno.
Semáforo binario (mutex): inicializado a 1. Solo permite que un proceso a la vez esté en la sección crítica. Es la implementación de la exclusión mutua.
Proceso A: Proceso B:
sem.wait() // P: toma el lock
[sección crítica] sem.wait() // P: se bloquea (sem=0)
sem.signal() // V: libera
[sección crítica] // ahora B puede entrar
sem.signal()
Canal de comunicación bidireccional que puede conectar procesos en la misma máquina (Unix domain sockets) o en máquinas diferentes a través de la red (TCP/IP sockets).
Los sockets son la base de toda comunicación en red: HTTP, HTTPS, SSH, DNS... son todos protocolos que usan sockets TCP o UDP por debajo.
En la misma máquina: el servidor web Nginx comunica con el intérprete PHP-FPM mediante Unix domain sockets. Más rápido que TCP local porque no hay overhead de red.
En la red: el navegador crea un socket TCP hacia el puerto 80 o 443 del servidor web para pedir páginas.
Las señales son notificaciones asíncronas que el SO puede enviar a un proceso para informarle de un evento. El proceso puede definir un manejador (signal handler) para cada señal, o aceptar el comportamiento por defecto.
Señales más importantes en Linux:
| Señal | Número | Comportamiento por defecto | Puede capturarse/ignorarse |
|---|---|---|---|
| SIGTERM | 15 | Terminación elegante | Sí |
| SIGKILL | 9 | Terminación forzosa e inmediata | No (no puede bloquearse) |
| SIGINT | 2 | Terminación (Ctrl+C en terminal) | Sí |
| SIGHUP | 1 | Colgar (cerrar terminal) / recargar config | Sí |
| SIGSEGV | 11 | Segmentation fault (acceso inválido de memoria) | Sí (para logging) |
| SIGCHLD | 17 | Proceso hijo terminó | Sí |
Relevancia directa con el examen (pregunta 1.e):
-kill 1234envía SIGTERM (15) → pide al proceso que termine limpiamente (cierre ficheros, libere recursos).
-kill -9 1234okill -SIGKILL 1234envía SIGKILL → el kernel destruye el proceso inmediatamente, sin darle oportunidad de limpiar. Útil cuando el proceso está colgado y no responde a SIGTERM.
-kill -llista todas las señales disponibles.
- Para enviar señales solo necesitas ser el propietario del proceso, o ser root. Esto es control de acceso del SO aplicado a procesos.
Cuando múltiples procesos o hilos acceden simultáneamente a datos compartidos, pueden surgir problemas muy sutiles y graves.
Ocurre cuando el resultado de una operación depende del orden de ejecución de los procesos, y ese orden no está controlado ni garantizado.
Ejemplo clásico con código:
Proceso A: Proceso B:
saldo = leer_saldo() saldo = leer_saldo() // ambos leen 1000€
saldo = saldo + 500 saldo = saldo + 200
escribir_saldo(saldo) escribir_saldo(saldo)
// A escribe 1500 // B escribe 1200
// B sobreescribe: resultado final = 1200€
// Correcto sería: 1000 + 500 + 200 = 1700€
// Se han "perdido" 500€
Este tipo de bug es especialmente peligroso porque:
- Solo ocurre bajo ciertas condiciones de temporización.
- Es muy difícil de reproducir y detectar.
- En producción puede ocurrir esporádicamente, sin un patrón claro.
¿Por qué ocurre esto? La operación "leer, modificar, escribir" no es atómica. A nivel de instrucciones máquina son tres operaciones separadas, y entre ellas el planificador puede cambiar de proceso.
Una sección crítica es cualquier fragmento de código que accede a datos compartidos que no deben ser modificados concurrentemente. Para que la protección de la sección crítica sea correcta, el mecanismo debe cumplir tres propiedades:
Exclusión mutua (Mutual exclusion): si el proceso Pi está ejecutando en su sección crítica, ningún otro proceso puede estar en su sección crítica al mismo tiempo. Esta es la propiedad fundamental.
Progreso: si ningún proceso está en su sección crítica y hay procesos que quieren entrar, solo los procesos que no están ejecutando en su sección resto pueden participar en la decisión de quién entra, y esta decisión no puede posponerse indefinidamente. En otras palabras: si la sección crítica está libre, alguien debe poder entrar.
Espera acotada (Bounded waiting): debe existir un límite en el número de veces que otros procesos pueden entrar en sus secciones críticas después de que un proceso haya solicitado entrar en la suya. Esto previene la inanición: garantiza que todo proceso que quiera entrar en la sección crítica, eventualmente lo logrará.
Un deadlock (interbloqueo o abrazo mortal) es una situación en que dos o más procesos esperan indefinidamente un recurso que está siendo retenido por otro proceso del grupo. Ninguno puede avanzar.
Ejemplo visual:
Proceso A tiene: Recurso 1 (impresora)
Proceso A espera: Recurso 2 (escáner)
Proceso B tiene: Recurso 2 (escáner)
Proceso B espera: Recurso 1 (impresora)
→ A espera a B, B espera a A → DEADLOCK: ninguno avanzará jamás
Las 4 condiciones de Coffman — deben cumplirse todas simultáneamente para que haya deadlock:
| # | Condición | Descripción | Ejemplo |
|---|---|---|---|
| 1 | Exclusión mutua | Al menos un recurso es no compartible; solo un proceso puede usarlo a la vez | Una impresora solo puede imprimir un trabajo a la vez |
| 2 | Retención y espera | Un proceso retiene al menos un recurso mientras espera adquirir otros retenidos por otros procesos | El proceso A tiene la impresora y espera el escáner |
| 3 | Sin desalojo | Un recurso solo puede ser liberado voluntariamente por el proceso que lo retiene, no puede arrebatársele | No se puede quitar la impresora a A a la fuerza |
| 4 | Espera circular | Existe una cadena circular P1 espera recurso de P2, P2 espera recurso de P3, ..., Pn espera recurso de P1 | A espera a B, B espera a A |
Si se rompe cualquiera de las cuatro, el deadlock no puede ocurrir. Esto da lugar a las estrategias de prevención.
Las 4 estrategias frente al deadlock:
Estrategia 1 — Prevención: diseñar el sistema para que al menos una condición de Coffman nunca se cumpla.
Estrategia 2 — Evasión (Avoidance): el algoritmo del banquero
Antes de conceder un recurso, el SO verifica si el estado resultante es "seguro". Un estado es seguro si existe al menos una secuencia de ejecución que permite que todos los procesos terminen. Si el estado tras la concesión no es seguro, se pospone la concesión aunque el recurso esté disponible.
Analogía: un banco que antes de aprobar un préstamo verifica que seguirá teniendo suficiente liquidez para atender a todos sus clientes. Si aprobar el préstamo le dejaría sin liquidez suficiente, lo deniega aunque tenga el dinero ahora.
Problema: requiere conocer de antemano el máximo de recursos que cada proceso puede necesitar. En la práctica esto no siempre es posible.
Estrategia 3 — Detección y recuperación:
Se permite que los deadlocks ocurran. El SO ejecuta periódicamente un algoritmo de detección (busca ciclos en el grafo de asignación de recursos). Cuando detecta un deadlock, lo resuelve:
- Matar uno o más procesos del ciclo (con pérdida de trabajo).
- Arrebatar recursos a algunos procesos (rollback: volver a un estado anterior guardado).
Estrategia 4 — Ignorarlo (Ostrich algorithm):
Si los deadlocks son muy raros y el coste de prevenirlos (complejidad, overhead, restricciones) es mayor que el coste de que ocurran ocasionalmente, se ignoran. El usuario reinicia el proceso bloqueado manualmente si lo nota.
Muchos SO de propósito general (Windows, Linux en ciertos subsistemas) adoptan esta estrategia pragmática para algunos tipos de recursos. El nombre "ostrich algorithm" viene de la imagen del avestruz que esconde la cabeza en la arena.
| Deadlock | Starvation | |
|---|---|---|
| Causa | Espera circular de recursos | Siempre hay procesos de mayor prioridad |
| ¿Puede avanzar el proceso? | No: espera algo que nunca llegará | Podría avanzar, pero nunca le toca |
| ¿Implica bloqueo del proceso? | Sí, bloqueado esperando recurso | No necesariamente; está en cola de "listo" pero nunca elegido |
| Solución | Prevención/evasión/detección del deadlock | Aging (envejecimiento de prioridad) |
Ejemplo de starvation: un servidor web bajo carga muy alta siempre tiene peticiones nuevas llegando. Con un algoritmo que prioriza las peticiones más cortas (SJF), una petición que genera un informe largo nunca ejecuta porque siempre llegan antes peticiones más cortas. La solución es el aging: después de esperar cierto tiempo, la prioridad de esa petición sube hasta que finalmente ejecuta.
La gestión de memoria es una de las funciones más complejas y cruciales del SO. Debe resolver simultáneamente problemas aparentemente contradictorios: aislar los procesos entre sí (seguridad) y permitirles compartir código (eficiencia), ejecutar procesos grandes con poca RAM (memoria virtual), y hacerlo todo a una velocidad que no lastre el rendimiento.
Relocalización: cuando el compilador genera un ejecutable, no sabe en qué dirección de RAM se cargará el programa. El SO puede cargar el mismo programa en diferentes posiciones de memoria en diferentes ejecuciones. El hardware (MMU) se encarga de traducir las direcciones del programa a direcciones físicas reales.
Protección: el proceso A no puede leer ni escribir en la memoria del proceso B, ni en la memoria del kernel. Cualquier intento genera un fallo de protección (segmentation fault en Linux, access violation en Windows) y el SO termina el proceso infractor. Esto es fundamental para la estabilidad del sistema: un programa con bugs no puede corromper el SO ni otros programas.
Compartición: a veces es deseable que dos procesos compartan memoria. Ejemplo: 50 instancias del mismo programa (vim) comparten el mismo código ejecutable en memoria (está una sola copia física), pero cada instancia tiene su propio heap y stack. Ahorro enorme de RAM.
Organización lógica: la memoria física es un array lineal de bytes, pero los programas se organizan en módulos, librerías, segmentos. El SO debe gestionar esta estructura.
Organización física: la memoria disponible puede estar fragmentada (muchos trozos libres pequeños pero no uno grande contiguo). El SO debe gestionar la fragmentación eficientemente.
La memoria no es un único bloque homogéneo. Existe una jerarquía de tecnologías con distintos compromisos entre velocidad, capacidad y coste:
Velocidad Capacidad Coste/GB Gestión
┌─────────────────┐
│ Registros │ <1 ns ~KB ----- Compilador/CPU
│ de la CPU │
├─────────────────┤
│ Caché L1 │ ~1 ns ~64 KB ~$1000 Hardware (automático)
├─────────────────┤
│ Caché L2 │ ~5 ns ~256 KB ~$100 Hardware (automático)
├─────────────────┤
│ Caché L3 │ ~20 ns ~8-32 MB ~$10 Hardware (automático)
├─────────────────┤
│ RAM (DRAM) │ ~100 ns ~8-64 GB ~$3 SO + hardware (MMU)
├─────────────────┤
│ SSD NVMe │ ~100 μs ~1-4 TB ~$0.10 SO
├─────────────────┤
│ HDD │ ~10 ms ~4-20 TB ~$0.02 SO
└─────────────────┘
¿Por qué importa para el SO? Cuando la RAM no es suficiente, el SO puede usar el disco como extensión (memoria virtual con swap). Pero acceder al disco es 100.000 veces más lento que acceder a la RAM. Si el SO usa el disco con demasiada frecuencia para gestionar memoria, el sistema se vuelve inutilizablemente lento. El SO debe gestionar este equilibrio con cuidado.
La memoria virtual es la técnica que permite a cada proceso ver un espacio de direcciones propio, continuo y potencialmente mayor que la RAM física disponible.
Sin memoria virtual (sistemas antiguos, algunos embebidos):
- Los procesos usan directamente las direcciones físicas de RAM.
- Si el proceso A ocupa las direcciones 0-10.000 y el proceso B ocupa 10.001-20.000, el proceso A podría acceder por error (o maliciosamente) a la memoria de B leyendo la dirección 10.001.
- Si el proceso C necesita 32 MB pero solo hay un hueco libre de 20 MB, no puede ejecutar aunque haya más RAM libre en otros fragmentos (fragmentación).
Con memoria virtual:
- Cada proceso tiene su propio espacio de direcciones virtual (típicamente 4 GB en 32 bits, 256 TB en 64 bits).
- Las direcciones del proceso (virtuales) se traducen a direcciones físicas mediante la MMU.
- El proceso A y el proceso B ambos pueden tener código en la dirección virtual 0x400000, y son páginas físicas completamente diferentes. No hay conflicto.
- El proceso puede referenciar más memoria de la que hay en RAM; las partes no usadas están en disco.
Ventajas de la memoria virtual:
1. Aislamiento: los procesos no pueden acceder a la memoria de otros (el SO controla las tablas de traducción).
2. Simplifica el modelo de programación: el programador escribe código sin preocuparse de dónde se cargará en RAM.
3. Permite procesos más grandes que la RAM: las partes no usadas están en disco (swap).
4. Compartición eficiente: la misma página física puede estar mapeada en los espacios virtuales de múltiples procesos (librerías compartidas).
La paginación divide tanto el espacio de direcciones virtuales como la RAM física en bloques de tamaño fijo:
La traducción de direcciones:
Una dirección virtual se divide en dos partes:
Dirección virtual de 32 bits:
┌──────────────────────────┬──────────────────┐
│ Número de página (20b) │ Desplazamiento │
│ (qué página virtual) │ (byte dentro de │
│ │ la página, 12b) │
└──────────────────────────┴──────────────────┘
│
│ Buscar en la tabla de páginas del proceso
▼
┌──────────────────────────┬────────────┬─────┐
│ Número de marco (20b) │ presente │ ... │
│ (dónde está en RAM) │ (1/0) │ │
└──────────────────────────┴────────────┴─────┘
│
▼
Dirección física:
┌──────────────────────────┬──────────────────┐
│ Número de marco (20b) │ Desplazamiento │
│ (igual que antes) │ (igual, sin │
│ │ cambiar) │
└──────────────────────────┴──────────────────┘
El bit "presente" en la entrada de la tabla de páginas indica si la página está actualmente en RAM (1) o en disco/swap (0). Si el bit es 0 y el proceso intenta acceder a esa página, ocurre un fallo de página.
La MMU (Memory Management Unit):
Es hardware dedicado dentro del procesador que realiza la traducción de direcciones en cada acceso a memoria, de forma totalmente transparente al proceso y al código que ejecuta. Sin la MMU, la traducción tendría que hacerse por software (miles de veces más lento).
El TLB (Translation Lookaside Buffer):
La tabla de páginas reside en RAM. Sin el TLB, cada acceso a memoria requeriría primero uno o más accesos adicionales a RAM para consultar la tabla de páginas (overhead de 100-300% sobre cada acceso). El TLB soluciona esto.
El TLB es una caché hardware de alta velocidad (dentro de la CPU) que guarda las traducciones de página más recientes. Capacidad típica: 64-512 entradas.
Funcionamiento:
1. La CPU genera una dirección virtual.
2. La MMU busca el número de página en el TLB (nanosegundos).
3. TLB hit: se encuentra la traducción → dirección física lista. Sin acceso adicional a RAM.
4. TLB miss: no se encuentra → la MMU debe consultar la tabla de páginas en RAM → mucho más lento. La nueva traducción se añade al TLB (desalojando la menos usada).
La tasa de acierto del TLB en aplicaciones típicas es >99%, lo que hace que la memoria virtual no suponga una penalización significativa de rendimiento.
Importante: cuando el SO cambia de proceso (cambio de contexto), el TLB debe invalidarse (las traducciones del proceso saliente no son válidas para el proceso entrante). Esto tiene un coste: los primeros accesos del nuevo proceso serán TLB misses. Los procesadores modernos tienen mecanismos (ASID: Address Space ID) para distinguir entradas de diferentes procesos en el TLB y evitar invalidaciones completas.
Cuando un proceso accede a una dirección virtual cuya página no está en RAM (el bit "presente" es 0 en la tabla de páginas), el hardware lanza una excepción de fallo de página (page fault). El control pasa al manejador de fallos de página del kernel.
¿Por qué puede no estar la página en RAM?
1. La página nunca se ha accedido (lazy loading): el SO no carga todas las páginas al iniciar el proceso, solo las que se van necesitando. Así los programas arrancan más rápido.
2. La página fue expulsada a disco (swap): la RAM estaba llena y el SO movió esta página al área de intercambio para liberar espacio.
3. Acceso inválido (segmentation fault): el proceso intenta acceder a una dirección que no tiene asignada. El SO termina el proceso.
Secuencia de manejo de un fallo de página legítimo:
1. El proceso accede a una dirección virtual → MMU detecta bit "presente"=0
2. El hardware genera una interrupción de fallo de página
3. El kernel toma el control y guarda el estado del proceso
4. El kernel determina si el acceso es legítimo:
- Si no es legítimo → envía SIGSEGV al proceso (segfault)
- Si es legítimo → continúa
5. El kernel busca un marco libre en RAM:
- Si hay marcos libres → elige uno
- Si no hay marcos libres → ejecuta el algoritmo de reemplazo para liberar uno
6. El kernel lee la página desde disco (swap o fichero ejecutable) al marco elegido
7. El kernel actualiza la tabla de páginas del proceso (número de marco, bit presente=1)
8. El kernel reanuda el proceso desde la instrucción que falló
(el proceso ni sabe que todo esto ocurrió)
Impacto en el rendimiento: cada fallo de página requiere acceder al disco, que puede tardar 10 ms (HDD) o 100 μs (SSD). Si un proceso genera 100 fallos de página por segundo, pierde 1 segundo completo por segundo en accesos a disco. Por eso es crucial minimizar los fallos de página.
Thrashing: si el SO dedica más tiempo a mover páginas entre RAM y disco que a ejecutar código útil, el sistema entra en thrashing. El síntoma es la CPU al 100% pero el sistema extremadamente lento: el CPU scheduler siempre tiene procesos "listos" pero todos están esperando que sus páginas se carguen desde disco. La solución es reducir el grado de multiprogramación (ejecutar menos procesos simultáneamente) o añadir más RAM.
Cuando no hay marcos libres y hay que cargar una nueva página, el SO debe elegir qué página expulsar. La elección importa mucho: si expulsa una página que se va a necesitar enseguida, causará otro fallo de página inmediatamente.
OPT (Óptimo): expulsa la página que no se usará durante más tiempo en el futuro. Es el óptimo teórico, minimiza los fallos de página. Imposible de implementar en la práctica (requiere conocer el futuro). Útil como referencia para comparar otros algoritmos.
FIFO (First In, First Out):
Expulsa la página que lleva más tiempo en memoria (la que entró primero). Implementación simple: una cola FIFO de marcos.
Problema: Anomalía de Belady. Puede ocurrir que añadir más marcos de RAM aumente el número de fallos de página. Paradójico. Ocurre porque puede que las páginas más antiguas sean las más usadas.
LRU (Least Recently Used):
Expulsa la página que fue accedida hace más tiempo. Se basa en la localidad temporal: si una página no se ha usado recientemente, probablemente tampoco se usará en el inmediato futuro.
Es una buena aproximación al óptimo y no sufre la anomalía de Belady. El problema es la implementación: para saber qué página es la "menos recientemente usada", habría que llevar una marca de tiempo en cada acceso a memoria. Con miles de millones de accesos por segundo, esto es prohibitivamente caro en hardware.
LFU (Least Frequently Used):
Expulsa la página que se ha usado con menos frecuencia. Problema: una página muy usada en el pasado puede quedar retenida aunque ya no sea necesaria.
Algoritmo del Reloj (Clock) / Segunda Oportunidad:
Es la aproximación eficiente de LRU usada en la práctica por la mayoría de SO incluyendo Linux.
Funcionamiento:
- Cada marco tiene un bit de referencia (R) que el hardware pone a 1 cada vez que se accede a la página.
- Los marcos se organizan en un ciclo circular (como las horas de un reloj).
- Cuando hay que expulsar una página, el "puntero del reloj" avanza:
- Si el marco apuntado tiene R=1: se pone R=0 (se le da "segunda oportunidad") y se avanza.
- Si el marco apuntado tiene R=0: se expulsa esa página (no ha sido accedida desde su última oportunidad).
El bit R lo pone el hardware automáticamente en cada acceso, así que no hay overhead por acceso. El algoritmo solo trabaja cuando hay que hacer un reemplazo.
Mientras la paginación divide la memoria en bloques de tamaño fijo sin significado lógico, la segmentación divide la memoria en segmentos de tamaño variable que corresponden a unidades lógicas del programa:
Las direcciones virtuales tienen la forma [número_segmento : desplazamiento].
Ventaja conceptual: cada segmento puede tener sus propios atributos de protección (el segmento de código es solo lectura; el stack es lectura/escritura pero no ejecutable). Más expresivo que la paginación.
Problema: fragmentación externa. Si el segmento de código ocupa 3,5 MB, necesita un hueco contiguo de 3,5 MB en RAM. Con el tiempo, la RAM se llena de huecos de distintos tamaños y puede no haber uno suficientemente grande aunque la suma de huecos sea mayor que 3,5 MB.
En la práctica: los procesadores x86-64 en modo de 64 bits usan segmentación con segmentos que cubren todo el espacio de direcciones (segmentación "plana"), por lo que efectivamente no se usa. La paginación domina en los SO modernos. Algunos SO combinan ambas (segmentación para definir las regiones lógicas del proceso + paginación dentro de cada segmento).
El swapping (intercambio) es la técnica de mover el estado completo de un proceso (todo su espacio de memoria) desde RAM al disco, para liberar RAM para otros procesos. Cuando el proceso vuelve a necesitar la CPU, se carga de vuelta desde disco a RAM.
A diferencia del paging (que trabaja con páginas individuales de 4 KB), el swapping trabaja con procesos completos (que pueden ser de cientos de MB o GB).
Es extremadamente lento: mover 1 GB desde/hacia disco puede tardar varios segundos. Solo se usa cuando el sistema está muy escaso de memoria y no hay otra opción.
Diferencia clave swapping vs paging:
| Swapping | Paging | |
|---|---|---|
| Unidad | Proceso completo | Página (4 KB) |
| Velocidad | Muy lento (segundos) | Lento pero manejable (ms) |
| Granularidad | Todo o nada | Solo las páginas necesarias |
| Uso | Emergencia extrema | Operación normal del SO |
Las siguientes preguntas están diseñadas con el mismo formato, profundidad y enfoque que el examen de referencia. Requieren tanto definición como análisis y casos prácticos.
Pregunta 1
Un administrador de sistemas observa que un proceso con PID 4821 lleva horas consumiendo el 99% de la CPU y no responde. Describe qué comandos Linux utilizarías para identificar el proceso, intentar terminarlo de forma elegante y, si esto falla, forzar su terminación. Explica qué mecanismo del sistema operativo se usa en cada caso y qué señales se envían.
Pregunta 2
Explica la diferencia entre un programa y un proceso. ¿Por qué puede haber múltiples procesos del mismo programa ejecutando simultáneamente? ¿Qué información guarda el SO sobre cada proceso y dónde la almacena? ¿Qué ocurre con esa información cuando el proceso termina?
Pregunta 3
Una empresa tiene un servidor de aplicaciones que procesa tanto peticiones interactivas de usuarios (requieren baja latencia) como trabajos de generación de informes (procesos batch largos). Explica qué algoritmo de planificación de CPU sería más adecuado para este escenario, razonando tu respuesta. ¿Qué inconvenientes tendría usar FCFS en este caso? ¿Y Round Robin con un quantum muy grande?
Pregunta 4
Define el concepto de deadlock (interbloqueo). Enumera y explica las cuatro condiciones de Coffman necesarias para que ocurra. Para cada condición, proporciona una estrategia de prevención que la elimine y evalúa su viabilidad práctica.
Pregunta 5
Explica qué es la memoria virtual y por qué es necesaria en los sistemas operativos modernos. Describe el mecanismo de paginación incluyendo: qué son las páginas y los marcos, cómo funciona la tabla de páginas, qué papel juega la MMU y qué optimización introduce el TLB. ¿Qué ocurre cuando se produce un fallo de página?
Pregunta 6
Diferencia claramente entre proceso e hilo (thread). Una empresa desarrolla un servidor web que debe atender hasta 10.000 conexiones simultáneas. ¿Sería más apropiado usar un proceso por conexión o un hilo por conexión? Razona la respuesta considerando el uso de memoria, el coste de creación y el coste de comunicación entre unidades de ejecución.
Pregunta 7
En el contexto de la gestión de memoria, explica qué es el thrashing, cuándo se produce y cuáles son sus síntomas observables en un sistema. ¿Qué medidas puede tomar el administrador de sistemas para resolver una situación de thrashing? ¿Cómo se relaciona con el algoritmo de planificación de procesos?
Pregunta 8
Un sistema tiene 4 marcos de página disponibles y la siguiente cadena de referencias a páginas: 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5. Calcula el número de fallos de página usando el algoritmo FIFO y el algoritmo LRU. ¿Cuál da mejor resultado en este caso? Explica brevemente por qué LRU suele aproximarse mejor al comportamiento óptimo que FIFO.
Pregunta 9
Explica qué son las condiciones de carrera (race conditions) en el contexto de la programación concurrente. Define qué es una sección crítica y enumera las tres propiedades que debe cumplir cualquier solución para protegerla correctamente. Pon un ejemplo concreto de una condición de carrera que podría ocurrir en un sistema bancario y cómo se evitaría.
Pregunta 10
Compara los mecanismos de IPC (Inter-Process Communication) pipes, memoria compartida y sockets, indicando para cada uno: cómo funciona, en qué casos es más apropiado usarlo y qué ventajas e inconvenientes presenta respecto a los otros. ¿Cuál usarías para comunicar dos procesos en la misma máquina que necesitan transferir grandes volúmenes de datos con la menor latencia posible? Razona la respuesta.
Fin de la Parte 1 — Definición, Funciones Principales y Gestión de Recursos
Continúa en Parte 2: Estructura del Kernel, Arranque, Interfaces de Usuario y Evolución Histórica