Mach traps vs syscalls BSD : deux noyaux dans un seul Mac
XNU mélange un micro-noyau Mach 3 et une personnalité BSD : à quoi sert chaque famille de syscalls et comment elles diffèrent en pratique.
La chose la plus déroutante à propos de XNU, le noyau de macOS, c'est qu'il s'agit de deux noyaux. En dessous, c'est un descendant de Mach 3 — un micro-noyau conçu à Carnegie Mellon à la fin des années 1980. Par-dessus, Apple a maintenu vivante toute la personnalité BSD : la surface POSIX, le VFS, le scheduler, le modèle de signaux. Le résultat est l'un des noyaux les plus étranges encore en production : un cœur Mach qui se parle à lui-même presque exclusivement par passage de messages, avec un noyau Unix coopérant dans le même espace d'adressage.
Cette hybridation explique pourquoi macOS expose deux familles distinctes de syscalls, acheminées par la même instruction SVC mais vivant dans des tables différentes.
Ce qui vit où
La séparation n'est pas arbitraire. Il existe des règles claires :
| Domaine | Famille | Pourquoi |
|---|---|---|
| Fichiers, sockets, signaux | BSD | Héritage direct de 4.4BSD ; POSIX les attend. |
Gestion mémoire (mmap) | BSD | Enrobe les primitives Mach VM sous-jacentes. |
Modèle de processus (fork) | BSD | API process POSIX au-dessus des tasks Mach. |
| Tasks, threads, ports | Mach | Objets Mach de première classe, propriété du micro-noyau. |
| IPC (tout type, partout) | Mach | Le message Mach est la seule primitive de communication cross-task. |
| Primitives mémoire virtuelle | Mach | mach_vm_allocate, mach_vm_protect, etc. portent les appels BSD VM. |
| Synchronisation côté noyau | Mach | semaphore_*, mk_timer_*, attribution de voucher. |
Autrement dit : ce que POSIX veut, BSD le possède. Ce qui touche à la comptabilité du micro-noyau — tasks, threads, ports, memory objects — c'est un Mach trap.
Comment le dispatch diffère
Quand le SVC se déclenche sur arm64, XNU regarde le signe de x16 :
- Positif → chemin BSD.
unix_syscall64()indexesysent[N], copie les arguments, appelle le handler, positionne le carry dans PSTATE en cas d'échec. Sémantique errno. - Négatif → chemin Mach.
mach_call_munger64()indexemach_trap_table[-N], copie les arguments, appelle le handler, retourne unkern_return_tdirectement. Pas d'errno.
C'est aussi pourquoi tout Mach trap sur ce site a un numéro négatif (mach_msg2_trap est -31, task_for_pid est -45) et tout syscall BSD un numéro positif.
Sur x86_64, l'encodage est différent — (class << 24) | numéro packed dans EAX — mais la séparation conceptuelle est la même.
Mach est la seule IPC
C'est la conséquence la plus importante de la séparation : sur macOS, toute communication inter-processus passe par Mach. Il n'y a pas d'équivalent du sendmsg Linux avec SCM_RIGHTS. Pas de pidfd_send_signal. Si A veut parler à B, A envoie un message Mach à un port que B possède. Point.
Cela a d'énormes conséquences :
- XPC, le framework IPC moderne de macOS, est construit sur les messages Mach.
- launchd, le système d'init, est un broker de ports Mach — les services enregistrent des noms, les clients les cherchent, launchd distribue des send rights.
- L'injection de code passe toujours par Mach :
task_for_piddonne un send right sur le port task de la cible, etmach_vm_write/thread_create_runningfont le reste. - Le trafic vers WindowServer (chaque dessin CGContext, chaque livraison d'événement) est un message Mach sous le capot.
Si vous vous êtes déjà demandé pourquoi un outil d'injection de processus sur Linux fait quelques centaines de lignes de ptrace et sur macOS quelques dizaines de lignes de mach_*, c'est pour ça. Mach rend l'opération conceptuellement plus propre — et, quand SIP et AMFI ne s'en mêlent pas, mécaniquement plus simple.
La personnalité BSD
Le côté BSD de XNU existe essentiellement pour garder portable le logiciel POSIX. Quand vous appelez open(), voilà ce qui se passe :
- Le handler BSD
sys_opens'exécute. - Il résout le chemin via le VFS.
- Il alloue une paire
fileproc+fileglobdans la table des fd BSD. - En interne, il demande à Mach toute la mémoire nécessaire via
kmem_allocet compagnie.
Idem pour mmap. Le handler BSD est un mince enrobage de mach_vm_map. Si vous regardez le graphe d'appels dans DTrace, vous verrez la plupart des syscalls BSD finir dans Mach VM ou Mach IPC.
Quand utiliser quoi
Pour du code applicatif, vous voulez presque toujours la surface BSD. POSIX est portable, bien documenté, prédictible.
Vous descendez dans Mach quand l'API BSD ne suffit pas :
- Monter une connexion XPC sans launchd. Vous jonglerez avec des ports Mach.
- Inspecter la mémoire d'un autre processus.
task_for_pid+mach_vm_read. - Implémenter une primitive de scheduling très précise.
semaphore_wait_trapest plus léger que les sémaphores POSIX. - Atteindre les user clients IOKit.
iokit_user_client_trapest le dispatch sous-jacent.
Pour les extensions noyau et DriverKit, Mach est la norme — objets IOKit, descripteurs de mémoire, primitives de synchronisation sont tous nativement Mach.
Pour aller plus loin
- Le post précédent, Comment fonctionnent les appels système macOS en 2026, parcourt le chemin de trap de bout en bout.
task_for_pid : le Mach trap le plus dangereux de macOStraite du point d'entrée critique pour la sécurité.- Le catalogue de référence est interrogeable et filtrable par famille — réglez le filtre sur « Mach » pour ne voir que les traps.