Le noyau Linux se rapproche d'une façon de créer un processus sans en dupliquer un d'abord, et la forme est devenue visible le 4 août quand LWN a couvert une série de patches de Li Chen. L'idée : ouvrir un processus vide avec pidfd_open(), le configurer morceau par morceau via un nouvel appel pidfd_config(), puis exécuter. C'est l'architecture esquissée par Christian Brauner en juin, et elle existe pour une raison précise : posix_spawn() sous Linux repose encore sur fork ou vfork, et ni l'un ni l'autre n'est le bon outil pour ce travail. Rien n'est intégré. C'est la direction qui compte.
The short answer
Une série de patches de Li Chen, couverte par LWN le 4 août, montre à quoi pourrait ressembler une API de constructeur de processus sous Linux : créer un processus vide, le configurer par appels successifs, puis l'exécuter. Elle suit la suggestion de Christian Brauner d'étendre pidfd_open() et d'ajouter un appel pidfd_config() dans l'esprit de fsconfig(). L'objectif est un posix_spawn() qui n'aurait plus à être bâti sur fork() ou vfork(). Rien n'est intégré, et la surface d'appels système fait encore débat.
Chaque programme que vous avez lancé sous Linux a été créé par un appel système dont le rôle est de copier quelque chose que vous vous apprêtiez à jeter. C'est vrai depuis avant que la plupart d'entre nous écrivent du code, et ça continue de fonctionner, ce qui explique pourquoi l'envie de corriger ça ne devient jamais tout à fait une urgence.
Le 4 août, LWN a publié un article sur les débuts d'une API de constructeur de processus, couvrant une série de patches de Li Chen qui met du code exécutable derrière une conception discutée par intermittence depuis des années. Chen est le développeur derrière la proposition de modèles de spawn de fin mai, et cette série répond directement aux retours reçus.
Ce que fork() fait mal est justement ce pour quoi tout le monde s'en sert
Pour démarrer un processus sous Unix, la séquence canonique est fork() puis exec(). Le fork duplique le processus appelant. Le exec remplace ensuite ce duplicata entièrement.
Le copy on write évite la copie physique des pages, mais le noyau construit quand même des tables de pages pour un espace d'adressage qui va exister pendant quelques microsecondes. La facture croît avec la taille du parent, ce qui produit le résultat que tout ingénieur de build a déjà rencontré : lancer un utilitaire trivial depuis un processus au tas volumineux coûte sensiblement plus cher que depuis un petit, pour des raisons qui n'ont rien à voir avec l'utilitaire.
Il y a aussi le problème de configuration, qui attire moins l'attention et provoque davantage de bugs. Tout ce que vous voulez de différent chez l'enfant (quels descripteurs il conserve, à quoi ressemblent ses gestionnaires de signaux, où se trouve son répertoire courant, quelles limites s'appliquent) doit se produire après le fork et avant le exec, à l'intérieur d'un enfant qui partage son état avec le parent d'une manière facile à rater.
vfork() et clone() avec CLONE_VFORK réduisent le coût de l'espace d'adressage en faisant emprunter la mémoire du parent à l'enfant et en gelant le parent jusqu'au exec. La couverture LWN précédente qualifiait ce motif de piège sérieux. C'est rapide, c'est ce qu'utilisent les vraies implémentations, et personne ne le défend comme une bonne conception.
La première tentative de Chen, et pourquoi elle n'a pas survécu
La proposition de mai prenait un angle différent. Elle ajoutait spawn_template_create(), qui construit un modèle en cache à partir d'un exécutable, et spawn_template_spawn(), qui lance depuis ce modèle avec arguments, environnement et actions sur les descripteurs portés par une structure spawn_template_action. La théorie : relancer plusieurs fois le même binaire ne devrait pas refaire le même travail de préparation.
Le gain mesuré était d'environ deux pour cent. Ce n'est pas rien, mais cela ne justifie pas deux nouveaux appels système et une couche de cache dans le noyau, et les réactions l'ont reflété. Mateusz Guzik a formulé la version la plus tranchante de l'objection : l'effort partait dans la réduction du surcoût de fork plutôt que dans la suppression du besoin de forker. Ce qu'il voulait, c'était la possibilité de créer un processus vierge, pas une copie moins chère d'un processus existant.
Ce que Brauner a proposé, et ce que la nouvelle série démontre
La contre-proposition de Christian Brauner est celle qui a recadré la discussion, et elle réutilise une mécanique que Linux possède déjà plutôt que d'inventer un mécanisme parallèle.
Les pidfd sont déjà la façon moderne de désigner un processus : un descripteur de fichier qui identifie un processus précis sans ambiguïté, à l'abri des courses de réutilisation de pid qui rendaient les anciennes API dangereuses. La suggestion de Brauner est d'étendre pidfd_open() pour qu'il puisse créer un processus vide au lieu de seulement pointer vers un processus existant, puis d'ajouter pidfd_config(), un appel dans l'esprit de fsconfig(), pour décrire ce processus par étapes avant son démarrage.
Les avantages découlent de la forme. La configuration se fait dans le parent, en appels système ordinaires, avec des retours d'erreur ordinaires. Un échec à mi-parcours donne un errno, pas un enfant orphelin. Rien n'est dupliqué, donc le coût ne suit plus la taille de l'appelant. Et comme l'interface est assez expressive pour couvrir les besoins de posix_spawn(), la glibc pourrait implémenter posix_spawn() directement dessus, au lieu du montage actuel où la bibliothèque C fabrique une interface POSIX à partir d'une primitive noyau qui n'a jamais été prévue pour ça.
La nouvelle série de Li Chen est une démonstration de cette idée avec du code. Elle est décrite ouvertement comme largement assistée par LLM, ce qui mérite d'être signalé dans une semaine où l'arbre staging du noyau a annoncé refuser les patches générés par LLM et où GCC a pris une ligne comparable. Déposer une démonstration dans un fil de conception ouvert n'est pas le même geste que soumettre un correctif pour intégration, et c'est reçu comme tel.
Ce qu'il faut en faire aujourd'hui
Rien, et c'est la réponse honnête. Il n'y a pas d'interface à viser, les noms d'appels système peuvent changer, et la série est une pièce de discussion plutôt qu'une candidate à l'intégration.
Ce qui vaut la peine, c'est de savoir sur quelle couche votre propre code repose. Si vous lancez des processus via posix_spawn(), subprocess en Python, std::process::Command en Rust ou ProcessBuilder en Java, vous êtes déjà isolé de ce que le noyau finira par retenir, et vous hériterez de l'amélioration gratuitement le jour où elle arrivera. Si vous avez du fork et exec écrit à la main quelque part dans un chemin critique, ou si vous avez choisi vfork délibérément parce que le coût du fork apparaissait dans un profil, c'est le fil à suivre. Il vous vise exactement.
Les discussions noyau qui atteignent le stade du code exécutable mettent en général un an ou plus à atteindre un noyau publié, quand elles y arrivent. Celle-ci a un problème clair, une approche sur laquelle deux mainteneurs respectés semblent d'accord, et désormais une série de patches assez concrète pour être discutée. C'est plus loin que cette idée n'était jamais allée.
Sources et pour aller plus loin
- The beginning of a process-builder API, LWN.net, 4 août 2026 (copie syndiquée)
- Moving beyond fork() + exec(), LWN.net
- Discussion des lecteurs sur la proposition, LWN.net
- Race-free process creation in the GNU C Library, LWN.net
- Page de manuel posix_spawn(3)
- pidfd_open(), LWN.net
Questions fréquentes
Qu'y a-t-il réellement de gênant avec fork() suivi de exec() ?
Rien, si on se place en 1975. Le problème est que fork() promet un duplicata complet du processus appelant, et que exec() jette ce duplicata immédiatement. Les noyaux modernes évitent la copie mémoire littérale grâce au copy on write, mais les tables de pages doivent quand même être construites, l'espace d'adressage parcouru, et le coût continue de croître avec la taille du parent. Un serveur de compilation avec douze gigaoctets résidents paie plus cher pour lancer /bin/true qu'un script shell. À cela s'ajoute que tout ce que vous voulez configurer dans l'enfant (descripteurs de fichiers, dispositions de signaux, répertoire courant, limites de ressources) doit se faire dans la fenêtre entre le fork et le exec, à l'intérieur d'un enfant qui tourne avec une copie de l'état du parent et très peu d'opérations réellement sûres.
vfork() ne règle-t-il pas déjà le problème de coût ?
Il règle le coût et en crée un autre. vfork() et clone() avec CLONE_VFORK évitent de dupliquer l'espace d'adressage, ce qui est exactement l'économie recherchée, mais ils le font en faisant partager la mémoire du parent à l'enfant et en suspendant le parent jusqu'au exec ou à la sortie. La couverture LWN de la discussion précédente qualifiait ce motif de piège sérieux, et le résumé est juste : la moindre erreur dans l'enfant corrompt le parent, l'ensemble des opérations réellement légales dans cette fenêtre est étroit et mal documenté, et le parent reste bloqué pendant toute la durée. Les auteurs de bibliothèques l'utilisent parce que l'alternative est plus lente, pas parce que c'est confortable.
À quoi ressemble concrètement l'approche constructeur ?
La version décrite par Christian Brauner fonctionne comme fsconfig() pour monter un système de fichiers. Vous obtenez une poignée sur un processus qui existe mais n'a pas démarré, puis vous enchaînez des appels sur cette poignée pour décrire à quoi le processus doit ressembler, puis vous validez. Chaque étape de configuration est un appel système normal, effectué par le parent, dans le contexte du parent, avec une gestion d'erreur normale. Si une étape échoue, vous recevez un code d'erreur au lieu d'un enfant à moitié construit qu'il faut maintenant récupérer. La série de Li Chen est une démonstration de cette forme plutôt qu'une interface finie, et la surface d'appels système reste discutable.
Est-ce que cela va changer quelque chose dans mon code ?
Pas directement, et probablement pas avant des années. Presque plus personne n'appelle fork() et exec() à la main : vous appelez posix_spawn(), ou subprocess en Python, ou Command en Rust, ou ProcessBuilder en Java, et le runtime choisit un mécanisme en dessous. C'est précisément pour cela que ce travail compte. Si la glibc peut implémenter posix_spawn() sur une interface noyau conçue pour ça au lieu de l'assembler à partir de vfork et d'une chorégraphie prudente, alors chacune de ces API de plus haut niveau devient plus rapide et moins fragile sans qu'une seule ligne change dans votre programme. Les premiers à le sentir seront ceux qui font tourner des charges riches en processus : systèmes de build, exécuteurs CI, shells, harnais de tests.
La série a été écrite avec l'aide d'un LLM. Est-ce que cela compte ici ?
Cela mérite d'être signalé, parce que la communauté noyau vient de passer une semaine à tracer des lignes exactement sur cette question. Greg Kroah-Hartman a annoncé que l'arbre staging refuserait les patches générés par LLM, et GCC a pris une position comparable. Une série de démonstration décrite ouvertement comme largement assistée par LLM, déposée dans une discussion de conception plutôt que soumise pour intégration, relève d'une catégorie différente d'un correctif non sollicité déposé chez un mainteneur. L'intérêt de cette publication précise est de rendre un débat d'API abstrait assez concret pour être critiqué. C'est un usage légitime, et il est lu comme tel.