Peu de débats de conception de systèmes d’exploitation ont duré aussi longtemps que celui qui oppose les microkernels aux kernels monolithiques.

En surface, la distinction paraît simple :

  • les kernels monolithiques gardent la plupart des services du système d’exploitation dans le kernel
  • les microkernels déplacent la plupart des services en espace utilisateur

En pratique, le compromis est plus subtil.

La vraie question n’est pas de savoir si une structure est universellement plus rapide, plus propre ou plus sûre. La vraie question est de savoir où doivent se trouver l’autorité, la complexité, les défaillances et les coûts de performance.

Cet article revisite ce compromis, explique pourquoi beaucoup d’anciens arguments sur les microkernels ont été trop simplifiés et montre pourquoi des systèmes modernes comme EriX rendent à nouveau le modèle microkernel viable.


La forme historique du débat

Les premiers systèmes d’exploitation ont été construits sous de fortes contraintes matérielles.

La mémoire était limitée. Les CPU étaient plus lents. Les changements de contexte étaient coûteux. Les caches, les TLB, les systèmes multiprocesseurs et les chemins syscall rapides étaient beaucoup moins capables qu’aujourd’hui.

Dans ces conditions, les kernels monolithiques étaient un choix naturel.

Les systèmes de type Unix plaçaient les systèmes de fichiers, les pilotes de périphériques, les piles réseau, la gestion des processus et beaucoup d’autres services dans un seul espace d’adressage privilégié du kernel. Cette conception rendait de nombreuses opérations peu coûteuses :

  • un système de fichiers pouvait appeler directement la couche bloc
  • une pile réseau pouvait accéder directement aux structures du pilote
  • les sous-systèmes du kernel pouvaient partager des données sans IPC

Le résultat était efficace et pragmatique.

Cela signifiait aussi que de grandes quantités de code s’exécutaient avec tous les privilèges du kernel.


Pourquoi les microkernels sont apparus

Les microkernels sont partis d’une autre observation :

La plupart du code d’un système d’exploitation n’a pas besoin de l’autorité complète de la machine.

Un système de fichiers n’a pas besoin de modifier des tables de pages arbitraires. Un pilote de clavier n’a pas besoin d’accéder à tous les processus. Une pile réseau n’a pas besoin de contrôler l’ordonnanceur.

Les microkernels ne gardent dans le kernel que les mécanismes les plus fondamentaux, généralement :

  • ordonnancement
  • gestion des espaces d’adressage
  • communication interprocessus
  • gestion des capacités ou des handles
  • livraison des interruptions et exceptions

Les services de plus haut niveau s’exécutent comme des processus ordinaires en espace utilisateur.

Cela donne au système une isolation plus forte. Le plantage d’un pilote ne doit pas nécessairement devenir un plantage du kernel. Un bogue dans un système de fichiers ne devient pas automatiquement une corruption arbitraire de la mémoire du kernel. L’autorité peut être distribuée plus précisément.

L’idée était convaincante, mais les premières implémentations ont souvent eu des difficultés de performance et de compatibilité.


Le premier problème de performance

La critique classique des microkernels est qu’ils sont lents.

Cette critique n’est pas sortie de nulle part.

Certains premiers systèmes microkernel plaçaient les services traditionnels du système d’exploitation derrière de nombreux serveurs séparés en espace utilisateur, puis essayaient de préserver des interfaces Unix familières par dessus. Une opération simple pouvait devenir une chaîne de messages :

  1. application vers serveur de fichiers
  2. serveur de fichiers vers gestionnaire de mémoire
  3. gestionnaire de mémoire vers pager
  4. pager vers service bloc
  5. service bloc vers pilote

Chaque étape pouvait impliquer un changement de contexte, une validation de message, une décision d’ordonnancement et parfois une copie.

Si les interfaces sont trop bavardes, le coût s’accumule.

L’erreur a été d’en faire une règle universelle :

Les microkernels sont lents.

Une règle plus juste est :

Les chemins IPC mal conçus et les frontières de service trop bavardes sont lents.

Cette distinction est importante.


Le chemin rapide monolithique

Les kernels monolithiques peuvent être extrêmement rapides parce qu’ils évitent de nombreuses frontières de protection.

Un système de fichiers dans le kernel peut appeler une couche bloc dans le kernel avec un simple appel de fonction. Un pilote peut partager directement de la mémoire avec un autre sous-système. Il n’est pas nécessaire de sérialiser chaque requête dans un format de message.

C’est un véritable avantage.

Mais il n’est pas gratuit.

