Fonctionnement de fork () dans linux gcc [dupliquer]

cette question a déjà une réponse ici:

fork() crée un nouveau processus et le processus enfant commence à s'exécuter à partir de l'état actuel du processus parent.

C'est ce que je sais sur fork() sous Linux.

donc, en conséquence le code suivant:

int main() {
  printf("Hi");
  fork();
  return 0;
}

ne doit imprimer" Hi " qu'une seule fois comme indiqué ci-dessus.

mais en exécutant ce qui précède dans Linux, compilé avec gcc, il imprime" Hi " deux fois .

est-ce que quelqu'un peut m'expliquer ce qui se passe réellement en utilisant fork() et si j'ai compris le travail de fork() correctement?

25
demandé sur Cœur 2010-08-18 18:39:03

5 réponses

(intégrant une explication tirée d'un commentaire de l'utilisateur @Jack)) Lorsque vous imprimez quelque chose à la sortie Standard stdout (moniteur d'ordinateur habituellement, bien que vous pouvez le rediriger vers un fichier), il est stocké dans la mémoire tampon temporaire au départ.

les deux côtés de la fourche héritent du tampon non déboulonné, donc quand chaque côté de la fourche touche l'instruction de retour et se termine, il est déboulonné deux fois.

avant de vous fourcher, vous devez fflush(stdout); qui sera videz le tampon pour que l'enfant n'hérite pas.

stdout à l'écran (par opposition à quand vous le redirigez vers un fichier) est en fait tamponné par les fins de ligne, donc si vous aviez fait printf("Hi\n"); vous n'auriez pas eu ce problème parce qu'il aurait rincé le tampon lui-même.

36
répondu Paul Tomblin 2010-08-19 11:30:17

printf("Hi"); n'affiche pas immédiatement le mot "salut" sur votre écran. Ce qu'il fait est de remplir le tampon stdout avec le mot "Hi", qui sera alors affiché une fois que le tampon est 'flushed'. Dans ce cas, stdout pointe vers votre moniteur (admettons). Dans ce cas, le tampon sera vidé quand il est plein, quand vous le forcez à tirer la chasse, ou (le plus souvent) quand vous imprimez un caractère newline ("\n"). Puisque le tampon est encore plein quand fork() est appelé, les processus parent et enfant en héritent et par conséquent ils imprimeront "Hi" quand ils videront le tampon. Si vous appelez fflush(stout); avant d'appeler fork, cela devrait fonctionner:

int main() {
  printf("Hi");
  fflush(stdout);
  fork();
  return 0;
}

alternativement, comme je l'ai dit, si vous incluez une nouvelle ligne dans votre printf cela devrait fonctionner aussi bien:

int main() {
  printf("Hi\n");
  fork();
  return 0;
}
22
répondu Stephen 2010-08-18 15:11:40

en général, il est très dangereux d'avoir des poignées / objets ouverts utilisés par les bibliothèques de chaque côté de fork().

comprend la bibliothèque standard C.

fork () fait deux processus sur un, et aucune bibliothèque ne peut le détecter se produire. Par conséquent, si les deux processus continuent à fonctionner avec les mêmes descripteurs de fichiers / sockets, etc, ils ont maintenant des états différents mais partagent les mêmes poignées de fichiers (techniquement, ils ont des copies, mais les mêmes fichiers sous-jacents). Cela fait de mauvaises choses qui arrivent.

Exemples de cas où le fork() sont les causes de ce problème

  • stdio p.ex. entrée/sortie tty, pipes ,disc files
  • Sockets utilisés par exemple par un client de base de données de la bibliothèque
  • Sockets en cours d'utilisation par un processus de serveur - qui peut obtenir des effets étranges quand un enfant de service une socket arrive à hériter d'un dossier pour anohter - obtenir ce genre de programmation la droite est délicate, consultez le code source D'Apache pour des exemples.

Comment résoudre ce problème dans le cas général:

soit

a) immédiatement après fork (), appelez exec (), éventuellement sur le même binaire (avec les paramètres nécessaires pour réaliser le travail que vous aviez l'intention de faire). C'est très facile.

b) après la bifurcation, n'utilisez pas les poignées ouvertes existantes ou les objets de bibliothèque qui en dépendent (l'ouverture de nouvelles poignées est ok); terminez votre travail aussi vite que possible, puis appelez _exit () (pas exit ()). Ne revenez pas du sous-programme qui appelle fork, car cela risque d'appeler des destructeurs C++ etc qui peuvent faire de mauvaises choses aux descripteurs de fichiers du processus parent. C'est assez facile.

c) Après avoir bifurqué, nettoyez d'une manière ou d'une autre tous les objets et faites-les tous dans un état sain avant d'avoir l'enfant continuer. par exemple, fermez les descripteurs de fichiers sous-jacents sans débusquer les données qui sont dans un tampon qui est dupliqué dans le parent. C'est délicat.

c) est approximativement Ce Que fait Apache.

8
répondu MarkR 2010-08-18 21:40:47

printf() n'mise en mémoire tampon. Avez-vous essayé l'impression de stderr ?

3
répondu swegi 2010-08-18 14:44:53

réponse technique:

lorsque vous utilisez fork (), vous devez vous assurer que exit() n'est pas appelé deux fois (tomber de main est la même chose que d'appeler exit()). L'enfant (ou rarement le parent) doit appeler _exit à la place. Et n'utilisez pas stdio chez l'enfant. C'est juste des ennuis.

certaines bibliothèques ont un fflushall () que vous pouvez appeler avant fork () qui rend stdio dans l'enfant en sécurité. Dans ce cas particulier, cela rendrait également exit() sûr mais ce n'est pas vrai dans le cas général.

2
répondu Joshua 2010-08-18 15:18:35