web-dev-qa-db-fra.com

Est-il vrai que fork () appelle clone () en interne?

J'ai lu ici que l'appel système clone() est utilisé pour créer un thread sous Linux. Maintenant, la syntaxe de clone() est telle qu'une adresse de routine/fonction de démarrage doit lui être transmise.

Mais ici, sur la page this , il est écrit que fork() appelle clone() en interne. Donc, ma question est de savoir comment le processus enfant créé par fork() commence à exécuter la partie de code qui se trouve après l'appel de fork(), c'est-à-dire comment ne nécessite-t-il pas une fonction comme point de départ?

Si les liens que j'ai fournis contiennent des informations incorrectes, veuillez me guider vers de meilleurs liens/ressources.

Merci

42

Pour des questions comme celle-ci, lisez toujours le code source.

À partir du nptl/sysdeps/unix/sysv/linux/fork.c de la glibc ( GitHub ) (nptl = threads Posix natifs pour Linux), nous pouvons trouver l'implémentation de fork(), qui est définitivement pas un appel système, nous pouvons voir que la magie se produit à l'intérieur de la macro Arch_FORK, qui est définie comme un appel en ligne à clone() dans nptl/sysdeps/unix/sysv/linux/x86_64/fork.c ( GitHub ). Mais attendez, aucune fonction ou pointeur de pile n'est transmis à cette version de clone()! Que se passe-t-il?

Regardons donc l'implémentation de clone() dans la glibc. C'est dans sysdeps/unix/sysv/linux/x86_64/clone.S ( GitHub ). Vous pouvez voir que ce qu'il fait est qu'il enregistre le pointeur de fonction sur la pile de l'enfant, appelle le syscall du clone, puis le nouveau processus lira pop la fonction hors de la pile, puis l'appellera.

Donc ça marche comme ça:

clone(void (*fn)(void *), void *stack_pointer)
{
    Push fn onto stack_pointer
    syscall_clone()
    if (child) {
        pop fn off of stack
        fn();
        exit();
    }
}

Et fork() est ...

fork()
{
    ...
    syscall_clone();
    ...
}

Résumé

L'appel système réel clone() ne prend pas d'argument de fonction, il continue simplement à partir du point de retour, tout comme fork(). Ainsi, clone() et fork() fonctions de bibliothèque sont des enveloppes autour de l'appel système clone().

Documentation

Ma copie du manuel est un peu plus directe sur le fait que clone() est à la fois une fonction de bibliothèque et un appel système. Cependant, je trouve quelque peu trompeur que clone() se trouve dans la section 2, plutôt que dans les sections 2 et 3. À partir de la page de manuel:

#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
          int flags, void *arg, ...
          /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

/* Prototype for the raw system call */

long clone(unsigned long flags, void *child_stack,
          void *ptid, void *ctid,
          struct pt_regs *regs);

Et,

Cette page décrit à la fois la fonction wrapper glibc clone() et l'appel système sous-jacent sur lequel elle est basée. Le texte principal décrit la fonction wrapper; les différences pour l'appel système brut sont décrites vers la fin de cette page.

Finalement,

L'appel système brut clone() correspond plus étroitement à fork(2) dans la mesure où l'exécution chez l'enfant continue à partir du point de l'appel. En tant que tels, les arguments fn et arg de la fonction wrapper clone() sont omis. De plus, l'ordre des arguments change.

71
Dietrich Epp

@Dietrich a fait un excellent travail en expliquant la mise en œuvre. C'est incroyable! Quoi qu'il en soit, il y a une autre façon de le découvrir: en regardant les appels strace "renifle".

Nous pouvons préparer un programme très simple qui utilise fork(2) puis vérifier notre hypothèse (c'est-à-dire qu'il n'y a pas vraiment de fork syscall).

#define WRITE(__fd, __msg) write(__fd, __msg, strlen(__msg))

int main(int argc, char *argv[])
{
  pid_t pid;

  switch (pid = fork()) {
    case -1:
      perror("fork:");
      exit(EXIT_FAILURE);
      break;
    case 0:
      WRITE(STDOUT_FILENO, "Hi, i'm the child");
      exit(EXIT_SUCCESS);
    default:
      WRITE(STDERR_FILENO, "Heey, parent here!");
      exit(EXIT_SUCCESS);
  }

  return EXIT_SUCCESS;
}

Maintenant, compilez ce code (clang -Wall -g fork.c -o fork.out) puis exécutez-le avec strace:

strace -Cfo ./fork.strace.log ./fork.out

Cela interceptera les appels système appelés par notre processus (avec -f nous interceptons également les appels de l'enfant), puis mettons ces appels dans ./fork.trace.log; -c option nous donne un résumé à la fin). Le résultat dans ma machine (Ubuntu 14.04, x86_64 Linux 3.16) est (résumé):

6915  Arch_prctl(Arch_SET_FS, 0x7fa001a93740) = 0
6915  mprotect(0x7fa00188c000, 16384, PROT_READ) = 0
6915  mprotect(0x600000, 4096, PROT_READ) = 0
6915  mprotect(0x7fa001ab9000, 4096, PROT_READ) = 0
6915  munmap(0x7fa001a96000, 133089)    = 0
6915  clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa001a93a10) = 6916
6915  write(2, "Heey, parent here!", 18) = 18
6916  write(1, "Hi, i'm the child", 17 <unfinished ...>
6915  exit_group(0)                     = ?
6916  <... write resumed> )             = 17
6916  exit_group(0)                     = ?
6915  +++ exited with 0 +++
6916  +++ exited with 0 +++
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 24.58    0.000029           4         7           mmap
 17.80    0.000021           5         4           mprotect
 14.41    0.000017           9         2           write
 11.02    0.000013          13         1           munmap
 11.02    0.000013           4         3         3 access
 10.17    0.000012           6         2           open
  2.54    0.000003           2         2           fstat
  2.54    0.000003           3         1           brk
  1.69    0.000002           2         1           read
  1.69    0.000002           1         2           close
  0.85    0.000001           1         1           clone
  0.85    0.000001           1         1           execve
  0.85    0.000001           1         1           Arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.000118                    28         3 total

Comme prévu, pas d'appels fork. Juste le brut clone syscall avec ses drapeaux, pile enfant et etc correctement défini.

11
Ciro Costa