Le chemin rapide monolithique s’accompagne souvent de :

  • plus de code privilégié
  • plus d’état mutable partagé
  • plus de complexité de verrouillage interne au kernel
  • plus de façons pour un sous-système d’en corrompre un autre
  • une base de calcul de confiance plus grande

La performance ne se limite pas au nombre d’instructions. Elle concerne aussi le comportement des caches, la contention des verrous, le confinement des défaillances, la récupération et le coût du maintien de la correction dans le temps.

Un kernel monolithique peut gagner un microbenchmark brut tout en rendant l’isolation et l’auditabilité plus difficiles.


Mythe de performance : chaque frontière est fatale

Un mythe courant affirme que chaque frontière de microkernel est si coûteuse que la conception ne peut pas être compétitive.

Cette vision est dépassée.

Une frontière a un coût, mais les systèmes modernes peuvent rendre ce coût gérable :

  • chemins syscall et retour rapides
  • meilleures heuristiques d’ordonnancement
  • chemins de données en mémoire partagée
  • mappage de pages au lieu de copies massives
  • requêtes groupées
  • livraison asynchrone d’événements
  • ABI IPC soigneusement conçues

L’objectif de conception important est de garder la politique hors du kernel sans forcer chaque octet de données à passer par le kernel.

Le kernel doit arbitrer l’autorité. Il ne doit pas nécessairement déplacer toutes les données.


Mythe de performance : IPC signifie tout copier

IPC est souvent imaginé comme “copier tout ce tampon du processus A vers le processus B”.

Ce n’est qu’une conception possible.

Un microkernel peut transmettre de petits messages de contrôle tout en transférant l’autorité sur de la mémoire partagée, des frames, des endpoints ou des objets de périphérique. Le chemin de données coûteux peut rester mappé, tandis que le kernel ne fait que valider qui est autorisé à y accéder.

C’est central dans une conception fondée sur les capacités.

Au lieu de copier de grandes structures de données à travers un sous-système privilégié, un processus peut recevoir une capacité qui autorise l’accès à un objet précis avec des droits précis.

Le kernel reste responsable de faire respecter le transfert. Il n’a pas besoin de comprendre tous les protocoles de haut niveau construits par-dessus ce transfert.


Mythe de performance : les pilotes en espace utilisateur ne sont pas pratiques

Les pilotes en espace utilisateur sont souvent traités comme une idée de recherche.

La préoccupation est compréhensible. L’accès au matériel est sensible, les interruptions dépendent du temps, et les pilotes se trouvent souvent sur des chemins chauds.

Mais la plupart des pilotes n’ont pas besoin de l’autorité complète du kernel.

Un pilote a généralement besoin d’accéder à :

  • une plage précise de ports d’E/S
  • une région MMIO précise
  • une ligne d’interruption précise
  • une disposition précise de DMA ou de tampons

Ce sont des formes d’autorité plus étroites que “tout le kernel”.

Si le kernel peut déléguer exactement ces ressources, un pilote peut s’exécuter hors du kernel tout en effectuant un travail utile. S’il échoue, le système a une chance d’arrêter, redémarrer ou remplacer ce pilote sans traiter la défaillance comme une corruption de mémoire du kernel.

Le compromis est réel : les pilotes en espace utilisateur ont besoin d’un bon IPC, d’une livraison soigneuse des interruptions et d’une propriété explicite des ressources. Mais le modèle n’est pas intrinsèquement impraticable.


Ce qu’EriX place dans le kernel

EriX est conçu comme un microkernel à capacités.

Le kernel d’EriX est volontairement minimal en politique. Ses documents d’architecture définissent le kernel comme responsable de :

  • valider le transfert du bootloader vers le kernel
  • gérer les objets de base du kernel et la sémantique des capacités
  • créer la tâche racine
  • exposer les points d’entrée trap, syscall et interruption

Le kernel n’est explicitement pas responsable de :

  • la politique du système
  • la politique d’orchestration des processus
  • la politique mémoire de haut niveau
  • la politique de cycle de vie des services

C’est la ligne microkernel en pratique.

Le kernel commence avec l’autorité de la machine, mais il doit convertir cette autorité en objets kernel explicites et en références de capacités. Aucune autorité ambiante ne doit fuir vers l’espace utilisateur.


Ce qu’EriX déplace hors du kernel

EriX place les fonctionnalités porteuses de politique dans des services en espace utilisateur.

