🔎 Buscar

👑 Nivel Élite — contenido

C y programación de sistemas, ensamblador y ABI, construir un sistema operativo, el kernel de Linux, compiladores e intérpretes, databases internals y redes de bajo nivel. Teoría y práctica con proyectos.

Nivel Élite📖 Contenido

Nivel Élite — contenido

Este es el nivel donde ya no hay cursos: se lee el código fuente primario y se construyen los sistemas. Es el camino de Linus Torvalds: entender el kernel, el compilador, el sistema operativo y la base de datos desde dentro. Cada tema se sella con un proyecto: escribir un OS, un compilador, un DBMS.

📌 La mentalidad del nivel élite

El principio rector: leer las fuentes primarias y construir los sistemas, no consumir cursos. Los libros clásicos no caducan: CSAPP (2ª/3ª ed.), OSTEP, K&R, Dragon Book, TCP/IP Illustrated siguen siendo correctos. El hábito final es leer código real (Linux, Go runtime, CPython, Node core) y contribuir.

C y la programación de sistemas

C es el lenguaje del kernel, de Unix y de todo lo que funciona a bajo nivel. Antes de cualquier proyecto élite hay que hablar C con fluidez.

Punteros, memoria y structs

Ya tienes la teoría en wiki: Memoria y punteros en C. Los elementos que te convierten en programador de sistemas:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char nombre[32];
    int  edad;
} Persona;

Persona* crear(const char* nombre, int edad) {
    Persona* p = malloc(sizeof(Persona));
    if (!p) return NULL;                  // comprobar malloc SIEMPRE
    strncpy(p->nombre, nombre, sizeof(p->nombre) - 1);
    p->edad = edad;
    return p;
}

int main(void) {
    Persona* ana = crear("Ana", 30);
    printf("%s, %d\n", ana->nombre, ana->edad);
    free(ana);                            // y liberar SIEMPRE
    return 0;
}

💡 El contrato C: cada malloc su free, cada recurso su liberación. Los leaks y los use-after-free son los bugs de C. Herramientas: valgrind, AddressSanitizer (-fsanitize=address).

Syscalls: la interfaz con el kernel

C programa sistemas porque puede llamar al kernel directamente:

#include <fcntl.h>
#include <unistd.h>

int main(void) {
    int fd = open("/tmp/miarchivo", O_WRONLY | O_CREAT, 0644);
    if (fd < 0) return 1;
    write(fd, "hola\n", 5);
    close(fd);
    return 0;
}
Syscall Qué hace
fork Crea un proceso hijo (copia del padre)
exec Reemplaza el proceso actual por un programa
wait Espera a que un hijo termine
open/read/write/close Archivos
mmap Mapear memoria o archivos
socket/bind/listen/accept Red
kill Enviar señal a un proceso
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        printf("soy el hijo (%d)\n", getpid());
        execlp("ls", "ls", "-l", NULL);   // el hijo se convierte en ls
    } else {
        wait(NULL);                       // el padre espera al hijo
        printf("soy el padre (%d)\n", getpid());
    }
    return 0;
}

⚠️ El flujo completo: fork duplica el proceso → el hijo usa exec para correr otro programa → el padre wait al hijo. Así crea un shell cada comando que ejecutas.

El modelo de ejecución: de C a ensamblador

En Fundamentos: sistemas viste el ciclo fetch-decode-execute. A nivel élite lo llevas al byte:

gcc -S -O2 programa.c     # genera el ensamblador
objdump -d programa        # desensambla el binario
readelf -h programa        # cabecera ELF (formato de binarios Linux)

Stack frames y la ABI

Una ABI (Application Binary Interface) define cómo se llaman las funciones a nivel de máquina: qué registros llevan los argumentos, quién limpia el stack, cómo se devuelve el valor.

Llamada a foo(a, b):
  args → registros (rdi, rsi) → call foo → push return addr
  foo: push rbp → mov rbp, rsp → [stack frame de foo]
  ret: pop return addr → saltar de vuelta al caller

💡 Entender la ABI es lo que te permite leer ensamblador de verdad, depurar con gdb a nivel de instrucción y escribir código seguro contra buffer overflows (el return address vive en el stack — viste el peligro en la wiki de memoria).

Construir un sistema operativo

De la teoría del OSTEP al kernel real. El camino tiene dos variantes:

  1. MIT 6.S081 (xv6): modificas un kernel real de RISC-V (labs: syscalls, páginas, traps, COW, threads, filesystem, mmap). El estándar académico.
  2. OS desde cero (phil-opp en Rust, o la wiki OSDev): escribes el boot y el kernel tú mismo. El rito moderno.

