Conceptos de Sistemas Operativos de 0 a Hero: indice del curso

Por: Artiko
sistemas-operativosadaelixirconcurrenciaindicecurso

Conceptos de Sistemas Operativos de 0 a Hero: indice del curso

Un sistema operativo es el programa que decide qué se ejecuta, cuándo se ejecuta, con cuánta memoria y con qué permisos. Está debajo de todo lo demás: del navegador, del compilador, del servidor de base de datos, del juego que corre a sesenta cuadros por segundo. Cuando ese programa hace bien su trabajo es invisible, y esa invisibilidad es justamente lo que hace difícil estudiarlo. Este curso lo vuelve visible: desarma las abstracciones una por una, muestra el mecanismo de hardware que hay debajo de cada una y lo reconstruye con código que se compila y se ejecuta.

El recorrido son diecisiete capítulos. Los primeros cinco construyen el vocabulario y la base de hardware. Los siguientes seis desarrollan los mecanismos centrales: procesos, planificación, concurrencia, semáforos, monitores, paso de mensajes y sistemas de archivos. Después vienen tres capítulos de Ada, un lenguaje que expresa la concurrencia como parte del idioma y no como una biblioteca externa, lo que permite leer el mecanismo en el código en lugar de adivinarlo. Y los últimos tres son proyectos completos: una minishell, un procesador concurrente de un millón de registros y un servidor de videojuegos multijugador.

A quién va dirigido

El curso está escrito para gente que sabe programar algo y quiere entender qué pasa debajo. No supone conocimientos previos de sistemas operativos, arquitectura de computadores ni concurrencia: cada uno de esos temas se levanta desde cero dentro del curso.

PerfilPunto de partida esperadoQué obtiene del curso
Estudiante de un ramo de sistemas operativosProgramación básica en cualquier lenguajeCobertura del temario clásico con código ejecutable en vez de solo teoría
Desarrollador de aplicacionesExperiencia web, móvil o de escritorioEntender por qué un proceso se cuelga, por qué un dato se corrompe entre hilos y qué mide realmente el monitor del sistema
Persona autodidacta sin formación formalCuriosidad y capacidad de seguir instrucciones de terminalUna ruta ordenada que no da por sabido nada y llega hasta la implementación
Alguien que prepara entrevistas técnicasEstructuras de datos y algoritmosLos temas que aparecen una y otra vez: procesos frente a hilos, deadlock, planificación, sistemas de archivos
Programador de sistemas embebidos o críticosC o C++Ada como alternativa con concurrencia integrada y verificación en tiempo de compilación

Lo que sí se supone: saber abrir una terminal, moverse entre directorios, editar un archivo de texto y ejecutar un comando. Si alguna de esas cuatro cosas es nueva, conviene resolverla antes de empezar, porque todos los capítulos las usan.

Qué sabrás hacer al terminar

Al final del recorrido, sin ayuda y desde una terminal en blanco, deberías poder:

  • Explicar qué ocurre, instrucción por instrucción, cuando un programa llama a una función de biblioteca que termina en una llamada al sistema, y por qué el procesador cambia de modo para atenderla.
  • Leer la salida de las herramientas de diagnóstico de un sistema real y saber qué significa cada columna: estado del proceso, memoria residente frente a memoria virtual, descriptores abiertos, tiempo de CPU en usuario y en kernel.
  • Reconocer una condición de carrera en un fragmento de código antes de ejecutarlo, señalar la sección crítica y elegir el mecanismo de sincronización adecuado.
  • Implementar desde cero un semáforo, un monitor con variables de condición y un esquema de paso de mensajes, y justificar cuál corresponde a cada problema.
  • Escribir un programa concurrente en Ada usando tareas, entradas, cita y objetos protegidos, y el equivalente en Elixir con procesos y mensajes.
  • Construir un intérprete de comandos que soporte redirecciones, tuberías, ejecución en segundo plano y manejo de señales.
  • Diseñar pruebas que hagan fallar de manera reproducible un error de concurrencia, en lugar de esperar a que aparezca en producción.
  • Levantar un servidor concurrente sobre sockets TCP con estado compartido bajo un único dueño, un ciclo de actualización de frecuencia fija y medición de latencia.

Qué se necesita para partir

Un computador con Linux, macOS o Windows con WSL2. Los ejemplos están escritos y verificados sobre Linux; en macOS casi todo funciona igual salvo detalles de las herramientas de diagnóstico, que se indican en cada capítulo cuando corresponde.

Las herramientas se instalan a medida que se necesitan, no todas de entrada:

HerramientaAparece a partir del capítuloPara qué
Un compilador de C (gcc o clang)4Ejemplos de llamadas al sistema, procesos, hilos y la minishell
Herramientas de diagnóstico del sistema3Observar procesos, memoria, archivos abiertos y llamadas al sistema
GNAT y Alire12Compilar y ejecutar todo el material de Ada
Elixir y Erlang/OTP8Modelo de actores, paso de mensajes y el servidor final
Python 316Versión secuencial de referencia del proyecto de concurrencia
Un editor de texto con resaltado de sintaxis1Todo el curso

En una distribución basada en Debian o Ubuntu, la base mínima para los primeros capítulos se instala así:

sudo apt update
sudo apt install -y build-essential gdb strace ltrace man-db manpages-dev
gcc --version