Par exemple :

  • rootd est la première autorité d’espace utilisateur porteuse de politique
  • procd possède la gestion du cycle de vie des processus
  • deviced possède la politique des pilotes et l’orchestration de leur démarrage
  • vfsd possède l’espace de noms public du système de fichiers
  • les fournisseurs de systèmes de fichiers comme ramfsd, e2fsd et fatd restent des pairs backend privés derrière vfsd

Ce n’est pas simplement “déplacer du code hors du kernel” comme choix esthétique.

Chaque frontière de service définit une frontière d’autorité.

rootd distribue des capacités de démarrage de moindre privilège. procd crée et démarre des processus par création d’enfants par étapes et octrois d’installation. deviced ne devient pas directement le kernel ; il demande à procd de gérer les processus de pilotes et ne transmet que l’autorité de pilote requise pour chaque rôle.

Cette structure est plus verbeuse qu’un graphe d’appels de kernel monolithique, mais elle rend visible le flux d’autorité.


Autorité étroite au lieu d’un privilège large

L’un des détails d’implémentation les plus importants d’EriX est l’abandon d’un large endpoint racine comme surface normale de contrôle à l’exécution.

Le kernel actuel expose des familles étroites d’endpoints de contrôle du kernel pour des tâches spécifiques :

  • contrôle du temps
  • contrôle des interruptions
  • événements hotplug
  • lectures de configuration PCI
  • accès console et framebuffer
  • E/S COM1
  • E/S i8042
  • retypage mémoire
  • mappage VSpace
  • résolution de défauts par le pager
  • contrôle des processus
  • lectures ACPI

Le dispatch à l’exécution est déterminé par l’objet endpoint et son type, pas par un numéro de slot global privilégié.

C’est important parce qu’une tâche ne gagne pas d’autorité simplement en connaissant une valeur de slot conventionnelle. Elle doit réellement posséder la bonne capacité dans son propre espace local de capacités.

Par exemple, drv-serial reçoit l’autorité d’E/S spécifique à COM1. drv-i8042 reçoit l’autorité d’E/S spécifique à i8042. drv-acpi reçoit l’autorité de lecture ACPI. probed reçoit l’autorité de lecture de configuration PCI.

C’est une forme de sécurité différente de celle qui consisterait à placer toutes ces opérations derrière un seul handle kernel large.


La mémoire de périphérique comme objet explicite

EriX traite aussi l’autorité sur la mémoire de périphérique comme explicite et typée.

Le kernel possède un CAP_TYPE_DEVICE_FRAME distinct pour la mémoire de périphérique validée. Dans le chemin de stockage, un frame MMIO adossé à un BAR peut être dérivé pour deviced, puis deviced peut installer uniquement ce frame de périphérique dérivé dans le paquet de démarrage par étapes du pilote.

Le but n’est pas que les pilotes de périphériques deviennent simples.

Le but est que l’autorité MMIO ne soit pas confondue avec des frames de RAM ordinaires et ne soit pas exposée par une échappatoire générique du type “faire n’importe quoi avec la mémoire de périphérique”.

C’est exactement le genre de détail qui rend les microkernels modernes viables : l’accès au matériel est délégué sous forme d’objet précis avec des droits précis.


IPC comme ABI, pas comme accident

Dans un kernel monolithique, beaucoup d’interfaces internes sont de simples appels de fonction.

Dans un microkernel, IPC devient une partie de l’ABI du système. Cela le rend plus important, pas moins.

EriX traite IPC comme un contrat partagé :

  • les en-têtes de message sont versionnés
  • les layouts sont fixes
  • le parsing utilise de l’arithmétique vérifiée
  • les charges mal formées échouent de manière fermée
  • les transferts de capacités sont explicites
  • les messages runtime porteurs de transferts exigent GRANT

C’est l’opposé d’un IPC traité après coup.

Le coût d’IPC se contrôle en partie par l’implémentation, mais aussi par la conception des interfaces. Une ABI bien conçue évite les allers-retours inutiles, garde les messages bornés et sépare le transfert de contrôle du mouvement des données.


Pourquoi les microkernels sont à nouveau viables

Les microkernels sont aujourd’hui plus viables pour plusieurs raisons.

1. Le matériel a changé

Le coût relatif d’une frontière de protection a changé.

Les changements de contexte et les syscalls ne sont toujours pas gratuits, mais les CPU modernes, les systèmes mémoire et les mécanismes d’interruption rendent le coût brut moins décisif qu’à l’époque où les premières expériences microkernel étaient jugées.

Dans le même temps, les systèmes modernes sont plus complexes et plus exposés. Le coût d’une compromission du kernel a augmenté.

L’isolation a donc plus de valeur.