Las piezas de un kernel

[Bootloader] carga el kernel en memoria
[GDT/IDT]    estructuras de memoria y de interrupciones
[MEMORIA]    gestión de páginas (virtual → física)
[PROCESOS]   tabla de procesos, scheduling
[SYSCOLLS]   la interfaz que tu OS expone a los programas
[DRIVERS]    puerto serie, teclado, video, disco
// La syscall mínima: un "hello" que el kernel expone
void sys_hello(void) {
    printk("hello desde el kernel\n");
}

💡 El proyecto vale oro: no hay mejor forma de entender procesos, memoria virtual e interrupciones que haberlos escrito.

El kernel de Linux

El paso final: leer y contribuir al proyecto más grande del mundo.

Cómo leer el kernel sin morir en el intento

  1. Empieza por lo familiar: fs/ (filesystems), kernel/ (scheduling, syscalls), mm/ (memoria), net/ (red).
  2. Usa LWN y los posts de maintainers para el contexto.
  3. Sigue una ruta: el flujo de una read() o de un fork() de principio a fin.
Un fork() por dentro (resumen):
  sys_fork (kernel/fork.c)
    → copy_process (copia task_struct, mm, fs, files)
    → wake_up_new_task (añade al scheduler)
  el hijo nace con su propio task_struct apuntando al mismo código
Término del kernel Qué es
task_struct La ficha de cada proceso/thread
HZ / jiffies El tick del reloj del kernel
Waitqueue Procesos esperando un evento
RCU Read-Copy-Update: lectura sin locks
Sysfs / procfs Interfaces del kernel a user space
Kconfig / Kbuild El sistema de build del kernel
mainline El kernel de Linus (torvalds/linux)

💡 Contribuir: kernelnewbies.org, «first patches» (código que arregla cosas triviales), mailing lists. El proceso es por email (git send-email), no PRs de GitHub.

Compiladores e intérpretes

Escribir tu propio lenguaje es el proyecto élite por excelencia. El camino moderno es Crafting Interpreters: construyes dos intérpretes completos (un tree-walker y una bytecode VM con GC).

Las fases de un compilador

código fuente
   ↓ [Lexer/Scanner]  → tokens (identificadores, palabras clave, números)
   ↓ [Parser]          → árbol de sintaxis (AST)
   ↓ [Análisis semántico] → tipos, referencias válidas
   ↓ [Generación de código] → bytecode, C, ensamblador
   ↓ [Optimización]    → código más eficiente

Un mini-intérprete para entenderlo

# Los 3 pasos en miniatura

# 1. LEXER: de texto a tokens
def tokenizar(src):
    tokens = []
    for palabra in src.split():
        if palabra.isdigit():
            tokens.append(("NUM", int(palabra)))
        elif palabra in ("+", "-", "*"):
            tokens.append(("OP", palabra))
        else:
            tokens.append(("ID", palabra))
    return tokens

# 2. PARSER: de tokens a árbol (expresión: número [op número]*)
def parsear(tokens):
    i = 0
    num = tokens[i][1]; i += 1
    operaciones = []
    while i < len(tokens):
        op = tokens[i][1]; num2 = tokens[i+1][1]
        operaciones.append((op, num2)); i += 2
    return num, operaciones

# 3. EVALUACIÓN: recorrer el árbol
def evaluar(num, operaciones):
    for op, n in operaciones:
        if op == "+": num += n
        if op == "-": num -= n
        if op == "*": num *= n
    return num

print(evaluar(*parsear(tokenizar("3 + 4 * 2"))))   # 14

Terminología de compiladores

Término Qué es
Token Unidad léxica (número, identificador, operador)
AST Árbol de sintaxis abstracta
Lexer / Parser Tokeniza / construye el árbol
Tree-walker Intérprete que recorre el AST
Bytecode Código intermedio compacto para una VM
Stack machine La VM clásica (como la JVM, CPython)
GC (garbage collector) Recolecta memoria que ya no se usa
Front-end / back-end Análisis del código / generación de código

💡 La joya: escribir una VM con garbage collector te conecta con CPython, la JVM, Lua y V8. Entender GC es entender la mitad de los runtimes del mundo.

Bases de datos internals

El proyecto: construir un DBMS (CMU 15-445, BusTub). Las piezas que tocas:

[SQL Parser] ──▶ [Query Planner/Optimizer] ──▶ [Executor]