Para el bloque de Ada, la vía recomendada es Alire, el gestor de paquetes del ecosistema, porque instala una cadena de herramientas propia y evita mezclar versiones con las del sistema:

sudo apt install -y gnat gprbuild
gnatmake --version
gprbuild --version

Y para el bloque de Elixir, cualquier instalación que deje disponibles elixir, iex y mix:

sudo apt install -y erlang elixir
elixir --version
iex --version

Cada capítulo repite la instrucción de instalación de lo que usa, con la verificación correspondiente, así que no hace falta preparar todo de antemano. Lo único imprescindible para abrir el capítulo 1 es una terminal.

Los lenguajes del curso y por qué está cada uno

El curso usa cuatro lenguajes y ninguno está por gusto. Cada uno muestra una parte del tema que los otros esconden.

LenguajeQué muestra que los otros noDónde aparece
CLa llamada al sistema sin capas intermedias: fork, exec, dup2, pipe y las señales tal como las expone el kernelCapítulos 4, 6 y 15
AdaLa concurrencia como parte de la sintaxis: tareas, citas y objetos protegidos escritos como enunciados del lenguajeCapítulos 8, 12, 13, 14, 16 y 17
ElixirEl modelo de actores puro: procesos livianos aislados que solo se comunican por mensajes, sin memoria compartida posibleCapítulos 6, 8, 10, 16 y 17
PythonUna versión secuencial de referencia contra la cual comparar, escrita en un lenguaje que casi todos leenCapítulo 16

El mismo problema resuelto en tres estilos distintos es el recurso didáctico central del bloque de concurrencia. Un contador compartido incrementado por varios flujos se ve así en cada uno.

En C, con hilos POSIX y un mutex, la sincronización es una llamada a biblioteca que rodea al dato:

#include <pthread.h>
#include <stdio.h>

static long contador = 0;
static pthread_mutex_t candado = PTHREAD_MUTEX_INITIALIZER;

static void *sumar(void *arg)
{
    (void)arg;
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&candado);
        contador++;
        pthread_mutex_unlock(&candado);
    }
    return NULL;
}

int main(void)
{
    pthread_t a, b;
    pthread_create(&a, NULL, sumar, NULL);
    pthread_create(&b, NULL, sumar, NULL);
    pthread_join(a, NULL);
    pthread_join(b, NULL);
    printf("contador = %ld\n", contador);
    return 0;
}

Se compila y se ejecuta así:

gcc -O2 -pthread -o contador contador.c
./contador

En Ada, la exclusión mutua no se pide: se declara. El objeto protegido garantiza que solo un flujo esté dentro de sus operaciones a la vez, y eso es parte del tipo:

with Ada.Text_IO; use Ada.Text_IO;

procedure Contador is

   protected Cuenta is
      procedure Incrementar;
      function Valor return Long_Integer;
   private
      Total : Long_Integer := 0;
   end Cuenta;

   protected body Cuenta is
      procedure Incrementar is
      begin
         Total := Total + 1;
      end Incrementar;

      function Valor return Long_Integer is
      begin
         return Total;
      end Valor;
   end Cuenta;

   task type Sumador;

   task body Sumador is
   begin
      for I in 1 .. 100_000 loop
         Cuenta.Incrementar;
      end loop;
   end Sumador;

   A, B : Sumador;

begin
   null;
end Contador;

Se compila y se ejecuta así:

gnatmake contador.adb
./contador

En Elixir no hay dato compartido que proteger: el contador vive dentro de un proceso y los demás le envían mensajes, de modo que la exclusión mutua es una consecuencia del diseño y no un mecanismo añadido:

defmodule Contador do
  def iniciar do
    spawn(fn -> ciclo(0) end)
  end

  defp ciclo(total) do
    receive do
      :incrementar -> ciclo(total + 1)
      {:valor, quien} ->
        send(quien, {:total, total})
        ciclo(total)
    end
  end

  def sumar(pid, cuantas) do
    Enum.each(1..cuantas, fn _ -> send(pid, :incrementar) end)
  end

  def valor(pid) do
    send(pid, {:valor, self()})
    receive do
      {:total, total} -> total
    end
  end
end

pid = Contador.iniciar()

tareas =
  for _ <- 1..2 do
    Task.async(fn -> Contador.sumar(pid, 100_000) end)
  end

Enum.each(tareas, &Task.await(&1, :infinity))
IO.puts("contador = #{Contador.valor(pid)}")

Se ejecuta directamente con el intérprete:

elixir contador.exs

Los tres programas hacen lo mismo y llegan al mismo resultado, pero cada uno responde la pregunta “¿quién impide que dos flujos escriban a la vez?” en un lugar distinto: en C la respuesta está en las dos llamadas que rodean la línea, en Ada está en el tipo del objeto y en Elixir está en que no existe la línea compartida. Los capítulos 8, 9 y 10 desarrollan exactamente esa diferencia.

La ruta de aprendizaje

El curso avanza en cinco bloques temáticos. Cada bloque cierra un tema completo y deja instalado el vocabulario que usa el siguiente. El diagrama muestra las dependencias reales entre capítulos, no solo el orden de lectura.

