Sous Finder, Safari, Terminal et toute autre application s’exécutant sur un Mac se trouve XNU, le noyau de macOS. Souvent présenté comme une combinaison de Mach et BSD, cela peut conduire à une erreur de compréhension : l’ordinateur ne fait pas fonctionner deux noyaux indépendants en même temps. XNU est un noyau hybride unique qui intègre des composants issus de Mach et BSD dans le même espace d’adresses du noyau, une architecture que Apple maintient depuis les origines de Mac OS X.

Les bases de XNU en 30 secondes

  • macOS utilise un seul noyau hybride nommé XNU, et non deux noyaux fonctionnant simultanément.
  • Mach fournit des mécanismes tels que les tâches, les threads, la mémoire virtuelle et la communication entre processus via IPC.
  • BSD offre une grande partie de l’interface Unix, notamment les processus POSIX, le système de fichiers, les sockets et de nombreuses appels système.
  • Les deux composants fonctionnent dans le même espace de noyau pour éviter le coût d’une architecture microkernel pure.
  • Le code source ouvert de XNU permet d’observer des tables spécifiques pour les traps Mach et les appels Unix.

Cette distinction n’est pas simplement académique. Comprendre l’organisation de XNU aide à interpréter les profils de performance, les mécanismes IPC (Communication Inter-Processus), les services XPC, les problèmes d’ordonnancement, les tracés et certains kernel panics.

Cela permet aussi d’expliquer pourquoi macOS peut se comporter comme un système Unix du point de vue d’un développeur tout en conservant des concepts issus de Mach sous cette interface.

Apple décrit officiellement XNU comme un noyau basé sur l’architecture du microkernel Mach, mais intégrant des fonctionnalités BSD. La société précise toutefois que XNU n’est pas une implémentation pure de microkernel, car une grande partie de ses composants sont intégrés directement dans le noyau pour des raisons de performance.

Mach et BSD cohabitent dans le même noyau

Mach a vu le jour à l’Université Carnegie Mellon dans les années 1980, proposant une architecture fortement basée sur la communication par message.

Des concepts comme tasks, threads, ports et messages restent essentiels dans XNU.

Une task représente, en simplification, un environnement d’exécution avec son espace d’adresses et ses ressources associées. Les threads sont les unités qui exécutent des instructions dans ce contexte.

Les Mach ports servent d’endpoints de communication et constituent une pièce fondamentale du système IPC.

Le code actuel de XNU conserve clairement cette architecture. La table des traps Mach inclut des opérations telles que la gestion de la mémoire virtuelle, des ports, des sémaphores, des tâches, des timers, et la messagerie. Parmi elles, on trouve mach_msg_trap, mach_msg2_trap, thread_self_trap, task_self_trap, semaphore_wait_trap ainsi que diverses opérations sur les Mach ports.

L’évolution du code montre aussi comment XNU continue de se développer.

Apple marque dans son code source mach_msg_trap() comme une interface qui finira par disparaître de macOS, en proposant mach_msg2_trap() comme alternative moderne, supportant notamment les messages vecteurs et l’Integrity du Contrôle de Flux (CFI).

BSD contribue à la partie visible par les développeurs.

Les interfaces Unix relatifs aux processus, descripteurs de fichiers, sockets, signaux, et une grande partie de POSIX proviennent de cette architecture.

Ainsi, une application peut utiliser des appels familiers tels que read(), open() ou close() sans avoir à connaître l’infrastructure Mach présente à d’autres niveaux du système.

Pourquoi Apple ne se limite pas à un microkernel Mach pur

Une implémentation classique de microkernel tente de ne garder dans le noyau que des mécanismes minimaux, en déléguant de nombreux services à des processus séparés en espace utilisateur.

Cette séparation offre des avantages architecturaux, mais introduit aussi des coûts.

Si différents composants doivent communiquer constamment via message à travers des frontières de protection, cela augmente le nombre de changements de contexte et d’opérations IPC.

Apple explique que ce problème de performance a influencé l’architecture de XNU.

