Comment fork () sait-il quand retourner 0?

prendre l'exemple suivant:

int main(void)
{
     pid_t  pid;

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

corrigez-moi donc si je me trompe, une fois que fork() exécute un processus enfant est créé. Maintenant va par ce réponse fourche () 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 deux processus distincts apparaissent pendant l'appel de fourche et non après sa fin.

Maintenant, je ne comprends pas comment il comprend comment retourner 0 pour le processus enfant et le PID correct pour le processus parent.

C'est là que ça devient vraiment confus. Cette réponse indique que fork () fonctionne en copiant les informations de contexte du processus et en définissant manuellement la valeur de retour à 0.

tout d'abord ai-je raison de dire que le retour à une fonction est placé dans un seul registre? puisque dans un environnement de processeur simple a processus ne peut appeler qu'un sous-programme qui ne renvoie 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 cette fonction renvoie une valeur, cette valeur sera stockée dans une barre de say register. Chaque fois qu'une fonction veut retourner une valeur, elle utilisera un registre de processeur particulier. donc si je suis capable de changer manuellement la valeur de retour dans le bloc process je suis capable de changer la valeur retourné à la fonction à droite?

donc ai-je raison de penser que c'est ainsi que fork() fonctionne?

48
demandé sur Community 2016-04-15 10:00:09

5 réponses

comment cela fonctionne est largement hors de propos - en tant que développeur travaillant à un certain niveau (c'est-à-dire codant sur les API UNIX), vous n'avez vraiment besoin de savoir que cela fonctionne.

ayant dit cela cependant, et reconnaissant que la curiosité ou le besoin de comprendre en profondeur est généralement un bon trait d'avoir, il y a un certain nombre de façons que ce pourrait être fait.

Tout d'abord, votre affirmation qu'une fonction ne peut retourner qu'une seule valeur est correcte dans la mesure où elle va, mais vous devez vous rappeler que, après la division du processus, il y a en fait deux instances de la fonction en cours d'exécution, une dans chaque processus. Ils sont pour la plupart indépendants les uns des autres et peuvent suivre des chemins de code différents. Le schéma suivant peut aider à comprendre ceci:

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

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


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

lorsque la fonction fork() commence à fonctionner, elle stocke L'identifiant du processus en cours (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. Pseudo-code suit:

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'appel split_proc_into_two() et que cela ne fonctionnera presque certainement pas du tout sous les couvertures (a) . C'est juste pour illustrer les concepts autour de lui, qui est essentiellement:

  • obtenir le PID original avant la séparation, qui restera identique pour les deux processus après leur séparation.
  • faites le partage.
  • obtenir le PID courant après la séparation, qui sera différent dans les deux processus.

vous pouvez également jeter un oeil à cette réponse , il explique la fork/exec philosophie.


(a) c'est presque certainement plus complexe que ce que j'ai expliqué. Par exemple, dans MINIX, l'appel à fork finit par s'exécuter dans le noyau, qui a accès à l'arborescence complète du processus.

il copie simplement la structure du processus parent dans une fente libre pour l'enfant, comme suit:

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++;

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

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

donc, fondamentalement identique au schéma que j'ai posé, mais en utilisant des modifications de données plutôt que la sélection de chemin de code pour décider quoi retourner à l'appelant - en d'autres termes, vous verriez quelque chose comme:

return rpc->p_reg[RET_REG];

à la fin de fork() afin que la valeur correcte soit retournée selon qu'il s'agit du processus parent ou enfant.

53
répondu paxdiablo 2017-05-23 12:10:51

dans Linux fork() se produit dans le noyau; la place réelle est le _do_fork ici . En résumé, l'appel système fork() pourrait être quelque chose comme

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

donc dans le noyau, fork() retourne vraiment une fois , dans le processus parent. Cependant le noyau crée aussi le processus enfant comme une copie du processus parent; mais au lieu de revenir d'une fonction ordinaire, il synthétiquement créer un nouveau "kernel stack pour le thread nouvellement créé du processus enfant; et puis context-switch à ce thread (et processus); comme le processus nouvellement créé retourne de la fonction de commutation de contexte, il ferait le processus enfant' thread fin de retourner en mode utilisateur avec 0 comme valeur de retour de fork() .


fondamentalement fork() dans userland est juste une couche mince retourne la valeur que le noyau mette sur sa pile/dans le registre de retour. Le noyau configure le nouveau processus enfant de sorte qu'il renvoie 0 via ce mécanisme à partir de son seul thread; et le pid enfant est retourné dans l'appel système parent comme toute autre valeur de retour de n'importe quel appel système tel que read(2) le serait.

29
répondu Antti Haapala 2016-05-15 07:20:29

vous devez d'abord savoir comment fonctionne le multitâche. 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 le mappage de ces objets virtuels sur les objets réels (la magie est dans le noyau), et il y a certaines machines qui changent les contextes virtuels (processus) en machine physique à mesure que le temps passe.

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

Note: non loin de dire "fourche retourne deux fois", un appel de fonction retourne seulement une fois.

il suffit de penser à une machine de Clonage: vous entrez seul, mais deux personnes sortent, l'une est vous et l'autre est votre clone (très légèrement différent); tandis que le clonage de la machine est en mesure de définir un nom différent du vôtre au clone.

10
répondu Jean-Baptiste Yunès 2016-04-15 16:58:40

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

une des choses que le noyau garde trace pour chaque processus sont les valeurs des registres que ce processus doit avoir restauré au retour d'un appel système, d'un piège, d'une interruption ou d'un commutateur de contexte (la plupart des commutateurs de contexte se produisent sur le système appels ou interruptions). Ces registres sont sauvegardés sur un syscall/trap/interrupt puis restaurés en retournant dans userland. Les appels système retournent les valeurs en écrivant dans cet état. Qui est ce que la fourche. Parent fork obtient une valeur, l'enfant en traite une autre.

puisque le processus Fourché est différent du processus parent, le noyau peut faire n'importe quoi pour lui. Donner toutes les valeurs dans les registres, des mappages de mémoire. Pour faire en sorte que presque tout sauf que la valeur de retour est la même que dans le processus parent nécessite plus d'effort.

8
répondu Art 2016-04-15 07:11:47

pour chaque processus en cours d'exécution, le noyau a une table de registres, à charger en arrière quand un commutateur de contexte est fait. fork() est un appel système; un appel spécial qui, lorsqu'il est fait, reçoit un commutateur de contexte et le code du noyau exécutant l'appel s'exécute dans un thread (noyau) différent.

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

2
répondu vz0 2016-04-15 16:08:24