[Buffer Pool]  ──│── (páginas en RAM, con eviction)─│
[Storage]       ──│── (tablas, índices B+Tree/LSM) ─│
[Concurrencia]  ──│── (locks, MVCC) ────────────────│
[Recovery]      ──│── (WAL, undo/redo) ────────────│
Componente Qué hace
Buffer pool Caché de páginas del disco en RAM (LRU)
B+Tree El índice: búsqueda O(log n) en disco
LSM-tree El índice de las NoSQL (escritura secuencial rápida)
WAL (Write-Ahead Log) Registrar los cambios ANTES de aplicarlos (durabilidad)
MVCC Versiones para que lectores y escritores no se bloqueen
Executor Ejecuta el plan de la query (joins, agregaciones)

💡 Después de construir uno, EXPLAIN ANALYZE de PostgreSQL deja de ser magia: ya sabes qué pasa por dentro. Detalle de teoría en Fundamentos: BBDD.

Redes de bajo nivel

De «usar HTTP» a «entender el paquete hasta el byte». La teoría está en Fundamentos: Redes y wiki: TCP/IP. A nivel élite:

  • TCP/IP Illustrated vol. 1 (Stevens): el protocolo a fondo, con diagramas.
  • Los RFC (fuente primaria): RFC 9293 (TCP), RFC 791 (IP), RFC 768 (UDP).
  • Sockets en C (Beej): implementar un cliente/servidor a mano.
// Servidor TCP mínimo en C (sockets)
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {
    int srv = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = { .sin_family = AF_INET,
                                .sin_port = htons(8080),
                                .sin_addr.s_addr = INADDR_ANY };
    bind(srv, (struct sockaddr*)&addr, sizeof(addr));
    listen(srv, 10);
    int cli = accept(srv, NULL, NULL);
    char buf[1024];
    read(cli, buf, sizeof(buf));           // recibe la petición
    write(cli, "HTTP/1.1 200 OK\r\n\r\nhola", 22);
    close(cli); close(srv);
    return 0;
}

⚠️ Ese servidor no maneja concurrencia (atiende una conexión y se acaba). Añadir fork/threads por conexión, timeouts y parseo HTTP real es exactamente el camino de los labs de sistemas.

El hábito final: leer código fuente real

Proyecto Qué aprenderás
Linux kernel El sistema operativo entero
Go runtime Scheduler, goroutines, GC
CPython Interprete, bytecode, GIL, GC
Node core Event loop, libuv
Redis Un servidor de datos completo y legible (~60k líneas)
SQLite Una base de datos completa en un archivo
Git Modelo de objetos, algoritmos de diff/merge

💡 Método: elige UN proyecto y rastrea un flujo completo de principio a fin (una petición HTTP en Node, un GET en Redis, una read() en Linux). Escribe apuntes. Eso es leer código de verdad — no ojear.

Terminología del nivel élite

Término En una frase
ABI El contrato binario entre código y CPU/OS
ELF Formato de binarios y librerías de Linux
Syscall La llamada del programa al kernel
fork / exec / wait Crear proceso / correr otro / esperar
task_struct La ficha de cada proceso del kernel
xv6 El kernel de enseñanza (RISC-V)
VM (máquina virtual) Ejecuta bytecode (CPython, JVM, V8)
GC Recolector de basura
AST Árbol de sintaxis abstracta
Bytecode Código intermedio de una VM
Buffer pool Caché de páginas de BBDD en RAM
B+Tree / LSM Índices de las bases de datos
WAL Log de escritura anticipada (durabilidad)
RCU Lectura sin locks del kernel

Práctica propuesta (proyectos que valen oro)

  1. Completa los CSAPP labs (Data Lab, Bomb Lab, Attack Lab, Shell Lab, Malloc Lab). El rito de paso.
  2. Escribe un shell en C: lee comandos, fork+exec, redirección >, |, cd, exit.
  3. Modifica xv6 (MIT 6.S081): añade una syscall propia, haz COW fork, un scheduler nuevo.
  4. Escribe un intérprete (Crafting Interpreters): tokens → AST → evaluador → bytecode VM.
  5. Construye un servidor HTTP en C con threads y timeouts.
  6. Implementa un mini-DBMS: buffer pool + B+Tree + WAL en tu lenguaje.
  7. Lee código real: sigue un GET completo en Redis o el arranque de Node. Escribe apuntes.
  8. Contribuye al kernel o a un proyecto grande: primera patch real.

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.