Plutôt que d’exécuter BSD comme un serveur séparé sur un Mach minimal, la fonctionnalité BSD a été intégrée au noyau aux côtés de Mach. De cette façon, ils peuvent interagir dans le même espace d’adresses.

Dès lors, qualifier XNU de « Mach avec BSD intégré » constitue une première approche historique, mais reste incomplet pour décrire l’implémentation actuelle.

Il ne s’agit pas d’un noyau Mach et d’un noyau BSD indépendants qui échangent des messages pour chaque opération.

Il existe un seul XNU.

Au sein de XNU, cohabitent des subsystèmes aux origines, abstractions et interfaces différentes.

C’est précisément cette décision qui fait de XNU un noyau hybride.

Que se passe-t-il vraiment lorsqu’une application appelle read()

Voici une autre nuance importante par rapport à certaines explications courantes sur macOS.

Une instruction comme :

read(fd, buffer, size);

ne nécessite pas automatiquement d’être convertie en message Mach pour qu’un serveur BSD hypothétique puisse la traiter.

L’appel entre dans l’interface Unix de XNU, pouvant traverser les composants BSD, le Virtual File System (VFS) et les sous-systèmes du noyau. Mach fournit des infrastructures fondamentales utilisées par l’ensemble du système, mais BSD et Mach ne sont pas séparés par une frontière d’espace utilisateur que chaque syscall doit traverser.

Cette différence est précisément l’un des motifs pour lesquels Apple a fusionné ces architectures.

XNU distingue cependant entre appels Unix et traps Mach.

Le code source contient une table spécifique appelée mach_trap_table. Apple indique qu’elle réserve certains numéros pour les appels Unix, tandis que d’autres correspondent aux opérations Mach.

Parmi ces opérations Mach, on trouve mach_msg2_trap, utilisé pour la communication par messages, en plus d’opérations sur la mémoire, les ports et la synchronisation.

Cela signifie qu’un processus macOS peut dialoguer avec le noyau via différentes familles d’interfaces selon l’opération effectuée.

Mais cela ne veut pas dire que chaque appel système BSD doit forcément se transformer en message Mach ultérieurement.

La communication Mach IPC reste essentielle sur un Mac moderne

Même si un développeur travaille exclusivement avec POSIX pendant des années, les mécanismes de communication hérités de Mach restent très importants dans macOS.

L’exemple le plus évident est le système de message.

mach_msg2_trap() permet d’envoyer et de recevoir des messages via l’infrastructure IPC du noyau. Le code ouvert de XNU montre comment il gère les informations sur les ports locaux et distants, les options d’envoi et réception, la taille des messages et autres paramètres.

Ces primitives ont permis de construire des mécanismes de niveau supérieur.

Un développeur d’applications n’a généralement pas besoin d’appeler directement Mach IPC. Les frameworks et les services du système offrent des abstractions plus conviviales pour la communication inter-processus.

Cette séparation est délibérée.

Une application classique devrait pouvoir manipuler fichiers, sockets, processus et threads sans avoir à connaître les détails internes de Mach. Le fait que macOS soit un système certifié Unix avec des API POSIX permet de conserver une bonne partie de cette portabilité.

Cependant, lorsqu’on descend dans les couches profondes, des termes peu courants sur Linux apparaissent.

mach_msg, ports Mach, tasks, exceptions Mach ou primitives de synchronisation variées peuvent se retrouver dans les traces, outils d’analyse et journaux du système.

Pourquoi macOS et Linux peuvent réagir différemment

Linux et XNU ont résolu nombre de problèmes similaires en adoptant des architectures différentes.

Linux est généralement considéré comme un noyau monolithique modulaire. Ses sous-systèmes de mémoire, processus, réseau, fichiers, pilotes et planification forment une architecture principalement conçue autour du noyau Linux lui-même.

XNU possède une origine différente.

Sa structure combine des concepts issus de Mach, BSD, et d’autres composants développés ultérieurement par Apple. Cette histoire se reflète encore dans ses API et ses structures internes.

