Saltar al contenido
Alexander Zelada

Ruta 01 · Lección 1 de 4

Kernel de Linux: Scheduler, Memoria y VFS a Fondo

Llamadas al sistema, scheduler CFS, overcommit, OOM Killer y page cache: cómo reparte el kernel de Linux tus recursos en producción.

Alexander Zelada

Alexander Zelada · 7 min de lectura

Ruta 01 · Sistema operativo por dentro

Aquí abrimos el sistema operativo para ver cómo reparte el kernel de Linux tus recursos. Verás cuánto cuesta una llamada al sistema y cómo funcionan tres subsistemas del kernel: el scheduler, el gestor de memoria y el VFS.

La idea central es que, cuando la latencia se dispara en producción y no es culpa de la red ni de la base de datos, muchas veces la explicación está en cómo gestiona el kernel la CPU, la memoria y el disco.

Esta lección en vídeo.

Por qué no debes tratar el sistema operativo como una caja negra

Tu código funciona y los tests pasan, pero en producción la latencia se dispara. No es la red ni la base de datos, y meterle más RAM solo es un parche.

El error está en tratar al sistema operativo como una caja negra. Vamos a abrirla para ver cómo gestiona el kernel tus recursos de verdad.

User space y kernel space

Empecemos por lo básico. Tu servidor tiene dos zonas separadas.

Arriba está el user space (espacio de usuario), donde viven tus aplicaciones: tu Nginx, tu Python, tu contenedor de Docker. En esta zona, si tu código falla, solo muere tu proceso.

Debajo está el kernel space (espacio del kernel). Ahí vive Linux, y desde ahí se controla todo el hardware: CPU, memoria, disco y red. Si algo falla en esta zona, se apaga todo. Es lo que se conoce como kernel panic.

¿Por qué te cuento esto? Porque cada vez que tu código quiere hacer algo útil, como leer un archivo, mandar un paquete o escribir un log, tiene que pedirle permiso al kernel. Esa petición se llama llamada al sistema (syscall), y tiene un costo.

Cuántas llamadas al sistema hace un Hello World

Para medirlo, voy a ejecutar un «Hello World» en Python, pero con strace, que muestra las llamadas al sistema que hace un programa por debajo.

strace -c python3 -c "print('Hello World')"

El resultado son 368 llamadas al sistema. Son 368 veces en las que tu programa tuvo que detenerse, pedirle permiso al kernel, esperar a que el kernel hiciera el trabajo y recuperar el control. Todo eso para imprimir un Hello World.

Hice la misma prueba con un Hello World en C, que es un lenguaje compilado y no tiene intermediarios. El resultado fue de 34 llamadas al sistema.

La diferencia se debe a que Python es un lenguaje interpretado y C es un lenguaje compilado. Python paga el costo del runtime: antes de ejecutar tu código, el sistema tiene que cargar el intérprete, inicializar la gestión de memoria y cargar librerías dinámicas. Esas ~300 llamadas extra son overhead de inicialización.

En cambio, C es código máquina nativo, y el binario ya está listo para la CPU. En un script esta diferencia no importa. El problema aparece cuando tu sistema escala en producción a miles de peticiones por segundo.

Los tres subsistemas del kernel que vamos a ver

El kernel es un programa complejo, con docenas de subsistemas: networking, drivers, seguridad… El networking es tan denso e importante que le dedico una lección aparte. Aquí nos centramos en estos tres:

  • El scheduler, que gestiona el tiempo de CPU.
  • El Memory Manager (gestor de memoria), que decide cuánta RAM te toca.
  • El VFS, que controla el acceso a tus datos en disco.

El scheduler: cómo se reparte el tiempo de CPU

Imagina un nodo de Kubernetes estándar con 2 vCPUs, en el que corren un Pod de API y otro de base de datos. El scheduler de Linux, que se llama CFS, decide qué proceso usa cada core y durante cuánto tiempo.