flowchart TD
    subgraph B1["Bloque 1 · Fundamentos"]
        C1["Cap 1<br/>Qué es un SO"]
        C2["Cap 2<br/>Historia"]
        C3["Cap 3<br/>Roles del SO"]
        C4["Cap 4<br/>Arquitectura"]
        C5["Cap 5<br/>El kernel"]
    end

    subgraph B2["Bloque 2 · Procesos y recursos"]
        C6["Cap 6<br/>Procesos y planificación"]
        C11["Cap 11<br/>Sistemas de archivos"]
    end

    subgraph B3["Bloque 3 · Concurrencia"]
        C7["Cap 7<br/>Problemas de concurrencia"]
        C8["Cap 8<br/>Semáforos"]
        C9["Cap 9<br/>Monitores"]
        C10["Cap 10<br/>Paso de mensajes"]
    end

    subgraph B4["Bloque 4 · Ada"]
        C12["Cap 12<br/>Ada desde cero"]
        C13["Cap 13<br/>Ada práctico"]
        C14["Cap 14<br/>Tareas y concurrencia"]
    end

    subgraph B5["Bloque 5 · Proyectos"]
        C15["Cap 15<br/>Minishell"]
        C16["Cap 16<br/>Concurrencia real"]
        C17["Cap 17<br/>Servidor de juegos"]
    end

    C1 --> C2 --> C3 --> C4 --> C5
    C5 --> C6
    C5 --> C11
    C6 --> C7
    C7 --> C8 --> C9 --> C10
    C6 --> C12
    C12 --> C13 --> C14
    C10 --> C14
    C6 --> C15
    C11 --> C15
    C14 --> C16
    C10 --> C16
    C16 --> C17

Las flechas que cruzan bloques son las que importan al momento de saltarse capítulos. El capítulo 15 necesita procesos, descriptores de archivo y señales, es decir los capítulos 6 y 11. El capítulo 16 necesita los tres mecanismos de sincronización y las tareas de Ada. El capítulo 17 no se sostiene sin el 16.

Los cinco bloques en detalle

Bloque 1: fundamentos (capítulos 1 a 5)

Responde tres preguntas: qué problema resuelve un sistema operativo, cómo llegó a resolverlo de esta manera y sobre qué hardware lo hace. El capítulo 4 es el pivote técnico del bloque, porque introduce los mecanismos físicos —interrupciones, modos de ejecución, jerarquía de memoria— que después se invocan en todos los capítulos posteriores. El capítulo 5 los junta en la figura del kernel.

El resultado concreto de este bloque es poder seguir el camino completo de una petición de un programa al kernel, que es la operación que se repite en todo lo que viene después:

sequenceDiagram
    participant P as Programa (modo usuario)
    participant B as Biblioteca del sistema
    participant K as Kernel (modo privilegiado)
    participant H as Dispositivo

    P->>B: llamada a una función de lectura
    B->>B: coloca número de servicio y argumentos en registros
    B->>K: instrucción que cambia el modo del procesador
    K->>K: valida argumentos y permisos
    K->>H: solicita los datos
    K-->>P: bloquea el proceso mientras espera
    H-->>K: interrupción: datos disponibles
    K->>K: copia los datos al espacio del proceso
    K->>P: retorna al modo usuario con el resultado

Bloque 2: procesos y recursos (capítulos 6 y 11)

El proceso es la abstracción central del área y el sistema de archivos es la abstracción de almacenamiento equivalente. Ambos capítulos comparten una estructura: qué estructura de datos mantiene el kernel, qué operaciones expone al programa y qué se ve desde afuera con herramientas de diagnóstico.

Bloque 3: concurrencia (capítulos 7 a 10)

Es el corazón teórico del curso y el bloque más largo en tiempo de práctica. El capítulo 7 plantea los problemas; los capítulos 8, 9 y 10 presentan las tres familias históricas de solución, en el mismo orden en que aparecieron: semáforos, monitores y paso de mensajes. Cada mecanismo resuelve las limitaciones del anterior, así que leerlos en desorden hace perder el hilo del argumento.

flowchart LR
    P["Problema:<br/>dos flujos tocan<br/>el mismo dato"] --> EA["Espera activa<br/>consume CPU<br/>sin avanzar"]
    EA --> S["Semáforo<br/>contador + cola<br/>wait / signal"]
    S --> LS["Limitación:<br/>el orden de las<br/>operaciones queda<br/>disperso en el código"]
    LS --> M["Monitor<br/>exclusión encapsulada<br/>+ variables de condición"]
    M --> LM["Limitación:<br/>requiere memoria<br/>compartida"]
    LM --> PM["Paso de mensajes<br/>send / receive<br/>sin estado común"]
    PM --> A["Ada: cita y<br/>objetos protegidos"]
    PM --> E["Elixir: procesos<br/>y buzones"]

Bloque 4: Ada (capítulos 12 a 14)

Ada aparece en el curso por una razón concreta: su modelo de concurrencia está en el lenguaje, con sintaxis propia para tareas, citas y objetos protegidos. Eso permite escribir un monitor sin biblioteca externa y leerlo como texto en lugar de reconstruirlo mentalmente a partir de llamadas a funciones. Los capítulos 12 y 13 enseñan el lenguaje desde el primer programa; el 14 es el que conecta con el bloque de concurrencia.

Bloque 5: proyectos (capítulos 15 a 17)