Cela ne signifie pas que l’une soit forcément plus rapide ou meilleure que l’autre.

Pour la majorité des logiciels, la différence est volontairement masquée par des API de haut niveau. Un programme en C peut appeler read() aussi bien sous Linux que sous macOS et obtenir une abstraction pratiquement identique.

Les différences se font sentir lorsque l’on travaille avec l’IPC, la détection de performance, la sécurité, la virtualisation, le débogage de bas niveau ou certains composants spécifiques à Darwin.

Elles apparaissent aussi lors de l’analyse du code du noyau lui-même.

Apple continue de publier le code source de XNU, permettant d’explorer directement l’implémentation de ces interfaces plutôt que de se limiter à des diagrammes historiques de Mac OS X.

Les outils de traçage ne montrent pas toujours toute l’histoire

Les outils comme dtruss, basé sur DTrace, sont traditionnellement utiles pour observer les appels effectués par les processus.

Un exemple classique :

sudo dtruss -f cat /etc/hosts

Une trace peut révéler des opérations classiques de tout système Unix : ouverture de fichiers, consultation d’attributs, lecture et fermeture.

Mais une application peut utiliser simultanément des interfaces de niveau inférieur ne correspondant pas exactement à la vision POSIX.

Il est donc utile de connaître les différentes familles d’interfaces de XNU lors de l’analyse de comportements liés à l’IPC ou à d’autres composants système.

Un autre défi pour les Mac modernes concerne les mécanismes de sécurité qui filtrent ou limitent l’utilisation de DTrace. Tous les exemples historiques ne fonctionnent pas forcément sur les versions récentes de macOS.

Il faut donc éviter de supposer qu’une vieille recette avec dtrace permettra d’observer tous les Mach traps sur un système moderne, en tenant compte de la version de macOS, de l’architecture et des paramètres de sécurité.

XNU, une fusion plutôt que deux noyaux concurrents

La façon la plus précise de comprendre l’architecture de macOS consiste à éviter les extrêmes.

Dire simplement que « macOS est Unix » décrit bien sa compatibilité pour l’utilisateur et le développeur, mais cela n’éclaire pas l’architecture interne.

A l’inverse, supposer que « un Mac exécute deux noyaux simultanément » va trop loin.

XNU est un noyau unique et hybride.

Mach apporte certaines abstractions fondamentales pour la mémoire, l’exécution et l’IPC. BSD fournit la majorité de l’interface Unix que rencontrent les applications. Apple a intégré ces deux éléments dans le même espace du noyau pour conserver leurs caractéristiques sans supporter le coût d’un microkernel strictement séparé.

Ce choix architectural est en place depuis plus de vingt ans et reste visible dans le code actuel de XNU.

Pour un développeur qui utilise uniquement open(), read() et des sockets, cela peut rester transparent. Mais pour ceux qui analysent l’IPC, déboguent le système ou examinent des traces en profondeur, savoir où se termine l’interface Unix et où commencent les abstractions Mach est essentiel pour bien interpréter ce que fait réellement le système.

Questions fréquentes

macOS exécute-t-il vraiment deux noyaux en même temps ?

Non. macOS utilise un seul noyau hybride nommé XNU. Il intègre des composants et concepts issus de Mach et BSD dans le même espace d’adresses du noyau.

Que signifie XNU ?

Traditionnellement, cela se traduit par « X is Not Unix » (X n’est pas Unix). Cependant, XNU fournit une grande partie de l’infrastructure sur laquelle macOS offre son environnement Unix.

Que apporte Mach au noyau de macOS ?

Mach fournit des concepts et mécanismes liés aux tâches, threads, mémoire virtuelle, ports, communication IPC et autres primitives internes utilisées par XNU.

Que fournit BSD à XNU ?

BSD offre toute la partie interface Unix utilisée par les applications, notamment les processus POSIX, les signaux, les sockets, le système de fichiers virtuel, et de nombreux appels système.