Su algoritmo busca la equidad en el tiempo de cómputo (fairness): si tienes dos procesos, intenta darle el 50% del tiempo a cada uno. Hasta aquí todo va bien, y tu API corre en el core 1 sin problemas.

En el mundo real, el problema aparece cuando alguien despliega un Job pesado sin definir límites. Si no configuras los limits, ese proceso puede acaparar el 100% de la CPU. Y si no tiene requests garantizados, el kernel le da la misma prioridad que a tu API crítica.

El resultado es que el Job satura los cores y tu API se queda esperando en la cola, con lo que la latencia se dispara. Lo peor es que, al ir tan lenta, la API falla el health check de Kubernetes, que la reinicia una y otra vez mientras el proceso que causa el problema sigue consumiendo recursos.

Por eso los limits no son opcionales. Son la única forma de decirle al kernel: «No me importa cuántos hilos tenga esta aplicación, no le des más del 50% o el 60% de la CPU».

El gestor de memoria: overcommit y OOM Killer

A diferencia de la CPU, la memoria no es elástica. Si te falta RAM, tu proceso no va más lento: el proceso muere.

El kernel usa una técnica llamada overcommit, que permite que las aplicaciones reserven más memoria de la que existe físicamente. Tus aplicaciones pueden pedir 100 GB de memoria virtual aunque el servidor solo tenga 32 GB de RAM. El kernel acepta la petición porque asume que no la usarán toda a la vez.

La memoria física que tus aplicaciones ocupan de verdad en cada momento se llama RSS (Resident Set Size). El sistema funciona bien mientras el RSS total no supere la RAM física disponible.

El problema llega cuando el uso real alcanza el límite físico y ya no queda RAM para nadie. En ese momento, el kernel activa el OOM Killer (Out of Memory Killer). Este mecanismo elige el proceso con la puntuación más alta (oom_score), que suele ser el que más memoria usa, le envía una señal SIGKILL y lo termina inmediatamente para liberar recursos.

El VFS y el page cache: por qué la segunda lectura es más rápida

Tu programa no sabe qué es un disco duro, un USB o la nube. Solo sabe qué es un archivo. El VFS (Virtual File System) es la capa del kernel que se encarga de que todo se vea como un archivo.

Gracias a esa capa, tu código usa las mismas llamadas (open, read, write) para leer o escribir datos, da igual si están en un pendrive o en un servidor al otro lado del mundo.

Además de compatibilidad, el VFS te ayuda con la velocidad, porque el disco tiene un problema físico: es lento.

Imagina que arrancas tu base de datos por primera vez. El kernel tiene que ir al disco a buscar los datos, y a esto lo llamamos cold read o lectura en frío. Sea un disco mecánico o un SSD, comparado con la CPU es muy lento.

Mientras lee del disco, ocurre algo que no ves. Linux sigue una regla: la RAM libre es RAM desperdiciada. Así que el kernel hace una copia de esos datos y la guarda en la RAM que te sobra. Esa copia es el page cache.

Si pides el mismo archivo un segundo después, la lectura baja a 0.007 segundos. A esto lo llamamos hot read o lectura en caliente: el kernel no necesita ir al disco porque ya tiene los datos, y te los sirve directamente desde la RAM.

Por eso tu servidor va mucho más rápido después de unos minutos de uso: el VFS sirve desde la RAM lo que antes tenía que leer del disco.

Lo que hemos visto

  • Tus aplicaciones viven en el user space y, cada vez que necesitan el hardware, hacen una llamada al sistema al kernel. Un Hello World en Python hace 368 y uno en C, 34.
  • El scheduler CFS reparte el tiempo de CPU de forma equitativa, así que un Job pesado sin limits puede dejar sin CPU a tu API.
  • Con el overcommit, el kernel acepta más memoria de la que existe. Cuando el uso real llena la RAM, el OOM Killer mata un proceso con SIGKILL.
  • El VFS da la misma interfaz para cualquier almacenamiento y guarda en el page cache lo que lee, para que la siguiente lectura salga de la RAM.