Pourquoi fork () aurait-il été conçu pour renvoyer un descripteur de fichier?

16

Sur sa page Web sur l' astuce du self-pipe , Dan Bernstein explique une condition de course avec select()et signale, propose une solution de contournement et conclut que

Bien sûr, la bonne chose serait d'avoir fork()renvoyé un descripteur de fichier, pas un ID de processus.

Que veut-il dire par cela - s'agit-il de pouvoir select()sur des processus enfants gérer leurs changements d'état au lieu d'avoir à utiliser un gestionnaire de signaux pour être notifié de ces changements d'état?

Lassi
la source
Cet article est-il mélangé entre les entrées et les sorties, ou ne suis-je pas en train de le lire correctement?
ctrl-alt-delor
Vous pouvez demander que les signaux soient délivrés par les tuyaux. Voilà ce que je fais.
ctrl-alt-delor
@ ctrl-alt-delor, oui, il semble utiliser "l'entrée / sortie du tuyau" un peu bizarrement, mais je pense que c'est clair où il écrit et où lire dans un tuyau. Ce texte date de 2003, et je ne suis pas sûr signalfdque ce soit une chose à l'époque?
ilkkachu
5
Dan sait de quoi il parle, même s'il peut être un peu délibérément provocateur. Si j'étais délibérément provocateur, je dirais que bien sûr, la bonne chose serait de se débarrasser de SIGCHLD.
Steve Summit
1
@mosvy J'exagère légèrement, mais chaque programme et chaque programmeur que j'ai jamais vu qui a essayé d'utiliser SIGCHLD a eu des problèmes avec. C'est une condition de course qui attend de se produire. À l'époque où tout ce que nous avions était de bloquer wait(), il y avait des choses que vous ne pouviez pas faire, alors quelqu'un a inventé SIGCHLD, mais c'était un mauvais travail. Dans mon expérience, et maintenant qu'ils existent, aspergeant agréable, non bloquante wait3(), wait4()et / ou des waitpid()appels à des endroits clés (peut - être votre boucle d'événement) est une alternative nettement supérieure.
Steve Summit

Réponses:

14

Le problème est décrit ici dans votre source, select()devrait être interrompu par des signaux comme SIGCHLD, mais dans certains cas, cela ne fonctionne pas bien. La solution de contournement consiste donc à écrire le signal sur un canal, qui est ensuite surveillé par select(). Regarder les descripteurs de fichiers est à quoi cela select()sert, donc cela contourne le problème.

La solution de contournement transforme essentiellement l'événement de signal en un événement de descripteur de fichier. Si fork()juste retourné un fd en premier lieu, la solution de contournement ne serait pas nécessaire, car ce fd pourrait alors vraisemblablement être utilisé directement avec select().

Alors oui, votre description dans le dernier paragraphe me semble juste.


Une autre raison pour laquelle un fd (ou un autre type de descripteur de noyau) serait préférable à un numéro d'identification de processus simple, est que les PID peuvent être réutilisés après la fin du processus. Cela peut être un problème dans certains cas lors de l'envoi de signaux aux processus, il peut ne pas être possible de savoir avec certitude que le processus est celui que vous pensez être, et pas un autre réutilisant le même PID. (Bien que je pense que cela ne devrait pas être un problème lors de l'envoi de signaux à un processus enfant, car le parent doit s'exécuter wait()sur l'enfant pour que son PID soit publié.)

ilkkachu
la source
Cela dit, je ne me souviens pas exactement des cas que j'ai lus sur la réutilisation des PID étant un problème, donc si quelqu'un veut développer ou clarifier cela, ou même modifier ce qui précède, n'hésitez pas à le faire.
ilkkachu
2
Comme vous l'avez déjà dit, il n'y a aucune excuse pour qu'un parent trouve que son propre enfant pid a été réutilisé. C'est elle qui contrôle parfaitement cette situation car c'est elle qui appelle wait().
Joshua
Ceux-ci sont appelés processus zombies : "un processus qui a terminé son exécution mais qui a toujours une entrée dans la table de processus: c'est un processus à" l'état Terminé ". Cela se produit pour les processus enfants, où l'entrée est toujours nécessaire pour autoriser le parent processus pour lire le statut de sortie de son enfant "
Lassi
6
Il convient de mentionner que Linux peut désormais renvoyer un descripteur de fichier (pidfd) clone, qui est l'appel système réel que fork appelle sur LInux. Le drapeau pour l'activer est appelé CLONE_PIDFD- Voir par exemple lwn.net/Articles/784831 .
KJ Tsanaktsidis
1
@Lie Ryan, Re "Le fait que fork () renvoie un descripteur global plutôt qu'un descripteur local est conceptuellement plus correct car ", Windows utilise des descripteurs de processus. Le code pid et exit restent jusqu'à ce que toutes les poignées du processus soient fermées (plutôt que d'attendre que le parent récolte), évitant les conditions de concurrence courantes dans les systèmes Unix. Lorsque les poignées maintiennent le processus en vie, il est beaucoup plus logique qu'elles soient des poignées locales plutôt que globales.
ikegami
9

C'est juste une réflexion sur les lignes de «ce serait génial si Unix était conçu différemment».

Le problème avec les PID est qu'ils vivent dans un espace de noms global où ils pourraient être réutilisés pour un autre processus, et ce serait bien s'ils étaient fork()retournés au parent une sorte de handle qui serait garanti de toujours se référer au processus enfant, et qu'il pourrait passer à d'autres processus via l'héritage ou les sockets unix / SCM_RIGHTS[1].

Voir également la discussion ici pour un effort récent pour "corriger" cela sous Linux, y compris l'ajout d'un indicateur clone()auquel il retournera un pid-fd au lieu d'un PID.

Mais même dans ce cas, cela n'éliminerait pas la nécessité de ce hack auto-pipe [2] ou de meilleures interfaces, car les signaux notifiant un processus parent de l'état d'un enfant ne sont pas les seuls que vous souhaitez gérer dans la boucle principale. du programme. Malheureusement, des choses comme epoll(7) + signalfd(2)Linux ou kqueue(2)BSD ne sont pas standard - la seule interface standard (mais non prise en charge sur les anciens systèmes) est la bien inférieure pselect(2).

[1] Empêcher le PID d'être recyclé au moment où l' waitpid()appel système est revenu et sa valeur de retour a été utilisée pourrait probablement être atteint sur les systèmes plus récents en utilisant à la waitid(.., WNOWAIT)place.

[2] Je ne commenterais pas l'affirmation de DJ Bernstein qu'il l'a inventé (désolé pour l'apophasie ;-)).

mosvy
la source
8

Bernstein ne donne pas beaucoup de contexte pour cette remarque "Right Thing", mais je vais hasarder une supposition: avoir fork (2) retourner un PID est incompatible avec open (2), creat (2) etc renvoyant des descripteurs de fichier. Le reste du système Unix aurait pu effectuer une manipulation de processus avec un descripteur de fichier représentant un processus, au lieu d'un PID. Il existe un appel système signalfd (2) , qui permet une interaction un peu meilleure entre les signaux et les descripteurs de fichier, et montre qu'un descripteur de fichier représentant un processus pourrait fonctionner.

Bruce Ediger
la source
signalfd (2) a l'air génial, merci de l'avoir mentionné! Dommage que ce soit uniquement Linux.
Lassi
1
Il y a également eu des discussions sur pidfd_openLinux, voir par exemple lwn.net/Articles/789023
dhag