49 votes

Comment fork() savoir quand return 0?

Prenons l'exemple suivant:

int main(void)
{
     pid_t  pid;

     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}

Donc corrigez-moi si je me trompe, une fois fork() exécute un processus enfant est créé. Maintenant, par cette réponse fork() retourne deux fois. C'est une fois que le processus père et une fois pour le processus de l'enfant.

Ce qui signifie que les deux processus distincts entrent en existence PENDANT la fourche d'appel et non après sa fin.

Maintenant, je ne comprends pas comment il comprend la façon de retourner la valeur 0 pour le processus de l'enfant et de la bonne PID du processus parent.

Ce là que ça devient vraiment confus. Cette réponse des états que fork() fonctionne en copiant les informations de contexte du processus et réglage manuel de la valeur de retour de 0.

Premier suis-je en droit de dire que le retour d'une fonction est placée dans un registre unique? Depuis le tout dans un seul processeur de l'environnement, un processus peut appeler uniquement une fonction qui ne retourne qu'une seule valeur (corrigez-moi si je me trompe ici).

Disons que j'appelle une fonction foo() à l'intérieur d'une routine et que la fonction renvoie une valeur, cette valeur sera stockée dans un registre dire de la BARRE. Chaque fois qu'une fonction, on veut retourner une valeur, il utilise un processeur particulier s'inscrire. Donc, si je suis en mesure de modifier manuellement la valeur de retour dans le processus de bloc, je suis en mesure de changer la valeur renvoyée de la fonction à droite?

Donc, ai-je raison de penser que est comment fork() fonctionne?

54voto

paxdiablo Points 341644

Comment il fonctionne, c'est largement hors de propos - en tant que développeur de travail à un certain niveau (c'est à dire, le codage de l'Api UNIX), vous avez vraiment besoin de savoir qui il travaille.

Cela dit cependant, et en reconnaissant que la curiosité ou le besoin de comprendre en profondeur est généralement une bonne qualité à avoir, il ya un certain nombre de façons que cela peut être fait.

Tout d'abord, votre affirmation selon laquelle une fonction ne peut retourner qu'une seule valeur est correcte aussi loin qu'il s'en va, mais vous devez vous rappeler que, après le processus de scission, il y a en fait deux instances de la fonction en cours d'exécution, un dans chaque processus. Ils sont le plus souvent indépendants les uns des autres et peuvent suivre différents chemins de code. Le diagramme suivant peut aider dans la compréhension de ce:

Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork     |
               | comes into existence
returns 271828 | returns 0

Vous pouvez espérer y voir qu'une seule instance d' fork ne peut retourner qu'une seule valeur (comme toute autre fonction C) mais il y a en fait plusieurs instances en cours d'exécution, qui est pourquoi il est dit de retourner plusieurs valeurs dans la documentation.


Voici une possibilité sur la façon dont il pourrait travailler.

Lorsque l' fork() fonction commence à courir, il stocke le courant ID de processus (PID).

Puis, quand vient le temps de retour, si le PID est le même que celui stocké, c'est le parent. Sinon, c'est l'enfant. Le Pseudo-code suivant:

def fork():
    saved_pid = getpid()

    # Magic here, returns PID of other process or -1 on failure.

    other_pid = split_proc_into_two();

    if other_pid == -1:        # fork failed -> return -1
        return -1

    if saved_pid == getpid():  # pid same, parent -> return child PID
        return other_pid

    return 0                   # pid changed, child, return zero

Notez qu'il y a beaucoup de magie dans l' split_proc_into_two() appel et il est presque certainement ne fonctionne pas de cette façon à tous sous les couvertures(un). C'est juste pour illustrer les concepts autour de lui, qui est en gros:

  • d'obtenir l'original PID avant la scission, qui restera la même pour les deux procédés, après leur séparation.
  • faire la distinction.
  • obtenir le PID après la scission, ce qui sera différent dans les deux processus.

Vous pouvez également prendre un coup d'oeil à cette réponse, il explique l' fork/exec philosophie.


(a) Il est presque certainement plus complexe que ce que j'ai expliqué. Par exemple, dans MINIX, l'appel à l' fork finit par courir dans le noyau, qui a accès à l'ensemble de l'arborescence des processus.

Il copie simplement le processus parent structure dans un slot libre pour l'enfant, le long des lignes de:

sptr = (char *) proc_addr (k1); // parent pointer
chld = (char *) proc_addr (k2); // child pointer
dptr = chld;
bytes = sizeof (struct proc);   // bytes to copy
while (bytes--)                 // copy the structure
    *dptr++ = *sptr++;

Alors il fait de légères modifications à l'enfant de la structure pour s'assurer qu'il sera approprié, y compris la ligne:

chld->p_reg[RET_REG] = 0;       // make sure child receives zero

Donc, fondamentalement identique au régime j'ai postulé, mais à l'aide de modifications de données plutôt que le code de sélection de chemin de décider ce qu'il doit retourner à l'appelant - en d'autres termes, vous devriez voir quelque chose comme:

return rpc->p_reg[RET_REG];

à la fin de l' fork() , de sorte que la valeur correcte revient selon qu'il est le parent ou l'enfant de processus.

29voto

Antti Haapala Points 11542

Sous Linux, fork() qui se passe dans le noyau; le lieu réel est l' _do_fork ici. Simplifié, l' fork() appel système pourrait être quelque chose comme

pid_t sys_fork() {
    pid_t child = create_child_copy();
    wait_for_child_to_start();
    return child;
}

Ainsi, dans le noyau, fork() vraiment renvoie une fois, dans le processus parent. Cependant le noyau crée également le processus de l'enfant comme une copie du processus parent; mais au lieu de retourner à partir d'une fonction ordinaire, il serait synthétiquement créer une nouvelle pile de noyau pour le nouveau thread de l'enfant; et puis le contexte-commutateur à ce thread (et processus); que le tout nouveau processus de renvoie de la commutation de contexte de fonction, il serait de rendre le processus de l'enfant " fil finit par revenir en mode utilisateur avec 0 comme valeur de retour d' fork().


Fondamentalement, fork() en userland est juste un wrapper mince renvoie la valeur que le noyau mis sur son stack/en retour vous inscrire. Le noyau met en place le nouveau processus enfant afin qu'elle retourne 0 par le biais de ce mécanisme à partir de son seul thread; et l'enfant pid est retourné dans le parent de l'appel système comme toute autre valeur de retour à partir de tout système d'appel comme read(2) serait.

10voto

Vous avez d'abord besoin de savoir comment multitâche fonctionne. Il n'est pas utile pour comprendre tous les détails, mais à chaque processus s'exécute dans une machine virtuelle contrôlée par le noyau: un processus a sa propre mémoire, du processeur et des registres, etc. Il y a la cartographie de ces objets virtuels sur le réel (la magie est dans le noyau), et il existe des machines qui swap virtuel contextes (processus) pour machine physique comme le temps passe.

Puis, lorsque le noyau fourches d'un processus (fork() est une entrée dans le noyau), et crée une copie de presque tout dans le parent processus à l' enfant de processus, il est capable de modifier tout ce qui est nécessaire. L'un de ces est la modification des structures correspondantes à retourner la valeur 0 pour l'enfant et le pid de l'enfant dans le parent de l'actuel appel à la fourchette.

Remarque: néant dire "de la fourchette des rendements deux fois", un appel de fonction retourne qu'une seule fois.

Il suffit de penser d'un clonage de machine: vous entrez seul, mais deux personnes à la sortie, on est en vous et l'autre est votre clone (très légèrement différente); alors que le clonage, la machine est en mesure de définir un nom différent de la vôtre pour le clone.

8voto

Art Points 6040

La fourche de l'appel système crée un nouveau processus et des copies de beaucoup de l'état du processus parent. Des choses comme la table de descripteurs de fichier est copié, les mappages de mémoire et de leur contenu, etc. Cet état est à l'intérieur du noyau.

Une des choses que le noyau conserve la trace de tous les processus sont les valeurs des registres de ce processus doit avoir rétabli au retour d'un appel système, piège, d'interrompre ou de changement de contexte (la plupart des changements de contexte se produire sur les appels système ou d'interruptions). Ces registres sont sauvegardés sur un syscall/piège/interruption et puis restauré lors du retour à userland. Les appels système les valeurs de retour par écrit dans cet état. Qui est ce que la fourche. Parent fourche obtient une valeur, processus enfant d'un autre.

Depuis la fourche processus est différent du processus parent, le noyau pourrait rien faire pour elle. Donner toutes les valeurs dans les registres, des mappages de mémoire. Pour faire en sorte que presque tout sauf la valeur de retour est la même que dans le processus parent exige plus d'efforts.

2voto

vz0 Points 11605

Pour chaque processus en cours d'exécution, le noyau a une table de registres, à charge de retour lorsqu'un changement de contexte est faite. fork() est un appel système; un appel spécial qui, une fois mis, le processus devient un changement de contexte et le code du noyau de l'exécution de l'appel s'exécute dans un autre (noyau) thread.

La valeur retournée par les appels système est placé dans un registre spécial (EAX dans x86) que votre application lit après l'appel. Lorsque l' fork() appel est fait, le noyau fait une copie de la procédure, et dans chaque table de registres de chaque descripteur écrit le appropiate valeur: 0, et le pid.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X