我读here clone()
系统调用用于在Linux中创建线程。现在clone()
的{{3}}使得需要将起始例程/函数地址传递给它。
但是在syntax页面上写着fork()
在内部调用clone()
。所以我的问题是fork()
创建的子进程如何开始运行fork()
调用之后的代码部分,即它如何不需要函数作为起始点?
如果我提供的链接信息不正确,请指导我一些更好的链接/资源。
由于
答案 0 :(得分:65)
对于这样的问题,请务必阅读源代码。
从glibc的nptl/sysdeps/unix/sysv/linux/fork.c
(GitHub)(nptl
= Linux的本地Posix线程),我们可以找到fork()
的实现,这绝对是而不是< / strong>系统调用,我们可以看到神奇发生在ARCH_FORK
宏内部,该宏定义为clone()
nptl/sysdeps/unix/sysv/linux/x86_64/fork.c
中clone()
的内联调用。但是等等,没有函数或堆栈指针传递给这个版本的clone()
!那么,这里发生了什么?
让我们看一下glibc中sysdeps/unix/sysv/linux/x86_64/clone.S
的实现。它位于clone(void (*fn)(void *), void *stack_pointer)
{
push fn onto stack_pointer
syscall_clone()
if (child) {
pop fn off of stack
fn();
exit();
}
}
(GitHub)。您可以看到它的作用是将函数指针保存在子堆栈上,调用克隆系统调用,然后新进程将读取弹出该函数的堆栈,然后调用它。
所以它的工作原理如下:
fork()
fork()
{
...
syscall_clone();
...
}
是......
clone()
实际的fork()
系统调用不接受函数参数,它只是从返回点继续,就像clone()
一样。因此,fork()
和clone()
库函数都是围绕clone()
系统调用的包装器。
我的手册副本更加关注clone()
既是库函数又是系统调用的事实。但是,我确实发现在第2部分中找到#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);
而不是第2部分和第3部分有点误导。从手册页:
clone()
和
此页面描述了glibc
clone()
包装函数和 它所基于的底层系统调用。正文描述 包装函数;原始系统调用的差异是 在本页末尾描述。
最后,
原始
fork(2)
系统调用与clone()
更接近 孩子的执行从呼叫的角度继续。因此, 省略了{{1}}包装函数的fn和arg参数。 此外,参数顺序也会改变。
答案 1 :(得分:9)
我们可以准备一个使用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
系统调用及其标志,子堆栈等正确设置。