Microkernels frente a kernels monolíticos: revisitando los compromisos
Pocos debates de diseño de sistemas operativos han durado tanto como el debate entre microkernels y kernels monolíticos.
En la superficie, la diferencia parece simple:
- los kernels monolíticos mantienen la mayoría de los servicios del sistema operativo dentro del kernel
- los microkernels mueven la mayoría de los servicios al espacio de usuario
En la práctica, el compromiso es más sutil.
La verdadera pregunta no es si una estructura es universalmente más rápida, más limpia o más segura. La verdadera pregunta es dónde deben vivir la autoridad, la complejidad, los fallos y los costes de rendimiento.
Esta publicación revisita ese compromiso, explica por qué muchos argumentos antiguos sobre microkernels se simplificaron demasiado y muestra por qué los sistemas modernos como EriX vuelven a hacer práctico el modelo de microkernel.
La forma histórica del debate⌗
Los primeros sistemas operativos se construyeron bajo restricciones de hardware severas.
La memoria era limitada. Las CPU eran más lentas. Los cambios de contexto eran caros. Las cachés, las TLB, los sistemas multiprocesador y las rutas rápidas de syscall eran mucho menos capaces que hoy.
Bajo esas restricciones, los kernels monolíticos eran una opción natural.
Los sistemas tipo Unix colocaban sistemas de archivos, controladores de dispositivos, redes, gestión de procesos y muchos otros servicios dentro de un único espacio de direcciones privilegiado del kernel. Ese diseño hacía baratas muchas operaciones:
- un sistema de archivos podía llamar directamente a la capa de bloques
- una pila de red podía acceder directamente a estructuras del controlador
- los subsistemas del kernel podían compartir datos sin IPC
El resultado era eficiente y pragmático.
También significaba que grandes cantidades de código se ejecutaban con privilegios completos de kernel.
Por qué aparecieron los microkernels⌗
Los microkernels partieron de otra observación:
La mayor parte del código de un sistema operativo no necesita autoridad total sobre la máquina.
Un sistema de archivos no necesita modificar tablas de páginas arbitrarias. Un controlador de teclado no necesita acceder a todos los procesos. Una pila de red no necesita controlar el planificador.
Los microkernels mantienen en el kernel solo los mecanismos mínimos necesarios, normalmente:
- planificación
- gestión de espacios de direcciones
- comunicación entre procesos
- gestión de capacidades o manejadores
- entrega de interrupciones y excepciones
Los servicios de nivel superior se ejecutan como procesos ordinarios de espacio de usuario.
Esto da al sistema un aislamiento más fuerte. Un fallo de controlador no tiene por qué ser un fallo del kernel. Un error en un sistema de archivos no se convierte automáticamente en corrupción arbitraria de memoria del kernel. La autoridad puede distribuirse con más precisión.
La idea era convincente, pero las primeras implementaciones a menudo tuvieron problemas de rendimiento y compatibilidad.
El primer problema de rendimiento⌗
La crítica clásica a los microkernels es que son lentos.
Esa crítica no apareció de la nada.
Algunos microkernels tempranos colocaban servicios tradicionales del sistema operativo detrás de muchos servidores separados en espacio de usuario, y luego intentaban preservar interfaces Unix familiares por encima. Una operación simple podía convertirse en una cadena de mensajes:
- aplicación al servidor de archivos
- servidor de archivos al gestor de memoria
- gestor de memoria al paginador
- paginador al servicio de bloques
- servicio de bloques al controlador
Cada paso podía implicar un cambio de contexto, validación de mensajes, una decisión de planificación y a veces copias.
Si las interfaces son demasiado conversacionales, el coste se acumula.
El error fue convertir esto en una regla universal:
Los microkernels son lentos.
Una regla más precisa es:
Las rutas IPC mal diseñadas y los límites de servicio demasiado conversacionales son lentos.
Esa distinción importa.
La ruta rápida monolítica⌗
Los kernels monolíticos pueden ser extremadamente rápidos porque evitan muchos límites de protección.
Un sistema de archivos dentro del kernel puede llamar a una capa de bloques dentro del kernel con una llamada de función normal. Un controlador puede compartir memoria directamente con otro subsistema. No hace falta serializar cada petición en un formato de mensaje.
Esta es una ventaja real.
Pero no es gratuita.
La ruta rápida monolítica suele venir con:
- más código privilegiado
- más estado mutable compartido
- más complejidad de bloqueos internos del kernel
- más formas de que un subsistema corrompa a otro
- una base de cómputo confiable más grande
El rendimiento no consiste solo en contar instrucciones. También incluye el comportamiento de caché, la contención de bloqueos, la contención de fallos, la recuperación y el coste de mantener la corrección a lo largo del tiempo.
Un kernel monolítico puede ganar un microbenchmark bruto y aun así hacer más difíciles el aislamiento y la auditabilidad.
Mito de rendimiento: todo límite es fatal⌗
Un mito común es que cada límite de microkernel es tan caro que el diseño no puede competir.
Esa visión está desactualizada.
Un límite tiene un coste, pero los sistemas modernos pueden hacer que ese coste sea manejable:
- rutas rápidas de syscall y retorno
- mejores heurísticas de planificación
- rutas de datos con memoria compartida
- mapeo de páginas en lugar de copias masivas
- peticiones por lotes
- entrega asíncrona de eventos
- ABI de IPC diseñadas con cuidado
El objetivo importante de diseño es mantener la política fuera del kernel sin obligar a que cada byte de datos pase por el kernel.
El kernel debe mediar autoridad. No necesariamente debe mover todos los datos.
Mito de rendimiento: IPC significa copiarlo todo⌗
IPC suele imaginarse como “copiar todo este búfer del proceso A al proceso B”.
Ese es solo un diseño posible.
Un microkernel puede pasar pequeños mensajes de control mientras transfiere autoridad sobre memoria compartida, frames, endpoints u objetos de dispositivo. La ruta de datos cara puede permanecer mapeada, mientras el kernel solo valida quién está autorizado a acceder a ella.
Esto es central en el diseño basado en capacidades.
En lugar de copiar grandes estructuras de datos a través de un subsistema privilegiado, un proceso puede recibir una capacidad que autoriza el acceso a un objeto específico con derechos específicos.
El kernel sigue siendo responsable de aplicar la transferencia. No necesita entender todos los protocolos de alto nivel construidos sobre esa transferencia.
Mito de rendimiento: los controladores en espacio de usuario no son prácticos⌗
Los controladores en espacio de usuario suelen tratarse como una idea de investigación.
La preocupación es comprensible. El acceso al hardware es sensible, las interrupciones dependen del tiempo y los controladores suelen estar en rutas calientes.
Pero la mayoría de los controladores no necesitan autoridad total de kernel.
Un controlador normalmente necesita acceso a:
- un rango concreto de puertos de E/S
- una región MMIO concreta
- una línea de interrupción concreta
- una disposición concreta de DMA o búferes
Esas son formas de autoridad más estrechas que “todo el kernel”.
Si el kernel puede delegar exactamente esos recursos, un controlador puede ejecutarse fuera del kernel y aun así hacer trabajo útil. Si falla, el sistema tiene la posibilidad de detener, reiniciar o reemplazar ese controlador sin tratar el fallo como corrupción de memoria del kernel.
El compromiso es real: los controladores en espacio de usuario necesitan buen IPC, entrega cuidadosa de interrupciones y propiedad explícita de recursos. Pero el modelo no es intrínsecamente impráctico.
Qué pone EriX en el kernel⌗
EriX está diseñado como un microkernel de capacidades.
El kernel de EriX es deliberadamente mínimo en política. Sus documentos de arquitectura definen que el kernel es responsable de:
- validar la entrega del bootloader al kernel
- gestionar objetos básicos del kernel y la semántica de capacidades
- crear la tarea raíz
- exponer puntos de entrada de traps, syscalls e interrupciones
El kernel explícitamente no es responsable de:
- política del sistema
- política de orquestación de procesos
- política de memoria de alto nivel
- política de ciclo de vida de servicios
Esta es la línea del microkernel en la práctica.
El kernel empieza con autoridad de máquina, pero debe convertir esa autoridad en objetos explícitos del kernel y referencias de capacidad. No debe filtrarse autoridad ambiental al espacio de usuario.
Qué mueve EriX fuera del kernel⌗
EriX coloca la funcionalidad que porta política en servicios de espacio de usuario.
Por ejemplo:
rootdes la primera autoridad de espacio de usuario que porta políticaprocdposee la gestión del ciclo de vida de procesosdevicedposee la política de controladores y la orquestación de su iniciovfsdposee el espacio de nombres público del sistema de archivos- proveedores de sistema de archivos como
ramfsd,e2fsdyfatdpermanecen como pares backend privados detrás devfsd
Esto no es solo “mover código fuera del kernel” como elección estética.
Cada límite de servicio define un límite de autoridad.
rootd distribuye capacidades de arranque de privilegio mínimo. procd crea e
inicia procesos mediante creación de hijos por etapas y permisos de instalación.
deviced no se convierte directamente en el kernel; pide a procd que gestione
procesos de controlador y pasa solo la autoridad de controlador requerida para
cada rol.
Esa estructura es más verbosa que un grafo de llamadas de kernel monolítico, pero hace visible el flujo de autoridad.
Autoridad estrecha en lugar de privilegio amplio⌗
Uno de los detalles de implementación más importantes de EriX es alejarse de un endpoint raíz amplio como superficie normal de control en tiempo de ejecución.
El kernel actual expone familias estrechas de endpoints de control del kernel para trabajos específicos:
- control de tiempo
- control de interrupciones
- eventos de hotplug
- lecturas de configuración PCI
- acceso a consola y framebuffer
- E/S COM1
- E/S i8042
- retyping de memoria
- mapeo de VSpace
- resolución de fallos del paginador
- control de procesos
- lecturas ACPI
El despacho en tiempo de ejecución se determina por el objeto endpoint y su tipo, no por un número de slot global privilegiado.
Esto importa porque una tarea no obtiene autoridad solo por conocer un valor de slot convencional. Debe poseer realmente la capacidad correcta en su propio espacio local de capacidades.
Por ejemplo, drv-serial recibe autoridad de E/S específica de COM1.
drv-i8042 recibe autoridad de E/S específica de i8042. drv-acpi recibe
autoridad de lectura ACPI. probed recibe autoridad de lectura de configuración
PCI.
Esa es una forma de seguridad distinta a colocar todas esas operaciones detrás de un único manejador amplio del kernel.
Memoria de dispositivo como objeto explícito⌗
EriX también trata la autoridad sobre memoria de dispositivo como explícita y tipada.
El kernel tiene un CAP_TYPE_DEVICE_FRAME distinto para memoria de dispositivo
validada. En la ruta de almacenamiento, puede derivarse para deviced un frame
MMIO respaldado por BAR, y deviced puede instalar solo ese frame de
dispositivo derivado en el paquete de inicio por etapas del controlador.
La idea no es que los controladores de dispositivo se vuelvan simples.
La idea es que la autoridad MMIO no se confunda con frames de RAM ordinarios y no se exponga mediante una vía genérica de “hacer cualquier cosa con memoria de dispositivo”.
Este es exactamente el tipo de detalle que hace viables a los microkernels modernos: el acceso al hardware se delega como un objeto preciso con derechos precisos.
IPC como ABI, no como accidente⌗
En un kernel monolítico, muchas interfaces internas son llamadas de función ordinarias.
En un microkernel, IPC pasa a formar parte de la ABI del sistema. Eso lo hace más importante, no menos.
EriX trata IPC como un contrato compartido:
- las cabeceras de mensajes tienen versión
- los layouts son fijos
- el parseo usa aritmética comprobada
- las cargas mal formadas fallan de forma cerrada
- las transferencias de capacidades son explícitas
- los mensajes de runtime que transportan transferencias requieren
GRANT
Esto es lo contrario de tratar IPC como una ocurrencia tardía.
El coste de IPC se controla en parte mediante implementación, pero también mediante diseño de interfaces. Una ABI cuidadosamente diseñada evita viajes innecesarios, mantiene los mensajes acotados y separa la transferencia de control del movimiento de datos.
Por qué los microkernels vuelven a ser viables⌗
Los microkernels son más viables hoy por varias razones.
1. El hardware cambió⌗
El coste relativo de un límite de protección ha cambiado.
Los cambios de contexto y las syscalls siguen sin ser gratuitos, pero las CPU modernas, los sistemas de memoria y los mecanismos de interrupción hacen que el coste bruto sea menos decisivo que cuando se juzgaron los primeros experimentos con microkernels.
Al mismo tiempo, los sistemas modernos son más complejos y están más expuestos. El coste de comprometer el kernel ha aumentado.
El aislamiento es más valioso ahora.
2. Entendemos mejor IPC⌗
La lección de los sistemas anteriores no es “evitar IPC”.
La lección es:
- evitar IPC innecesario
- evitar protocolos demasiado conversacionales
- evitar copiar grandes datos cuando basta con transferir autoridad
- diseñar límites de servicio alrededor de propiedad real
Los microkernels son viables cuando IPC se trata como un problema de diseño de primera clase.
3. Las capacidades hacen útiles los límites⌗
Mover código al espacio de usuario es solo la mitad de la historia.
Si todos los servidores de espacio de usuario siguen recibiendo privilegio implícito amplio, el sistema ha recreado en gran medida un monolito con cambios de contexto adicionales.
Las capacidades dan significado al límite.
En EriX, la autoridad se representa mediante capacidades tipadas con derechos explícitos. Los servicios validan las capacidades que reciben. Los paquetes de inicio describen la autoridad declarada. El código del kernel y de los servicios evita tratar los números de slot canónicos como permiso ambiental.
Eso hace que la descomposición sea más que modularidad. Hace que la descomposición sea parte del modelo de seguridad.
4. El lenguaje y las herramientas mejoraron⌗
Los lenguajes de implementación y las herramientas modernas también cambian el compromiso.
Rust no elimina los errores de sistemas operativos, pero hace más difícil escribir accidentalmente muchos errores de seguridad de memoria. También hace visibles los límites inseguros durante la revisión.
Para un sistema microkernel, esto es especialmente útil. El kernel puede permanecer pequeño y auditable, mientras que los servicios de espacio de usuario pueden seguir escribiéndose con garantías de seguridad más fuertes que los componentes de sistema tradicionales dominados por C.
EriX combina esto con un enfoque de sala limpia y sin crates de terceros, lo que mantiene el sistema más fácil de auditar aunque aumente el trabajo de implementación.
Los costes restantes⌗
Los microkernels siguen teniendo costes reales.
Requieren:
- lógica de inicio más explícita
- contratos IPC cuidadosamente versionados
- supervisión robusta de servicios
- más atención al procesamiento por lotes y al movimiento de datos
- propiedad clara de cada capacidad
- buen trazado y medición de rendimiento
También mueven parte de la complejidad fuera del kernel en lugar de eliminarla.
rootd, procd, deviced y los servicios de sistema de archivos siguen
necesitando un diseño cuidadoso. Pueden estar fuera del kernel, pero aun así
pueden ser componentes confiables para partes específicas del sistema.
La diferencia es que su autoridad puede ser más estrecha que la autoridad del kernel, y sus fallos pueden contenerse de forma más deliberada.
El compromiso revisitado⌗
El marco antiguo solía ser:
- los kernels monolíticos son rápidos
- los microkernels son limpios pero lentos
Ese marco es demasiado simple.
Un marco mejor es:
- los kernels monolíticos optimizan la cooperación directa dentro del kernel
- los microkernels optimizan la autoridad explícita y el aislamiento de fallos
- cualquiera de los dos diseños puede ser rápido o lento según la implementación
- cualquiera de los dos diseños puede volverse complejo si los límites se eligen mal
Para EriX, la elección de microkernel se sigue de los objetivos del sistema:
- base de cómputo confiable mínima
- autoridad explícita mediante capacidades
- separación estricta entre kernel y espacio de usuario
- límites de servicio auditables
- arranque y comportamiento de fallo deterministas
Esos objetivos no hacen irrelevante el rendimiento.
Definen dónde debe ocurrir el trabajo de rendimiento: IPC rápido, interfaces de servicio cuidadosas, rutas de datos con memoria compartida, familias estrechas de endpoints y transferencia explícita de capacidades.
Mirando hacia delante⌗
Los microkernels no son un atajo.
Exigen más disciplina de diseño inicial que un simple grafo de llamadas dentro del kernel. Obligan al sistema a definir temprano la autoridad, la propiedad y el comportamiento ante fallos.
Precisamente por eso son interesantes.
EriX usa el modelo de microkernel no porque esté de moda, sino porque encaja con la arquitectura: un kernel pequeño, autoridad mediada por capacidades y política implementada por servicios explícitos de espacio de usuario.
La próxima publicación examinará la idea que motiva gran parte de esta estructura: la base de cómputo confiable.
Veremos qué incluye realmente la TCB, por qué su tamaño afecta a la superficie de ataque y cómo EriX intenta mantener pequeño el código confiable moviendo la política a servicios de espacio de usuario explícitos y restringidos por capacidades.