2. Nous comprenons mieux IPC

La leçon des anciens systèmes n’est pas “éviter IPC”.

La leçon est :

  • éviter l’IPC inutile
  • éviter les protocoles trop bavards
  • éviter de copier de grosses données quand un transfert d’autorité suffit
  • concevoir les frontières de service autour d’une vraie propriété

Les microkernels sont viables quand IPC est traité comme un problème de conception de premier ordre.


3. Les capacités rendent les frontières utiles

Déplacer du code en espace utilisateur n’est que la moitié de l’histoire.

Si chaque serveur en espace utilisateur reçoit encore un large privilège implicite, le système a surtout recréé un monolithe avec des changements de contexte supplémentaires.

Les capacités donnent du sens à la frontière.

Dans EriX, l’autorité est représentée par des capacités typées avec des droits explicites. Les services valident les capacités qu’ils reçoivent. Les paquets de démarrage décrivent l’autorité déclarée. Le code du kernel et des services évite de traiter les numéros de slots canoniques comme des permissions ambiantes.

Cela fait de la décomposition plus que de la modularité. Cela fait de la décomposition une partie du modèle de sécurité.


4. Les langages et les outils se sont améliorés

Les langages d’implémentation et les outils modernes changent aussi le compromis.

Rust n’élimine pas les bogues de systèmes d’exploitation, mais il rend plus difficile l’écriture accidentelle de nombreux défauts de sûreté mémoire. Il rend aussi les frontières unsafe visibles lors de la revue.

Pour un système microkernel, c’est particulièrement utile. Le kernel peut rester petit et auditable, tandis que les services en espace utilisateur peuvent être écrits avec de meilleures garanties de sûreté que les composants système traditionnellement lourds en C.

EriX combine cela avec une approche salle blanche et sans crates tiers, ce qui rend le système plus facile à auditer même si cela augmente le travail d’implémentation.


Les coûts restants

Les microkernels ont toujours de vrais coûts.

Ils exigent :

  • une logique de démarrage plus explicite
  • des contrats IPC soigneusement versionnés
  • une supervision robuste des services
  • plus d’attention au groupement des requêtes et au mouvement des données
  • une propriété claire de chaque capacité
  • de bonnes traces et mesures de performance

Ils déplacent aussi une partie de la complexité hors du kernel au lieu de la supprimer.

rootd, procd, deviced et les services de système de fichiers doivent toujours être conçus avec soin. Ils peuvent être hors du kernel, mais ils peuvent quand même être des composants de confiance pour certaines parties du système.

La différence est que leur autorité peut être plus étroite que celle du kernel, et que leurs défaillances peuvent être contenues plus délibérément.


Le compromis revisité

L’ancien cadrage était souvent :

  • les kernels monolithiques sont rapides
  • les microkernels sont propres mais lents

Ce cadrage est trop simple.

Un meilleur cadrage est :

  • les kernels monolithiques optimisent la coopération directe dans le kernel
  • les microkernels optimisent l’autorité explicite et l’isolation des défaillances
  • chaque conception peut être rapide ou lente selon l’implémentation
  • chaque conception peut devenir complexe si les frontières sont mal choisies

Pour EriX, le choix du microkernel découle des objectifs du système :

  • base de calcul de confiance minimale
  • autorité explicite par capacités
  • séparation stricte entre kernel et espace utilisateur
  • frontières de service auditables
  • démarrage et comportement de défaillance déterministes

Ces objectifs ne rendent pas la performance hors sujet.

Ils définissent où le travail de performance doit se faire : IPC rapide, interfaces de service soigneuses, chemins de données en mémoire partagée, familles d’endpoints étroites et transfert explicite de capacités.


Et ensuite

Les microkernels ne sont pas un raccourci.

Ils demandent plus de discipline de conception en amont qu’un simple graphe d’appels dans le kernel. Ils obligent le système à définir tôt l’autorité, la propriété et le comportement en cas de défaillance.

C’est précisément ce qui les rend intéressants.

EriX utilise le modèle microkernel non parce qu’il est à la mode, mais parce qu’il correspond à l’architecture : un petit kernel, une autorité médiée par des capacités et une politique implémentée par des services explicites en espace utilisateur.

Le prochain article examinera l’idée qui motive une grande partie de cette structure : la base de calcul de confiance.

Nous verrons ce que la TCB inclut réellement, pourquoi sa taille affecte la surface d’attaque et comment EriX essaie de garder le code de confiance petit en déplaçant la politique dans des services explicites en espace utilisateur, contraints par des capacités.