Tres programas completos, de dificultad creciente, que obligan a usar todo lo anterior junto. No son ejercicios ilustrativos: cada uno tiene un enunciado, una implementación completa, pruebas y una discusión de qué falla y por qué.

Tabla resumen del curso

CapítuloQué aprendesCon qué sales
1. IntroducciónQué es un sistema operativo, la máquina desnuda frente a la máquina extendida, el rol de gestor de recursosVocabulario base y un mapa mental de las capas entre tu programa y el hardware
2. HistoriaLotes, monitor residente, spooling, multiprogramación, tiempo compartido, UNIX, Windows, Linux, móvilesCapacidad de explicar por qué los sistemas actuales tienen la forma que tienen
3. Roles del SOGestión de procesos, memoria, archivos y dispositivos; protección; interfaz de llamadas al sistemaEntender qué pide un programa al kernel y cómo lo pide
4. ArquitecturaCiclo de la CPU, registros, jerarquía de memoria, cache, stack y heap, interrupciones, DMA, modo usuario y kernelLa base de hardware que explica el costo de cada operación del sistema
5. El kernelMonolítico, microkernel e híbrido; frontera usuario-kernel; módulos; planificadorSaber qué hay dentro del núcleo y cómo se extiende sin reiniciar
6. Procesos y planificaciónPCB, estados, árbol de procesos, fork, exec, wait, hilos, cambio de contexto, FCFS, SJF, Round Robin, colas multinivelCrear procesos e hilos, y calcular a mano el resultado de un planificador
7. ConcurrenciaConcurrencia frente a paralelismo, condición de carrera, sección crítica, exclusión mutua, deadlock, inanición, problemas clásicosDetectar el error antes de ejecutarlo y nombrar con precisión qué falla
8. SemáforosSemáforos binarios y contadores, wait y signal, atomicidad, patrones de uso y errores típicosImplementar y usar semáforos en Ada y en Elixir sin espera activa
9. MonitoresEncapsulamiento de la exclusión mutua, invariante, variables de condición, semánticas de Hoare y Mesa, la regla del whileEscribir un buffer acotado correcto y saber por qué el if no basta
10. Paso de mensajessend y receive, bloqueo y no bloqueo, direccionamiento directo e indirecto, buzones, cita de Ada, actores de Elixir, cifrado entre procesosComunicar procesos sin memoria compartida y proteger el contenido del canal
11. Sistemas de archivosArchivos, directorios, inodos, punteros indirectos, asignación de bloques, journaling, FAT, ext4, btrfs, ZFS, permisos, montajeExplicar qué ocurre en disco al crear, escribir y borrar un archivo
12. Ada desde ceroHistoria del lenguaje, instalación de GNAT y Alire, archivos .ads, .adb y .gpr, primeros programasCompilar y ejecutar programas Ada propios
13. Ada prácticoArreglos restringidos y no restringidos, contenedores, cadenas, UTF-8, argumentos de línea de comandosUn programa interactivo completo que conversa con el shell
14. Ada concurrenteTareas y tipos de tarea, entradas, accept, select con guardas, objetos protegidos, lanzamiento de procesos del sistemaEscribir concurrencia correcta con sintaxis del lenguaje, no con bibliotecas
15. MinishellCiclo leer-parsear-ejecutar, tokenizador, fork y exec, códigos de salida, redirección con descriptores, tuberías, señalesUn intérprete de comandos propio que ejecuta programas reales
16. Concurrencia realUn millón de registros procesados secuencialmente y de forma concurrente, los tres mecanismos aplicados, pruebas que exponen la carreraUna batería de pruebas que hace fallar el error de forma reproducible
17. Servidor de juegosSockets TCP, estado autoritativo, tick loop de frecuencia fija, latencia y percentiles, predicción y reconciliaciónUn servidor multijugador funcionando y la bibliografía para seguir solo

Los diecisiete capítulos

Capítulo 1

Introducción a los sistemas operativos: la máquina desnuda y la máquina extendida

Punto de partida del curso. Define qué es exactamente un sistema operativo, qué problema resuelve y por qué apareció, contrastando la máquina desnuda que entrega el hardware con la máquina extendida que construye el software de sistema. Introduce el doble rol de máquina extendida y gestor de recursos, la separación entre modo usuario y modo kernel, y deja armado el mapa que recorren los dieciséis capítulos siguientes.

Capítulo 2

Historia de los sistemas operativos: del lote a los sistemas móviles

Recorrido por la evolución del área, desde las máquinas que no tenían software de sistema hasta los teléfonos actuales. Cubre el procesamiento por lotes y el monitor residente, el spooling y las interrupciones, la multiprogramación, el tiempo compartido, el nacimiento de UNIX con su árbol genealógico, la familia Windows, la aparición de Linux y los sistemas móviles. Sirve para entender que casi ninguna decisión de diseño actual es arbitraria: cada una responde a una limitación concreta de su época.

Capítulo 3

Los roles del sistema operativo: árbitro, ilusionista y pegamento

Desarrolla una por una las funciones que cumple el sistema operativo: gestión de procesos, gestión de memoria, sistemas de archivos, control de dispositivos, protección y aislamiento. La segunda mitad del capítulo se dedica a la interfaz de llamadas al sistema: cómo un programa formula una petición, cómo viaja hasta el kernel y cómo vuelve el resultado. Es el capítulo que convierte la palabra “sistema operativo” en una lista concreta de responsabilidades.

