1. Qué va dentro del kernel
En el tema 11 enunciamos un principio: todo lo que pueda hacerse fuera del kernel conviene hacerlo fuera, porque cada línea que se ejecuta en modo privilegiado es una línea cuyo error puede detener la máquina. El tema 18 le puso nombre a esa consecuencia: un puntero nulo en un programa termina con una señal; el mismo puntero en un driver, con un pánico.
El principio es claro. Lo difícil es decidir hasta dónde llevarlo. Estos son los componentes que un sistema operativo necesita, ordenados aproximadamente de más a menos imprescindibles en modo kernel:
| Componente | ¿Necesita modo kernel? |
|---|---|
| Cambio de contexto, manejo de interrupciones, tablas de memoria | Sí, sin alternativa: usan instrucciones privilegiadas. |
| Planificador | El mecanismo, sí. La política podría decidirse fuera. |
| Comunicación entre procesos | Sí: alguien debe poder cruzar la frontera entre dos espacios de direcciones. |
| Drivers de dispositivos | Discutible. Necesitan acceso al hardware, que el kernel podría concederles uno por uno. |
| Sistemas de archivos | Discutible. Son lógica sobre bloques de disco. |
| Protocolos de red | Discutible. Son lógica sobre paquetes. |
| Interfaz gráfica | No, aunque algunos sistemas la pusieron dentro por rendimiento. |
La arquitectura de un kernel es la decisión sobre qué componentes del sistema operativo se ejecutan en modo kernel, dentro de un mismo espacio de direcciones, y cuáles se ejecutan como procesos en modo usuario.
Las filas «discutibles» son las que separan a las tres arquitecturas de este tema. Y son la mayor parte del código: en Linux, los drivers solos superan la mitad de los 35 millones de líneas.
2. El kernel monolítico
En un kernel monolítico, todos los servicios del sistema operativo —planificación, memoria, sistemas de archivos, red y drivers— forman un único programa, que se ejecuta entero en modo kernel y en un solo espacio de direcciones.
Es el diseño original de Unix, y el de Linux y los sistemas BSD. «Monolítico» significa «de una sola piedra»: no quiere decir desordenado. Por dentro, el código está dividido en subsistemas con interfaces definidas. Lo que lo caracteriza es que no hay barreras de protección entre ellos. El sistema de archivos llama al driver del disco con una llamada a función corriente, y los dos pueden leer y escribir las estructuras de datos del otro.
| Ventajas | Inconvenientes |
|---|---|
| Rendimiento. La comunicación entre componentes cuesta lo que una llamada a función: unos nanosegundos. | Un error en cualquier parte afecta a todo. Un driver defectuoso puede corromper la memoria del planificador. |
| Simplicidad para compartir. Todos los componentes ven los mismos datos, sin copiarlos. | Mucho código con privilegio total. Más líneas en modo kernel son más oportunidades para un fallo de seguridad. |
| Un único camino de entrada. Una llamada al sistema se atiende completa sin salir del kernel. | Los fallos no se aíslan. No se puede reiniciar un componente dañado: hay que reiniciar la máquina. |
3. El microkernel
Un microkernel mantiene en modo kernel solo los mecanismos que no pueden estar en otro lugar: los espacios de direcciones, la planificación básica y la comunicación entre procesos. Los demás servicios son procesos comunes, llamados servidores, que se ejecutan en modo usuario y se comunican mediante mensajes.
El sistema de archivos es un proceso. El driver del disco es otro proceso. La red, otro. Cada uno tiene su espacio de direcciones, protegido por la unidad de gestión de memoria como el de cualquier programa. Cuando una aplicación quiere leer un archivo, no «entra» al sistema de archivos: le envía un mensaje y espera la respuesta. El kernel se limita a llevar los mensajes de un proceso a otro.
Las propiedades de este diseño son el reverso exacto del anterior:
| Ventajas | Inconvenientes |
|---|---|
| Aislamiento. Un driver con un error termina con una señal, como cualquier programa. El resto del sistema sigue. | Rendimiento. Cada pedido entre componentes cruza la frontera del kernel y cambia de proceso. |
| Recuperación. Un servidor caído se puede reiniciar sin reiniciar la máquina. | Complejidad de diseño. Los componentes ya no comparten datos; hay que definir un protocolo de mensajes para cada interacción. |
| Poco código con privilegio total. Un microkernel tiene del orden de diez mil líneas, que se pueden revisar una por una. | Coordinación. Operaciones que involucran a varios servidores son difíciles de hacer correctas y eficientes. |
La tercera ventaja se llevó a su extremo con seL4, un microkernel del que existe una demostración matemática, verificada por computadora, de que su código cumple su especificación. Es algo impensable para un kernel de millones de líneas.
Los representantes principales son Mach, de los años ochenta, que influyó en casi todos los posteriores; MINIX, creado para la enseñanza; QNX, de uso comercial en automóviles y equipos médicos; y la familia L4.
4. El precio de los mensajes
El inconveniente del microkernel se puede contar. Sigamos una operación simple, leer un bloque de un archivo que no está en la caché, en las dos arquitecturas.
En un kernel monolítico es el recorrido del tema 13: la aplicación ejecuta syscall, el kernel atiende el pedido completo —el sistema de archivos llama al driver con una llamada a función— y vuelve. En un microkernel, cada flecha es un mensaje:
- La aplicación envía «leer» al servidor de archivos. Entra al kernel, que copia el mensaje y cambia al proceso del servidor. cambio de contexto
- El servidor de archivos determina qué bloque necesita y envía un mensaje al driver del disco. El kernel cambia al proceso del driver. cambio de contexto
- El driver da la orden al dispositivo y espera. Cuando llega la interrupción, el kernel la convierte en un mensaje para el driver. interrupción
- El driver responde al servidor de archivos con el bloque. cambio de contexto
- El servidor de archivos responde a la aplicación con los datos. cambio de contexto
| Monolítico | Microkernel | |
|---|---|---|
| Entradas al kernel | 1, más la interrupción | 4 como mínimo, más la interrupción |
| Cambios de proceso | Ninguno obligado | 4 como mínimo |
| Copias de los datos | Una: del kernel a la aplicación | Varias, salvo que se comparta memoria |
| Entre el sistema de archivos y el driver | Una llamada a función | Dos mensajes |
En el tema 19 vimos que un cambio de contexto cuesta del orden de un microsegundo y que, además, invalida las cachés. Los primeros microkernels, con Mach a la cabeza, resultaron mucho más lentos que los sistemas monolíticos, y esa mala fama los acompañó durante años.
La generación siguiente, la familia L4, demostró que buena parte del problema era de implementación: con un paso de mensajes diseñado al detalle, el costo se redujo a una fracción. La diferencia con un kernel monolítico no desapareció, pero dejó de ser prohibitiva. En un equipo donde el aislamiento importa más que el último punto de rendimiento, es un precio razonable.
5. Un debate de 1992
La discusión entre las dos arquitecturas tiene un episodio célebre. En enero de 1992, pocos meses después de que Linus Torvalds publicara las primeras versiones de Linux, el profesor Andrew Tanenbaum, autor de MINIX y de uno de los libros de texto más usados de la materia, escribió en un foro un mensaje titulado «Linux es obsoleto».
Su argumento era que el diseño monolítico pertenecía a los años setenta, y que escribir un kernel así en 1991 era un paso atrás; el futuro eran los microkernels. Torvalds, que tenía veintidós años, respondió que los microkernels eran superiores en teoría y que en la práctica los existentes funcionaban peor; que el suyo estaba disponible, hacía lo que la gente necesitaba y era más rápido.
Treinta años después, el balance da algo de razón a cada uno:
- En computadoras personales y servidores se impuso el diseño monolítico. Linux, y los híbridos de la sección siguiente, dominan por completo.
- Donde la confiabilidad es el requisito principal se impusieron los microkernels. QNX gobierna las pantallas y los sistemas de asistencia de millones de automóviles. Variantes de L4 se ejecutan en los procesadores de seguridad y de comunicaciones de la mayoría de los teléfonos.
- Los problemas que Tanenbaum señalaba eran reales. Los drivers son, por mucho, la mayor fuente de fallos en los sistemas monolíticos, y todos ellos han ido adoptando técnicas para contenerlos.
El episodio ilustra una regla frecuente en ingeniería: un diseño que existe y funciona suele ganarle a uno mejor que todavía no está terminado. Y también que las preguntas de arquitectura rara vez tienen una respuesta única: dependen de lo que se quiera optimizar, como vimos en el tema 23.
6. Los kernels híbridos
Entre los dos extremos hay una posición intermedia, que es la de los dos sistemas comerciales más extendidos.
Un kernel híbrido está organizado internamente como un microkernel, con un núcleo mínimo y servicios separados, pero ejecuta la mayoría de esos servicios en modo kernel, en el mismo espacio de direcciones, para evitar el costo de los mensajes.
| Windows NT | XNU (macOS, iOS) | |
|---|---|---|
| Origen | Diseñado a fines de los ochenta, bajo la influencia de las ideas de microkernel | Construido sobre el microkernel Mach, al que se añadió el código de un Unix de la familia BSD |
| Núcleo mínimo | Una capa pequeña que maneja interrupciones y planificación | Mach: mensajes, memoria y planificación |
| Servicios en modo kernel | Memoria, procesos, archivos, red, drivers y, desde 1996, parte del sistema gráfico | La capa BSD —archivos, red, procesos— y el sistema de drivers |
| En modo usuario | Subsistemas de entorno y una parte de los drivers | Una cantidad creciente de drivers |
El nombre de XNU es una declaración de origen: son las siglas de «X is Not Unix». Combina el paso de mensajes de Mach con la interfaz POSIX de BSD, y ambas partes conviven en modo kernel.
La categoría es polémica. Sus críticos, Torvalds entre ellos, sostienen que «híbrido» es una palabra de mercadotecnia: si los servicios comparten espacio de direcciones y privilegios, un error en uno afecta a todos, y a efectos prácticos el kernel es monolítico, por muy ordenado que esté por dentro. Es una objeción atendible. Lo que el término sí describe es una diferencia de estructura interna, y una tendencia: los dos sistemas llevan años trasladando drivers al modo usuario, que es precisamente lo que proponía el microkernel.
El caso de Windows tiene una lección concreta. En 1996, el sistema gráfico se movió del modo usuario al modo kernel para ganar velocidad. Durante las dos décadas siguientes fue una de las fuentes principales de fallos de seguridad del sistema. Es la tabla de la sección 2, leída en un caso real.
7. Linux: monolítico y modular
Linux es monolítico, y resuelve con otro recurso uno de los inconvenientes prácticos de ese diseño. Un kernel de una sola pieza que debe funcionar en cualquier equipo tendría que incluir los drivers de todo el hardware existente: sería enorme, y la mayor parte no se usaría nunca. La solución son los módulos cargables, que presentamos en el tema 11.
Un módulo del kernel es un archivo de código compilado que puede incorporarse a un kernel en ejecución, y retirarse de él, sin reiniciar el sistema. Una vez cargado, forma parte del kernel: se ejecuta en modo kernel, en su espacio de direcciones y con todos sus privilegios.
La última frase es la que importa, y la que se confunde con más facilidad. Los módulos no son servidores de un microkernel. La semejanza es superficial: en ambos casos hay piezas separadas. La diferencia es de fondo:
| Módulo de Linux | Servidor de un microkernel | |
|---|---|---|
| Se ejecuta en | Modo kernel | Modo usuario |
| Espacio de direcciones | El del kernel, compartido con todo lo demás | Propio y protegido |
| Se comunica con el resto mediante | Llamadas a función | Mensajes |
| Si tiene un error | Puede detener el sistema | Termina él solo |
| La separación es | De organización: en el disco y al cargar | De protección: la impone el hardware |
Los módulos aportan flexibilidad, no aislamiento. Permiten un kernel base pequeño, que crece según el hardware presente; es lo que hizo necesario el initramfs del tema 21. Y permiten añadir un driver sin recompilar ni reiniciar. Casi todo lo que no es imprescindible para arrancar se distribuye así: drivers, sistemas de archivos, protocolos de red.
Windows y macOS tienen mecanismos equivalentes: los archivos .sys y las extensiones del kernel.
8. Gestionar los módulos
Cinco comandos cubren todo el trabajo habitual con módulos:
| Comando | Función |
|---|---|
lsmod | Lista los módulos cargados. |
modinfo módulo | Muestra la información de un módulo: archivo, descripción, licencia, dependencias, parámetros. |
modprobe módulo | Carga un módulo por su nombre, junto con los que necesite. |
modprobe -r módulo | Lo descarga, si nadie lo está usando. |
insmod archivo.ko, rmmod módulo | Cargan y descargan un archivo concreto, sin resolver dependencias. Se usan al desarrollar. |
$ lsmod | wc -l
143
$ lsmod | grep -E '^Module|^nvme'
Module Size Used by
nvme 61440 2
nvme_core 212992 3 nvme
De los más de seis mil módulos disponibles que contamos en el tema 11, este equipo tiene cargados unos ciento cuarenta. La columna Size es la memoria que ocupa cada uno, y Used by indica cuántos usuarios tiene y qué otros módulos dependen de él: nvme_core no puede descargarse mientras nvme siga cargado.
Casi nunca hace falta cargar un módulo a mano. Cuando el kernel descubre un dispositivo durante la enumeración del tema 8, publica sus identificadores, y un servicio del sistema busca qué módulo declaró ser capaz de manejarlos. Esa declaración está en el propio módulo:
$ modinfo nvme | grep -E '^(filename|license|depends)|bc01sc08'
filename: /lib/modules/6.8.0-45-generic/kernel/drivers/nvme/host/nvme.ko.zst
license: GPL
alias: pci:v*d*sv*sd*bc01sc08i02*
depends: nvme-core
La línea alias es un patrón. Dice: «sirvo para cualquier dispositivo PCI, de cualquier fabricante y modelo, cuya clase sea 01, subclase 08 e interfaz 02», que es la identificación normalizada de un disco NVMe. Es la manera en que un equipo recién instalado reconoce su hardware sin que nadie le indique qué drivers cargar.
9. Experimento: cargar y descargar un módulo
Para ver el mecanismo sin riesgo sirve dummy, un módulo que viene con el kernel. Crea una interfaz de red ficticia, que descarta todo lo que se le envía; se usa para pruebas.
$ lsmod | grep dummy # no está cargado
$ ip link show dummy0
Device "dummy0" does not exist.
$ sudo modprobe dummy # incorporarlo al kernel
$ lsmod | grep dummy
dummy 12288 0
$ ip link show dummy0
5: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6a:1f:3c:92:0b:e4 brd ff:ff:ff:ff:ff:ff
$ sudo modprobe -r dummy # retirarlo
$ ip link show dummy0
Device "dummy0" does not exist.
Entre el segundo y el cuarto comando, el kernel en ejecución cambió: adquirió doce kilobytes de código nuevo, y con ellos la capacidad de manejar un tipo de dispositivo que un instante antes no conocía. No hubo reinicio, y ningún programa en marcha lo notó. Al retirarlo, el dispositivo desapareció con él.
Hizo falta sudo, y por una buena razón. Cargar un módulo es añadir código que se ejecutará con privilegio total. Es la operación que mencionamos en el tema 12 al separar root del modo kernel: el administrador no se ejecuta en el anillo 0, pero puede pedirle al kernel que acepte código en él.
El kernel de WSL trae casi todo incorporado y muy pocos módulos, de modo que lsmod muestra una lista breve y modprobe dummy probablemente falle. Para esta sección y la siguiente conviene una máquina virtual.
10. Escribir un módulo
Un módulo es código C, con las particularidades que enumeramos en el tema 11: no tiene función main, no dispone de la biblioteca de C y se ejecuta en modo kernel. El más pequeño posible tiene dos funciones, una que el kernel llama al cargarlo y otra al descargarlo. A este le añadimos la línea de ensamblador de anillo.c, el programa del tema 12 que consultaba su nivel de privilegio.
Este código es inofensivo, pero se ejecuta dentro del kernel. Un error al modificarlo no produce un fallo de segmentación: puede colgar el sistema. Es precisamente lo que el experimento pretende hacer sentir. Usa un equipo donde reiniciar no importe.
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Modulo minimo de ejemplo");
/* El kernel llama a esta función al cargar el módulo. */
static int __init hola_inicio(void)
{
unsigned long cs;
asm volatile ("mov %%cs, %0" : "=r"(cs)); /* igual que en anillo.c */
pr_info("hola: modulo cargado, en el anillo %lu\n", cs & 3);
return 0; /* 0 = carga correcta */
}
/* Y a esta, al descargarlo. */
static void __exit hola_fin(void)
{
pr_info("hola: modulo descargado\n");
}
module_init(hola_inicio);
module_exit(hola_fin);
obj-m += hola.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Un módulo no se compila con una orden directa a gcc, sino con el sistema de construcción del propio kernel, que aplica las mismas opciones con que se compiló el kernel en uso. Para eso hace falta instalar sus archivos de cabecera.
$ sudo apt install linux-headers-$(uname -r) # en Fedora: sudo dnf install kernel-devel
$ make
…
LD [M] /home/diego/modulo/hola.ko
$ sudo insmod hola.ko
$ lsmod | grep hola
hola 12288 0
$ sudo dmesg | tail -3
[ 5123.118822] hola: loading out-of-tree module taints kernel.
[ 5123.118870] hola: module verification failed: signature and/or required key missing - tainting kernel
[ 5123.119412] hola: modulo cargado, en el anillo 0
$ sudo rmmod hola
$ sudo dmesg | tail -1
[ 5140.002133] hola: modulo descargado
La línea resaltada es el resultado del experimento. En el tema 12, el mismo cálculo dentro de un programa daba 3, incluso ejecutado con sudo. Aquí da 0. El código es nuestro, lo escribimos y compilamos como cualquier otro, y se está ejecutando en el nivel de privilegio del kernel.
Varios detalles del listado y de la salida confirman lo dicho sobre los módulos:
- No hay
printf.pr_infoes una función del kernel, que escribe en el búfer de mensajes que leedmesg. No hay terminal ni salida estándar: dentro del kernel no existen los descriptores de un proceso. - La función de inicio termina y el módulo permanece. No es un programa que se ejecuta de principio a fin. Es código que queda disponible para cuando el kernel lo necesite.
- Las dos primeras líneas de
dmesgson advertencias. El kernel deja constancia de que se le incorporó código que no forma parte de su distribución y que no está firmado, y se marca a sí mismo como «contaminado» (tainted). Si más tarde ocurre un fallo, esa marca avisa a quien lo investigue de que había código ajeno.
La segunda advertencia se vuelve un obstáculo si el equipo tiene activado el arranque seguro. En ese caso insmod falla con el mensaje Key was rejected by service. Es el último eslabón de la cadena de confianza del tema 20: el firmware verifica al cargador, el cargador al kernel, y el kernel a cada módulo. Un módulo sin firma válida no entra.
11. Sacar código del kernel, sin cambiar de arquitectura
Los sistemas monolíticos no se convirtieron en microkernels, pero tomaron de ellos la idea central: lo que pueda ejecutarse fuera, mejor fuera. Linux ofrece varios mecanismos para hacerlo caso por caso:
| Mecanismo | Qué permite | Ejemplos |
|---|---|---|
| FUSE | Escribir un sistema de archivos como un programa común. El kernel conserva un módulo pequeño que reenvía cada operación a ese programa. | Montar por red el directorio de otro equipo; leer discos con formato de Windows. |
| Drivers en espacio de usuario | Manejar un dispositivo desde un proceso, al que el kernel concede acceso solo a ese dispositivo. | Impresoras, escáneres y la mayoría de los dispositivos USB poco comunes. |
| Reparto del trabajo | Dejar en el kernel solo la parte que toca el hardware, y lo demás en bibliotecas. | Los drivers de video: la mayor parte de su código está en espacio de usuario. |
| eBPF | Lo contrario: cargar en el kernel programas pequeños, que el kernel analiza antes de aceptar para comprobar que no pueden dañarlo. | Las herramientas de observación que mencionamos en el tema 16; filtros de red. |
FUSE es, literalmente, la arquitectura de microkernel aplicada a un componente: un servidor de archivos en modo usuario, con el que el kernel se comunica por mensajes. Tiene el costo que calculamos en la sección 4, y por eso se usa donde el rendimiento importa poco y la comodidad mucho. Un error en un sistema de archivos así termina con un proceso caído y un directorio inaccesible, no con un pánico.
La última fila ataca el problema desde el otro lado. En lugar de sacar código del kernel para que sus errores no lo alcancen, admite código dentro con la condición de poder demostrar de antemano que es inofensivo: que termina, que no accede a memoria indebida. Es una tercera vía entre confiar en todo y aislarlo todo.
12. Otras ideas
Las tres arquitecturas vistas cubren casi todos los sistemas en uso. Hay otras propuestas que conviene reconocer cuando aparecen:
| Arquitectura | Idea | Dónde se la encuentra |
|---|---|---|
| Por capas | El sistema se organiza en niveles; cada uno usa solo los servicios del inferior. | Fue un diseño de los años sesenta. Hoy es un criterio de orden interno, no una arquitectura completa. |
| Exokernel | El kernel solo reparte el hardware de forma segura, sin ofrecer abstracciones. Los archivos y los procesos los implementa una biblioteca dentro de cada aplicación. | Investigación. Sus ideas influyeron en las técnicas de red de alto rendimiento. |
| Unikernel | La aplicación y las partes del sistema operativo que necesita se compilan juntas en una sola imagen, que arranca directamente sobre un hipervisor. No hay modo usuario ni otros procesos. | Servicios en la nube muy especializados, donde el hipervisor ya proporciona el aislamiento. |
El unikernel cierra un círculo. Es la misma estructura del RTOS del tema 23, donde el kernel y la aplicación se enlazaban en una sola imagen sin protección entre ellos, trasladada del microcontrolador al centro de datos. Y hace explícito algo que este tema muestra desde la primera sección: el aislamiento tiene que estar en algún nivel, pero no hace falta pagarlo en todos.
13. Cuadro comparativo
| Monolítico | Microkernel | Híbrido | |
|---|---|---|---|
| En modo kernel | Todos los servicios | Mensajes, planificación y memoria básica | Casi todos los servicios |
| Drivers y sistemas de archivos | Dentro del kernel | Procesos en modo usuario | Dentro, en su mayoría |
| Comunicación interna | Llamadas a función | Mensajes entre procesos | Llamadas a función, con estructura de mensajes |
| Rendimiento | El mejor | Menor, por los cambios de contexto | Cercano al monolítico |
| Un driver defectuoso | Puede detener el sistema | Se reinicia el driver | Puede detener el sistema |
| Código con privilegio total | Millones de líneas | Decenas de miles | Millones de líneas |
| Ejemplos | Linux, FreeBSD | QNX, seL4, MINIX 3 | Windows NT, XNU |
| Predomina en | Servidores, escritorio, móviles | Automóviles, equipos médicos, procesadores de seguridad | Escritorio y móviles comerciales |
La tabla presenta tres columnas; la realidad es una línea continua. Linux es monolítico y usa servidores en modo usuario cuando conviene. Windows y macOS son híbridos y cada año sacan más drivers del kernel. Los microkernels incorporan atajos para acercarse al rendimiento de los monolíticos. Todos los diseños se mueven hacia el mismo punto medio, empujados por dos fuerzas que ya conocemos: el costo de cruzar la frontera, que medimos en el tema 13, y el costo de un error del lado privilegiado, que vimos en el tema 18.
En el tema 25 dejamos el interior del kernel y miramos los sistemas completos: las grandes familias y lo que cambia para quien programa sobre cada una.
14. Resumen
- La arquitectura de un kernel es la decisión sobre qué componentes se ejecutan en modo kernel y cuáles como procesos de usuario.
- En un kernel monolítico, todos los servicios forman un único programa en modo kernel. Es rápido, y un error en cualquier parte afecta a todo.
- Un microkernel conserva en modo kernel solo los mensajes, la planificación y la memoria básica. Los drivers y los sistemas de archivos son procesos aislados, que pueden fallar y reiniciarse.
- El costo del microkernel es el paso de mensajes: una operación que en un kernel monolítico es una llamada a función requiere varios cambios de contexto.
- Los sistemas monolíticos dominan en servidores y equipos personales; los microkernels, donde la confiabilidad es el requisito principal.
- Un kernel híbrido, como los de Windows y macOS, tiene estructura interna de microkernel y ejecuta casi todos sus servicios en modo kernel.
- Linux es monolítico y modular. Un módulo se carga y descarga en marcha, y una vez cargado es parte del kernel, con todos sus privilegios: da flexibilidad, no aislamiento.
lsmod,modinfoymodprobelistan, describen y cargan módulos. El sistema los carga solo, según los identificadores del hardware detectado.- Un módulo propio se ejecuta en el anillo 0. Cargarlo exige ser administrador y, con el arranque seguro, que esté firmado.
- Mecanismos como FUSE y los drivers en espacio de usuario aplican la idea del microkernel a componentes concretos de un sistema monolítico.
15. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué decide la arquitectura de un kernel?
Qué componentes del sistema operativo se ejecutan en modo kernel, compartiendo un espacio de direcciones, y cuáles se ejecutan como procesos en modo usuario, cada uno con su espacio protegido.
2. «Monolítico» ¿significa que el código no está organizado?
No. El código está dividido en subsistemas con interfaces definidas. Significa que entre ellos no hay barreras de protección: todos se ejecutan en modo kernel, en el mismo espacio de direcciones, y se llaman con llamadas a función.
3. ¿Qué tres cosas conserva un microkernel en modo kernel?
La gestión básica de los espacios de direcciones, la planificación de los procesos y la comunicación entre ellos mediante mensajes.
4. En un microkernel, ¿qué ocurre si el driver del disco tiene un puntero nulo?
El driver es un proceso en modo usuario, de modo que recibe una señal y termina, como cualquier programa. El sistema puede reiniciarlo. El kernel y los demás servidores no se ven afectados.
5. ¿Por qué leer un archivo cuesta más en un microkernel?
Porque la aplicación, el servidor de archivos y el driver son procesos distintos. Cada pedido y cada respuesta entre ellos es un mensaje que pasa por el kernel y requiere un cambio de contexto. En un kernel monolítico, esas mismas interacciones son llamadas a función.
6. ¿Qué sostenía cada parte en el debate de 1992, y cómo resultó?
Tanenbaum sostenía que el diseño monolítico era anticuado y que el futuro eran los microkernels. Torvalds, que en la práctica los microkernels existentes rendían menos y que su kernel funcionaba. En equipos de uso general ganó el diseño monolítico; en sistemas críticos, los microkernels.
7. ¿Qué es un kernel híbrido, y por qué algunos objetan el término?
Es un kernel con estructura interna de microkernel que ejecuta la mayoría de sus servicios en modo kernel. La objeción es que, si los servicios comparten privilegios y espacio de direcciones, un error en uno afecta a todos, y en la práctica se comporta como un kernel monolítico.
8. ¿En qué se diferencia un módulo de Linux de un servidor de un microkernel?
El módulo, una vez cargado, se ejecuta en modo kernel, dentro del espacio de direcciones del kernel, y un error suyo puede detener el sistema. El servidor se ejecuta en modo usuario, en su propio espacio protegido, y un error suyo solo lo afecta a él.
9. Entonces, ¿qué ventaja aportan los módulos?
Flexibilidad. Permiten un kernel base pequeño, que incorpora solo los drivers del hardware presente, y añadir o quitar funciones sin recompilar ni reiniciar.
10. ¿Cómo sabe el sistema qué módulo cargar para un dispositivo recién detectado?
Cada módulo declara, en sus líneas alias, los identificadores de los dispositivos que sabe manejar. Cuando el kernel detecta un dispositivo, publica sus identificadores, y el sistema carga el módulo cuyo patrón coincide.
11. El módulo hola informa «anillo 0», y anillo.c con sudo informaba 3. ¿Por qué?
Porque anillo es un programa: se ejecuta en modo usuario, sea cual sea el usuario que lo lance. El módulo se incorpora al kernel y su código se ejecuta en modo kernel. Ser administrador no cambia el modo; permite pedirle al kernel que cargue código.
12. ¿Por qué el kernel se marca como «contaminado» al cargar hola?
Para dejar constancia de que se le incorporó código que no pertenece a su distribución y no está firmado. Si después ocurre un fallo, esa marca indica que el estado del kernel pudo ser alterado por código ajeno.
13. ¿En qué sentido FUSE es «un microkernel para un solo componente»?
El sistema de archivos se ejecuta como un proceso en modo usuario, y el kernel le reenvía cada operación como un mensaje. Tiene las propiedades del microkernel para esa pieza: un error no detiene el sistema, y cada operación cuesta cambios de contexto.
Para practicar
- Cuenta los módulos cargados en tu equipo y ordénalos por tamaño con
lsmod | sort -k2 -n | tail. ¿Qué son los cinco mayores? Usamodinfo -dpara ver la descripción de cada uno. - Elige un módulo cargado que tenga dependientes en la columna
Used byy dibuja el árbol de dependencias, ayudándote conmodinfo -F depends. - Intenta descargar el módulo de tu disco con
sudo modprobe -r. ¿Qué responde el sistema, y por qué es una suerte que lo haga? - Repite el experimento de la sección 9 con
sudo modprobe dummy numdummies=3. ¿Qué cambia? Busca ese parámetro en la salida demodinfo dummy. - Compila y carga
holaen una máquina virtual. Comprueba la marca concat /proc/sys/kernel/taintedantes y después. - En esa misma máquina, añade
asm volatile ("hlt");a la función de inicio. En el tema 12 esa instrucción terminó con el programa. ¿Qué ocurre ahora? Explica el resultado recordando qué hacehlty qué llega a continuación. - Lista los sistemas de archivos de tipo FUSE montados en tu sistema con
findmnt -t fuse,fuseblk,fuse.portal. Si tienes un entorno gráfico, es probable que haya alguno. - Para cada uno de estos casos, indica qué arquitectura elegirías y por qué: el sistema de frenado de un automóvil, un servidor de base de datos, un reloj inteligente, el procesador que guarda las huellas dactilares de un teléfono.