GNU/Linux >> Tutoriels Linux >  >> Linux

Que se passe-t-il lorsqu'un thread bifurque ?

Le nouveau processus sera l'enfant du thread principal qui a créé le thread. Je pense.

fork crée un nouveau processus. Le parent d'un processus est un autre processus, pas un thread. Ainsi, le parent du nouveau processus est l'ancien processus.

Notez que le processus enfant n'aura qu'un seul thread car fork ne duplique que la (pile pour le) thread qui appelle fork . (Ce n'est pas tout à fait vrai :toute la mémoire est dupliquée, mais le processus enfant n'aura qu'un seul thread actif.)

Si son parent se termine en premier, le nouveau processus sera attaché au processus init.

Si le parent termine premier un SIGHUP signal est envoyé à l'enfant. Si l'enfant ne sort pas suite au SIGHUP il obtiendra init comme son nouveau parent. Voir aussi les pages de manuel pour nohup et signal(7) pour un peu plus d'informations sur SIGHUP .

Et son parent est le thread principal, pas le thread qui l'a créé.

Le parent d'un processus est un processus, pas un thread spécifique, il n'est donc pas significatif de dire que le thread principal ou enfant est le parent. L'ensemble du processus est le parent.

Une dernière note :le mélange des fils et de la fourchette doit être fait avec soin. Certains des pièges sont discutés ici.


Corrigez-moi si je me trompe.

Va faire :)

Comme fork() est un appel système POSIX, son comportement est bien défini :

Un processus doit être créé avec un seul thread . Si un processus multithread appelle fork(), le nouveau processus doit contenir une réplique du thread appelant et tout son espace d'adressage, y compris éventuellement les états des mutex et d'autres ressources. Par conséquent, pour éviter les erreurs, le processus enfant ne peut exécuter des opérations sécurisées pour le signal asynchrone que jusqu'à ce qu'une des fonctions exec soit appelée.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html

Un enfant forké est une copie exacte de son parent, mais seulement le thread qui a appelé fork() dans le parent, existe toujours dans l'enfant et est le nouveau thread principal de cet enfant jusqu'à ce que vous appeliez exec() .

La description POSIX "doit être créé avec un seul thread" est trompeuse car en fait la plupart des implémentations créeront vraiment une copie exacte du processus parent, donc tous les autres threads et leur mémoire sont également dupliqués, ce qui signifie que les threads sont en fait là , ils ne peuvent tout simplement plus fonctionner car le système ne leur attribue jamais de temps CPU ; ils manquent en fait dans la table du planificateur de threads du noyau.

Une image mentale plus simple est la suivante :

Lorsque le parent appelle fork, l'ensemble du processus est gelé pendant un moment, dupliqué de manière atomique, puis le parent est dégelé dans son ensemble, mais dans l'enfant, seul le thread qui a appelé fork est dégelé, tout le reste reste gelé.

C'est pourquoi il n'est pas sûr d'effectuer certains appels système entre fork() et exec() comme le souligne également la norme POSIX. Idéalement, vous ne devriez pas faire beaucoup plus que peut-être fermer ou dupliquer des descripteurs de fichiers, définir ou restaurer des gestionnaires de signaux, puis appeler exec() .


Cependant, que se passera-t-il si un thread crée un nouveau processus en utilisant fork() ?

Un nouveau processus sera créé en copiant le thread appelant l'espace d'adressage (pas l'intégralité de l'espace d'adressage du processus ). C'est généralement considéré comme une mauvaise idée parce qu'il est très difficile de bien faire les choses. POSIX indique que le processus enfant (créé dans un programme multithread) ne peut appeler que des fonctions async-signal-safe jusqu'à ce qu'il appelle l'un des exec* fonctions.

Si son parent se termine en premier, le nouveau processus sera attaché à initprocess.

Le processus enfant est généralement hérité par le processus init. Si le processus parent est un processus de contrôle (par exemple, shell), alors POSIX nécessite :

Si le processus est un processus de contrôle, le signal SIGHUP doit être envoyé à chaque processus du groupe de processus de premier plan du terminal de contrôle appartenant au processus appelant.

Cependant, ce n'est pas vrai pour la plupart des processus car la plupart des processus ne contrôlent pas les processus.

Et son parent est le thread principal, pas le thread qui l'a créé.

Le parent de l'enfant forké sera toujours le processus qui a appelé fork(). Ainsi, PPID est le processus enfant sera le PID de votre programme.


problème vient du comportement de fork(2) lui-même. Chaque fois qu'un nouveau processus enfant est créé avec fork(2), le nouveau processus obtient un nouvel espace d'adressage mémoire mais tout en mémoire est copié à partir de l'ancien processus (avec la copie sur écriture, ce n'est pas vrai à 100 %, mais la sémantique est la même).

Si nous appelons fork(2) dans un environnement multi-thread, le thread effectuant l'appel est maintenant le thread principal du nouveau processus et tous les autres threads, qui s'exécutaient dans le processus parent, sont morts. Et tout ce qu'ils ont fait est resté exactement tel qu'il était juste avant l'appel à fork(2).

Imaginez maintenant que ces autres threads faisaient leur travail avec bonheur avant l'appel à fork(2) et que quelques millisecondes plus tard, ils étaient morts. Et si quelque chose que ces discussions maintenant mortes faisaient n'était pas destiné à être laissé tel quel ?

Laisse moi te donner un exemple. Disons que notre thread principal (celui qui va appeler fork(2)) dormait alors que nous avions beaucoup d'autres threads qui travaillaient joyeusement. Allouer de la mémoire, y écrire, en copier, écrire dans des fichiers, écrire dans une base de données, etc. Ils allouaient probablement de la mémoire avec quelque chose comme malloc (3). Eh bien, il s'avère que malloc (3) utilise un mutex en interne pour garantir la sécurité du filetage. Et c'est exactement le problème.

Et si l'un de ces threads utilisait malloc(3) et avait acquis le verrou du mutex exactement au même moment où le thread principal a appelé fork(2) ? Dans le nouveau processus enfant, le verrou est toujours détenu - par un thread maintenant mort, qui ne le renverra jamais.

Le nouveau processus enfant n'aura aucune idée s'il est sûr d'utiliser malloc(3) ou non. Dans le pire des cas, il appellera malloc(3) et bloquera jusqu'à ce qu'il acquière le verrou, ce qui n'arrivera jamais, car le thread censé le renvoyer est mort. Et c'est juste malloc(3). Pensez à tous les autres mutex et verrous possibles dans les pilotes de base de données, les bibliothèques de gestion de fichiers, les bibliothèques de mise en réseau, etc.

pour une explication complète, vous pouvez passer par ce lien.


Linux
  1. Que se passe-t-il exactement lorsque j'exécute un fichier dans le shell ?

  2. Que fait un programme lorsqu'il envoie un signal Sigkill ?

  3. Qu'est-ce qu'un dispositif de boucle lors du montage ?

  4. Lorsqu'un processus bifurque, sa mémoire virtuelle ou résidente est-elle copiée ?

  5. Que se passe-t-il lors de l'envoi de SIGKILL à un processus zombie sous Linux ?

Que se passe-t-il lorsque j'exécute la commande Cat /proc/cpuinfo ?

Que se passe-t-il lorsque la limite de bande passante est dépassée ?

Qu'est-ce qu'un processus ininterrompu ?

Quel processus utilise toutes mes E/S de disque

Que se passe-t-il si mv est interrompu ?

Qu'est-ce qu'un processus arrêté sous Linux ?