Capítulo 4

Arquitectura de computadoras para entender el sistema operativo

El hardware que el sistema operativo administra, explicado desde cero: el ciclo de la CPU, los registros, la jerarquía de memoria con sus cachés, el stack y el heap de un proceso, las interrupciones, el acceso directo a memoria y la separación entre modo usuario y modo kernel. Cada mecanismo se conecta de inmediato con la decisión de diseño del sistema operativo que lo aprovecha, de modo que no queda como teoría suelta de arquitectura.

Capítulo 5

El kernel por dentro: arquitecturas, llamadas al sistema, módulos y planificación

Entra al núcleo del sistema. Explica la frontera entre espacio de usuario y espacio de kernel y qué instrucción del procesador la cruza, las diferencias reales entre kernel monolítico, microkernel e híbrido con sus compromisos de rendimiento y aislamiento, el ciclo de vida de los módulos cargables y el papel del planificador dentro del núcleo. Cierra el bloque de fundamentos.

Capítulo 6

Procesos y planificación: del bloque de control al reparto del procesador

Anatomía completa del proceso: qué guarda el bloque de control, cómo se mueve por sus estados, cómo se forma el árbol de procesos y qué hacen exactamente fork, exec y wait. Estudia los hilos frente a los procesos, el mecanismo real del cambio de contexto y los algoritmos clásicos de planificación —FCFS, SJF, SRTF, Round Robin, prioridades y colas multinivel— con trazas calculadas paso a paso e implementaciones en C, Ada y Elixir.

Capítulo 7

Concurrencia y sus problemas: condiciones de carrera, sección crítica, interbloqueo e inanición

Separa concurrencia de paralelismo y muestra por qué un programa correcto en secuencial deja de serlo cuando dos flujos tocan el mismo dato. Desarma una condición de carrera hasta el nivel de instrucciones individuales, define sección crítica y exclusión mutua con sus cuatro requisitos, y desarrolla el interbloqueo con sus condiciones necesarias, la inanición y los tres problemas clásicos: productor-consumidor, lectores-escritores y filósofos comensales.

Capítulo 8

Semáforos: el primer mecanismo que resolvió la exclusión mutua sin espera activa

Estudio completo del semáforo: la estructura de contador más cola de espera, las operaciones wait y signal, la diferencia entre semáforos binarios y contadores, y cómo el kernel garantiza que esas operaciones sean atómicas. Se implementan semáforos desde cero en Ada con objetos protegidos y se resuelven con ellos los problemas clásicos, además de catalogar los errores de uso que producen bloqueos permanentes.

Capítulo 9

Monitores: exclusión mutua encapsulada, variables de condición y las semánticas de Hoare y Mesa

Presenta el monitor como construcción de sincronización de alto nivel: qué encapsula, qué es su invariante y por qué una variable de condición no es un semáforo. Explica en detalle las tres semánticas de señalización —Hoare, Mesa y señalar-y-salir— y la regla de esperar dentro de un while en lugar de un if, con el buffer acotado como caso central y una comparación directa contra la solución del capítulo anterior.

Capítulo 10

Paso de mensajes: buzones, rendezvous de Ada, actores de Elixir y cifrado entre procesos

Tercera familia de sincronización, la que no necesita memoria compartida. Cubre las operaciones send y receive con sus cuatro combinaciones de bloqueo, el direccionamiento directo e indirecto, los buzones con disciplina FIFO y por prioridad, la cita de Ada y el modelo de actores de Elixir. Incluye además el cifrado del contenido de los mensajes que viajan entre procesos.

Capítulo 11

Sistemas de archivos: del bloque crudo al árbol de directorios

Cómo un sistema operativo convierte un disco de bloques anónimos en archivos con nombre. Recorre la anatomía del archivo y del directorio, los inodos con sus punteros indirectos, las estrategias de asignación de bloques —contigua, enlazada, indexada y por extensiones—, el journaling y sus modos, y las familias reales: FAT, ext4, btrfs y ZFS. Termina con permisos, montaje y el efecto de todo eso sobre lo que ve un programa cuando abre un archivo.

Capítulo 12

El lenguaje Ada: historia, instalación de GNAT y primeros programas

Abre el bloque de Ada explicando por qué el Departamento de Defensa estadounidense encargó el lenguaje en 1974, cómo se eligió el diseño ganador y qué principios quedaron grabados en el estándar. Cubre la instalación de GNAT y Alire en Linux, la estructura de archivos .ads, .adb y .gpr, la etapa real de compilación, y tres programas completos explicados línea a línea: hola mundo, raíz cuadrada y conversor de temperatura.

Capítulo 13

Ada práctico: arreglos, contenedores, cadenas y argumentos de línea de comandos

Segunda parte práctica del lenguaje: arreglos restringidos y no restringidos, los contenedores estándar Vectors, Doubly_Linked_Lists y Ordered_Maps, las tres familias de cadenas de Ada con el manejo de UTF-8, y cómo un programa recibe argumentos del sistema operativo mediante Ada.Command_Line. Todo desemboca en un juego de adivinar el número con entrada validada, historial y código de salida hacia el shell.

Capítulo 14

