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
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();
...
}
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()
.
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 wrapperclone()
sont omis. De plus, l'ordre des arguments change.
@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.