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

时间:2013-09-19 20:26:29

标签: c linux operating-system system-calls

我读here clone()系统调用用于在Linux中创建线程。现在clone()的{​​{3}}使得需要将起始例程/函数地址传递给它。

但是在syntax页面上写着fork()在内部调用clone()。所以我的问题是fork()创建的子进程如何开始运行fork()调用之后的代码部分,即它如何不需要函数作为起始点?

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

由于

2 个答案:

答案 0 :(得分:65)

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

从glibc的nptl/sysdeps/unix/sysv/linux/fork.cGitHub)(nptl = Linux的本地Posix线程),我们可以找到fork()的实现,这绝对是而不是< / strong>系统调用,我们可以看到神奇发生在ARCH_FORK宏内部,该宏定义为clone() nptl/sysdeps/unix/sysv/linux/x86_64/fork.cclone()的内联调用。但是等等,没有函数或堆栈指针传递给这个版本的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)

通过查看实施,@ 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系统调用及其标志,子堆栈等正确设置。