read calls c linux operating-system system-calls

calls - read system call



¿Es cierto que fork() llama a clone() internamente? (2)

Leí here que la llamada al sistema clone() se usa para crear un hilo en Linux. Ahora la syntax de clone() es tal que se necesita pasar una dirección de rutina / función de inicio.

Pero aquí en this página está escrito que fork() llama a clone() internamente. Entonces mi pregunta es ¿cómo el proceso hijo creado por fork() comienza a ejecutar la parte del código que está después de la llamada fork() , es decir, cómo no requiere una función como punto de partida?

Si los enlaces que proporcioné tienen información incorrecta, guíenme a algunos enlaces / recursos mejores.

Gracias


@Dietrich hizo un gran trabajo al explicar la implementación. ¡Eso es increíble! De todos modos, hay otra manera de descubrir eso: mirando las llamadas strace "sniffs".

Podemos preparar un programa muy simple que use fork(2) y luego verificar nuestra hipótesis (es decir, que no hay realmente ningún syscall de fork ).

#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; }

Ahora, compila ese código ( clang -Wall -g fork.c -o fork.out ) y luego strace con strace :

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

Esto interceptará las llamadas al sistema llamadas por nuestro proceso (con -f también interceptamos las llamadas del niño) y luego pondrá esas llamadas en ./fork.trace.log ; -c opción nos da un resumen al final). El resultado en mi máquina (Ubuntu 14.04, x86_64 Linux 3.16) es (resumido):

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

Como era de esperar, no hay llamadas de fork . Solo el syscall de clone procesar con sus banderas, pila de elementos secundarios, etc. está correctamente configurado.


Para preguntas como esta, siempre lea el código fuente.

Desde glibc''s nptl/sysdeps/unix/sysv/linux/fork.c ( GitHub ) ( nptl = hilos nativos de Posix para Linux) podemos encontrar la implementación de fork() , que definitivamente no es un syscall, podemos ver que la magia ocurre dentro de la macro ARCH_FORK , que se define como una llamada en línea a clone() en nptl/sysdeps/unix/sysv/linux/x86_64/fork.c ( GitHub ). Pero espere, ninguna función o puntero de pila se pasa a esta versión de clone() ! ¿Entonces, qué está pasando aquí?

Veamos la implementación de clone() en glibc, entonces. Está en sysdeps/unix/sysv/linux/x86_64/clone.S ( GitHub ). Usted puede ver que lo que hace es guardar el puntero de la función en la pila del niño, llama al clon syscall, y luego el nuevo proceso leerá la función de la pila y luego la llamará.

Entonces funciona así:

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

Y fork() es ...

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

Resumen

El syscall de clone() real no toma un argumento de función, simplemente continúa desde el punto de retorno, al igual que fork() . Por lo tanto, las funciones de la biblioteca clone() y fork() son envoltorios alrededor de syscall clone() .

Documentación

Mi copia del manual es algo más sincera sobre el hecho de que clone() es tanto una función de biblioteca como una llamada al sistema. Sin embargo, encuentro algo engañoso que clone() se encuentre en la sección 2, en lugar de en la sección 2 y la sección 3. Desde la página man:

#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);

Y,

Esta página describe la función de envoltura glibc clone() y la llamada al sistema subyacente en la que se basa. El texto principal describe la función de envoltura; las diferencias para la llamada al sistema en bruto se describen hacia el final de esta página.

Finalmente,

La llamada al sistema raw clone() corresponde más estrechamente a fork(2) en que la ejecución en el niño continúa desde el punto de la llamada. Como tal, se omiten los argumentos fn y arg de la función de envoltura clone() . Además, el orden de los argumentos cambia.