fork()是否真的在内部调用clone()?

我在这里读到, clone()系统调用被用来在Linux中创build一个线程。 现在clone()的语法是这样的,一个启动例程/函数地址需要传递给它。

但是在这个页面上写了fork() clone()内部调用clone() 。 所以我的问题是,由fork()创build的subprocess如何开始运行fork()调用之后的那部分代码,也就是说,如何不需要函数作为起点?

如果我提供的链接有不正确的信息,请指导我一些更好的链接/资源。

谢谢

Solutions Collecting From Web of "fork()是否真的在内部调用clone()?"

对于这样的问题,请阅读源代码。

从glibc的nptl/sysdeps/unix/sysv/linux/fork.c ( GitHub )( nptl = Linux的本地Posix线程),我们可以找到fork()的实现,这绝对不是系统调用,我们可以看到,发生在ARCH_FORK宏中,该宏被定义为在nptl/sysdeps/unix/sysv/linux/x86_64/fork.c ( GitHub )中对clone()的内联调用。 但是等等,没有函数或堆栈指针传递给这个版本的clone() ! 那么,这里发生了什么?

我们来看一下glibc中的clone()的实现。 它在sysdeps/unix/sysv/linux/x86_64/clone.S ( GitHub )中。 你可以看到它所做的是将函数指针保存在子栈中,调用clone系统调用,然后新进程读取从栈中弹出函数,然后调用它。

所以它是这样工作的:

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

fork()是…

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

概要

实际的clone()系统调用不带一个函数参数,它只是继续从返回点,就像fork() 。 所以clone()fork() 库函数都是clone()系统调用的包装器。

文档

我的手册副本有点更事前的事实, clone()是一个库函数和一个系统调用。 但是,我发现在第2部分找到了clone() ,而不是在第2部分和第3部分中都有点误导。从手册页:

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

和,

这个页面描述了glibc的clone()包装函数和它所基于的底层系统调用。 正文描述包装功能; 原始系统调用的差异在本页末尾描述。

最后,

原始的clone()系统调用更接近于fork(2) ,因为子进程中的执行从调用点继续。 因此, clone()包装器函数的fn和arg参数被省略。 此外,参数顺序也会改变。

@Dietrich通过观察实施做了很好的解释。 太棒了! 无论如何,还有另一种发现的方式:通过查看呼吁strace“嗅”。

我们可以准备一个使用fork(2)的非常简单的程序,然后检查我们的假设(即没有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; } 

现在,编译该代码( clang -Wall -g fork.c -o fork.out ),然后用strace执行它:

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

这将拦截由我们的进程调用的系统调用(使用-f我们也拦截了孩子的调用),然后把这些调用放到./fork.trace.log ; -c选项最后给我们一个总结)。 我的机器(Ubuntu 14.04,x86_64 Linux 3.16)的结果是(总结):

 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 

如预期的那样,没有fork电话。 只是原始clone系统调用与其标志,子堆栈等正确设置。