Concurrencia en Ada: tareas, cita, objetos protegidos y procesos del sistema

El capítulo que une el bloque de Ada con el bloque de concurrencia. Desarrolla las tareas y los tipos de tarea, las entradas y la cita, el enunciado select con sus guardas y alternativas, los objetos protegidos con barreras, y el uso de GNAT.OS_Lib para lanzar y supervisar procesos del sistema operativo desde varias tareas. Compara el modelo con los actores de Elixir y con los hilos POSIX.

Capítulo 15

Proyecto 1: construir una minishell desde cero con fork, exec, pipes y señales

Primer proyecto integrador: escribir un intérprete de comandos propio. Construye el ciclo leer-parsear-ejecutar, el tokenizador que reconoce redirecciones, tuberías y ejecución en segundo plano, la pareja fork/exec con su espera y sus códigos de salida, la redirección con dup2 sobre la tabla de descriptores y el manejo de señales para que la shell no muera con el proceso que lanzó.

Capítulo 16

Proyecto 2: un millón de registros, tres mecanismos de sincronización y pruebas que hacen visible la condición de carrera

Segundo proyecto: generar un archivo de un millón de registros y procesarlo, primero de forma secuencial en Python y después de forma concurrente en Elixir y en Ada. El mismo punto de contención se resuelve con las tres familias estudiadas —semáforo contador, monitor y paso de mensajes— y se acompaña de pruebas diseñadas para que la condición de carrera falle de manera reproducible, con la versión rota a propósito, el test que la caza y la medición comparativa de tiempos.

Capítulo 17

Proyecto final: un servidor de videojuegos concurrente y la bibliografía del curso

Cierre del curso: un servidor multijugador sobre sockets TCP que parte del cachipún por turnos y llega a un servidor autoritativo con tick loop de frecuencia fija, estado compartido bajo un único dueño y control explícito de la latencia. Se implementa completo en Elixir con :gen_tcp, procesos y supervisores, y en Ada con GNAT.Sockets, tareas y objetos protegidos, con protocolo interoperable, percentiles de latencia, predicción del cliente y reconciliación. Termina con la bibliografía recomendada de los diecisiete capítulos.

Cómo recorrer el curso

El orden numérico es el orden diseñado y funciona para cualquier punto de partida. Aun así, hay tres rutas alternativas que sirven según el objetivo.

flowchart TD
    START["¿Cuál es tu objetivo?"]
    START --> R1["Entender el área completa"]
    START --> R2["Resolver concurrencia<br/>en el trabajo"]
    START --> R3["Preparar una prueba<br/>o entrevista"]

    R1 --> P1["Ruta completa<br/>1 → 17 en orden"]
    R2 --> P2["4 → 5 → 6 → 7 → 8 → 9 → 10 → 14 → 16"]
    R3 --> P3["1 → 3 → 5 → 6 → 7 → 11<br/>+ tablas comparativas<br/>de 8, 9 y 10"]

    P1 --> FIN["Proyectos 15, 16 y 17"]
    P2 --> FIN
    P3 --> REV["Revisión con los<br/>ejercicios del final<br/>de cada capítulo"]

Ruta completa. Del 1 al 17 en orden. Es la única que garantiza que ningún concepto aparezca antes de haber sido explicado, y la única en que los tres proyectos finales se pueden resolver sin volver atrás.

Ruta de concurrencia. Para quien ya trabaja con hilos o procesos y necesita el marco conceptual rápido. Empieza en el capítulo 4 para tener la base de hardware, salta la historia y los sistemas de archivos, y desemboca en el proyecto 16, que es el que pone a prueba lo aprendido. El capítulo 12 se puede leer en diagonal si el único interés en Ada es la sintaxis de tareas del capítulo 14.

Ruta de repaso. Para quien tiene una evaluación cerca. Prioriza los capítulos con más contenido conceptual evaluable y usa las tablas comparativas de los capítulos 8, 9 y 10 como resumen de los mecanismos de sincronización.

Recomendaciones de práctica

  • Escribe el código a mano. Copiar y pegar los ejemplos hace que compilen, no que se entiendan. Teclear el mismo programa obliga a leer cada línea.
  • Rompe los ejemplos a propósito. Quita el while de una variable de condición, invierte el orden de dos wait sobre semáforos distintos, borra el wait del padre en un fork. Los capítulos indican qué debería fallar en cada caso; comprobarlo vale más que leerlo.
  • Ejecuta cada programa concurrente muchas veces seguidas. Un error de sincronización puede no aparecer en veinte ejecuciones y aparecer en la vigésimo primera. El capítulo 16 desarrolla esta idea hasta convertirla en pruebas.
  • Observa el sistema mientras corre. Casi todos los capítulos incluyen comandos para mirar desde afuera lo que está haciendo el programa. Tener esa ventana abierta en paralelo convierte la teoría en algo medible.
  • No avances con un capítulo a medias. Los mecanismos se apilan: el monitor del capítulo 9 supone entendido el semáforo del 8, y el proyecto 16 supone entendidos los tres.
  • Haz los ejercicios del final antes de pasar al siguiente capítulo. Están diseñados para exponer justamente los malentendidos que se arrastran hacia adelante.

Ritmo sugerido

