J'ai pu trouver le code source de mon système. xargs
en courant strings $(which xargs)
et à la recherche de mots-clés intéressants. PROJECT:shell_cmds-207.40.1
m'a interpellé et j'ai rapidement trouvé le code source d'une version légèrement plus ancienne. shell_cmds-203
sur Le site Open Source d'Apple .
J'ai compilé la version de xargs
dans ce paquet avec gcc -g *.c
, a couru echo | ./a.out > >(cat)
et j'ai attaché mon débogueur lldb
à la a.out
processus. J'ai découvert qu'il était coincé dans un appel à waitpid
de xargs.c:610
( source ). Extrait :
while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
WNOHANG : 0)) > 0) {
Parce que xargs
est un programme compliqué, je voulais faire un petit programme en C qui reproduirait le comportement. Le voici :
// tiny.c
#include <sys/wait.h>
int main() {
int status;
waitpid(-1, &status, 0);
return 0;
}
En compilant cela avec gcc tiny.c -o tiny
et en cours d'exécution echo | ./tiny > >(cat)
accroché comme xargs
. En effet, je pourrais maintenant simplifier encore plus, ./tiny > >(cat)
seraient suspendus, tandis que ( ./tiny; : ) > >(cat)
ne s'accrocherait pas.
En outre, ce petit programme peut être compilé sur Linux et vous pouvez alors reproduire ce comportement sur Linux facilement.
Passing -1
a waitpid
le fera attendre sur tout processus enfant . Cela pose donc la question : pourquoi tiny
ont un processus enfant dans ./tiny > >(cat)
mais pas ( ./tiny; : ) > >(cat)
?
Je n'ai pas plongé dans bash
Je ne connais pas le code source de l'entreprise, mais j'ai une idée assez précise de ce qui se passe.
D'abord, disséquons la première commande : ./tiny > >(cat)
. Premier bash
crée un tuyau nommé, et ensuite fork()-exec()
s cat
dans la création en tant que processus enfant. Ensuite, il définit ses propres stdout
pour être ce même tuyau nommé. Enfin, bash
termine sa vie en appelant exec()
pour se transformer en tiny
. Maintenant tiny
a le même PID et le système d'exploitation considère toujours la cat
pour être son enfant.
Il est important de noter que la même chose se produit avec ( ./tiny ) > >(cat)
mais c'est juste exec()
s dans bash (les parenthèses démarrent un sous-shell) et ensuite dans tiny
. Un fait essentiel semble être que lorsque bash
est démarré avec une seule commande à exécuter, il n'est pas fork()-exec()
mais plutôt exec()
immédiatement.
Maintenant, disséquons la deuxième commande : ( ./tiny; : ) > >(cat)
. On a la même chose au début : fork()-exec()
ing cat
à l'existence. Puis bash
exec()
dans une nouvelle bash
instance. Puis il voit qu'il a deux commandes à exécuter, donc il fork()-exec()
s tiny
à l'existence, et parce qu'elle a bifurqué, cette nouvelle tiny
Le processus n'a pas cat
comme un enfant pour qu'il ne soit pas suspendu. Puis bash
exécute :
( :
est un intégré spécial, donc il n'y a pas d'exécution ici, mais l'utilisation d'un non intégré causerait toujours tiny
pour être bifurqué afin qu'il n'y ait toujours pas d'accrochage).