Una sesión de estudio productiva termina con algo ejecutándose. Conviene dividir cada capítulo en dos pasadas: una primera de lectura completa sin tocar el teclado, para tener el mapa, y una segunda con la terminal abierta reproduciendo los ejemplos. Los capítulos de proyecto —15, 16 y 17— no se leen: se construyen, y conviene abordarlos en sesiones dedicadas, porque partir un proyecto por la mitad obliga a reconstruir el contexto cada vez.

Errores comunes al recorrer el curso

ErrorCausaSolución
Saltar del capítulo 1 directo a semáforosLa concurrencia parece el tema “interesante” y los fundamentos parecen rellenoLeer al menos los capítulos 4, 6 y 7: sin cambio de contexto ni sección crítica, el semáforo queda como una receta memorizada
Leer el bloque de concurrencia sin ejecutar nadaLos ejemplos se entienden al leerlos y da la sensación de que bastaEjecutar cada programa varias veces; la condición de carrera es un fenómeno de ejecución, no de lectura
Tratar Ada como un obstáculo antes de llegar a la concurrenciaSintaxis desconocida y verbosa comparada con lenguajes de uso diarioLos capítulos 12 y 13 existen para eso; con hacer sus ejemplos alcanza para leer el capítulo 14 sin fricción
Empezar el proyecto 15 sin dominar fork y descriptoresLa minishell parece un ejercicio de parseo de textoVolver al capítulo 6 para procesos y al 11 para descriptores; el parseo es la parte fácil del proyecto
Empezar el proyecto 17 sin haber hecho el 16El servidor de juegos suena más atractivo que procesar un archivoEl 16 instala la disciplina de pruebas y la elección de mecanismo que el 17 da por resuelta
Copiar y pegar todos los ejemplosAhorra tiempo en la sesiónTranscribir a mano al menos los ejemplos de sincronización; el error típico está en un detalle de una línea
Confundir concurrencia con paralelismo durante todo el cursoAmbos términos se usan como sinónimos fuera del ámbito técnicoEl capítulo 7 los separa explícitamente; si esa distinción no queda firme, los capítulos 8 a 10 se leen mal
Estudiar los algoritmos de planificación solo de memoriaLas tablas de FCFS, SJF y Round Robin parecen fáciles de recordarCalcular a mano las trazas del capítulo 6 con datos propios: el tiempo de espera promedio se equivoca sistemáticamente al hacerlo de memoria
Dar por entendido el while de las variables de condiciónEn el ejemplo funciona igual con if la mayoría de las vecesReproducir el caso de despertar espurio del capítulo 9 y ver el resultado incorrecto

Glosario de arranque

Estos términos aparecen desde los primeros capítulos y se definen con precisión en el lugar que corresponde. La tabla sirve como referencia rápida mientras el vocabulario se asienta, no como reemplazo de la explicación completa.

TérminoDefinición breveCapítulo donde se desarrolla
Máquina desnudaEl hardware tal como es antes de que exista software de sistema sobre él1
Máquina extendidaLa vista simplificada del hardware que el sistema operativo presenta a los programas1
Modo usuario / modo kernelDos niveles de privilegio del procesador; en modo usuario ciertas instrucciones están prohibidas1, 4, 5
Llamada al sistemaPetición de un programa al kernel para hacer algo que no puede hacer por su cuenta3, 5
InterrupciónSeñal del hardware que desvía la ejecución del procesador hacia una rutina de atención4
DMATransferencia de datos entre dispositivo y memoria sin pasar por el procesador4
ProcesoPrograma en ejecución con su propio espacio de direcciones y su estado asociado6
PCBEstructura del kernel que guarda todo lo que define a un proceso6
HiloFlujo de ejecución dentro de un proceso, que comparte memoria con los demás hilos del mismo proceso6
Cambio de contextoGuardar el estado de un flujo y restaurar el de otro para que el procesador siga con este último4, 6
PlanificadorComponente del kernel que decide cuál de los procesos listos usa el procesador5, 6
Condición de carreraResultado que depende del orden en que dos flujos ejecutan sus instrucciones7
Sección críticaFragmento de código que accede a un recurso compartido y no debe ejecutarse en paralelo7
Exclusión mutuaGarantía de que solo un flujo está dentro de la sección crítica a la vez7
InterbloqueoSituación en que dos o más flujos se esperan entre sí de forma permanente7
InaniciónUn flujo listo para ejecutarse que nunca obtiene el recurso porque otros se le adelantan siempre7
SemáforoContador con cola de espera y dos operaciones atómicas, wait y signal8
MonitorConstrucción que encapsula datos, operaciones y exclusión mutua en una sola unidad9
Variable de condiciónCola de espera dentro de un monitor asociada a una condición lógica sobre su estado9
Cita (rendezvous)Sincronización de Ada en que dos tareas se encuentran para intercambiar datos10, 14
BuzónDestino intermedio de mensajes que desacopla al emisor del receptor10
InodoEstructura que guarda los metadatos y los punteros a bloques de un archivo11
JournalingRegistro previo de las operaciones del sistema de archivos para poder recuperarse tras un corte11
Descriptor de archivoNúmero entero con que un proceso se refiere a un archivo o canal abierto11, 15
Objeto protegidoTipo de Ada que garantiza acceso exclusivo a su estado interno sin escribir bloqueos8, 14
Tick loopCiclo de servidor que avanza el estado del mundo a una frecuencia fija17

Qué no cubre este curso

Delimitar el alcance evita frustraciones. Estos temas se mencionan cuando hacen falta para entender otra cosa, pero no se desarrollan:

  • Memoria virtual en profundidad. El curso explica la jerarquía de memoria, el stack, el heap y el aislamiento entre procesos, pero no entra en el detalle de la paginación, la tabla de páginas multinivel ni los algoritmos de reemplazo.
  • Escribir un kernel propio. El capítulo 5 explica la estructura interna y los módulos, sin llegar a la implementación de un sistema operativo desde cero.
  • Administración de sistemas. No hay configuración de servicios, gestión de usuarios ni afinamiento de rendimiento del sistema.
  • Redes más allá de los sockets. El capítulo 17 usa TCP como herramienta; no desarrolla la pila de protocolos ni el enrutamiento.
  • Virtualización y contenedores. Aparecen mencionados como consecuencia del aislamiento, sin capítulo propio.
  • Seguridad ofensiva. El curso cubre protección, permisos y cifrado de mensajes entre procesos, no técnicas de explotación.

Preguntas frecuentes

¿Hay que saber C para empezar? No. El capítulo 4 introduce lo que se necesita y los ejemplos en C están comentados línea a línea. Sí ayuda haber programado antes en cualquier lenguaje.

¿Por qué Ada y no C++ o Java? Porque en Ada la concurrencia es sintaxis del lenguaje. Un objeto protegido se lee como lo que es —un monitor— sin tener que reconstruirlo mentalmente a partir de una clase con un candado adentro. Eso hace que los capítulos 8, 9 y 14 muestren el mecanismo en vez de una implementación de él.

¿Sirve el curso si trabajo en desarrollo web? Sí, para la parte que suele quedar como caja negra: por qué el servidor se queda sin descriptores, qué significa que un proceso esté en estado de espera, por qué dos peticiones simultáneas corrompen un contador, qué hace realmente un pool de conexiones.

¿Se puede hacer el curso en Windows? Con WSL2 sí, y es la vía recomendada. Sin WSL2, las herramientas de diagnóstico y varias llamadas al sistema de los capítulos 6, 11 y 15 no tienen equivalente directo.

¿Los proyectos finales sirven como portafolio? Los tres son programas completos y ejecutables. La minishell y el servidor de juegos, en particular, son piezas que se pueden mostrar y explicar.

¿Qué pasa si un capítulo me queda a medias? Conviene resolverlo antes de avanzar. El diagrama de la ruta de aprendizaje muestra qué capítulos dependen de cuál; si el capítulo pendiente tiene flechas salientes hacia el siguiente, seguir sin él implica arrastrar el hueco.

Ejercicios previos de diagnóstico

Antes de abrir el capítulo 1, estas preguntas sirven para medir el punto de partida. No hay que saber responderlas: el objetivo es volver a ellas al terminar el curso y notar la diferencia.

  1. Cuando escribes un comando en la terminal y presionas Enter, ¿cuántos programas distintos participan hasta que aparece la salida?
  2. Un programa reserva memoria y nunca la libera. ¿Qué pasa con esa memoria cuando el programa termina, y quién se encarga?
  3. Dos hilos incrementan la misma variable un millón de veces cada uno. ¿Cuál es el valor final, y por qué la respuesta no es dos millones?
  4. ¿Qué diferencia hay entre borrar un archivo y liberar el espacio que ocupaba?
  5. Si el procesador tiene cuatro núcleos y hay cuarenta procesos en ejecución, ¿qué significa exactamente que los cuarenta estén “corriendo”?
  6. ¿Por qué un programa no puede escribir directamente en la memoria de otro?

Fuente y créditos

El temario de este curso está basado en la guía de sistemas operativos publicada por Ninjas.cl, disponible en https://ninjascl.github.io/sistemas-operativos/. De ahí provienen la selección de temas, el orden general de los bloques, la decisión de usar Ada y Elixir como lenguajes de trabajo para la concurrencia y la idea de los tres proyectos integradores que cierran el recorrido.

Este material es una reelaboración propia y extendida: los textos, las explicaciones, los diagramas, los ejemplos de código, las tablas comparativas, las secciones de errores frecuentes y los ejercicios fueron escritos para este curso. No se reproduce texto literal de la fuente. Cada capítulo desarrolla los temas con mayor extensión y con código ejecutable y verificado, y agrega material que no está en la guía original, como el bloque de arquitectura de computadoras, el detalle de las semánticas de señalización de monitores y la parte de medición de latencia del proyecto final.

El crédito por el diseño del temario corresponde a sus autores; los errores, omisiones y decisiones de redacción de esta versión son responsabilidad de este sitio. Las referencias bibliográficas específicas —libros, artículos y documentación de cada tema— están reunidas al final del capítulo 17.

Por dónde empezar

El primer paso es el capítulo 1, que define qué es un sistema operativo partiendo de lo que hace el hardware cuando no hay ninguno. Ahí queda planteada la distinción entre la máquina desnuda y la máquina extendida, que es el hilo conductor de todo lo que viene después: cada capítulo del curso agrega una capa a esa máquina extendida, hasta que al final, en el servidor del capítulo 17, se usan todas juntas sin pensar en ellas.