48

我在 Robert Love (ISBN:0-672-32720-1) 的“Linux Kernel Development, Second Edition”的第 3 章中读到系统调用clone用于在 Linux 中创建线程。现在的语法clone这样的,需要将起始例程/函数地址传递给它。

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

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

4

2 回答 2

87

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

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

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

所以它是这样工作的:

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(). 因此,theclone()fork() library 函数都是clone()系统调用的包装器。

文档

clone()我的手册副本对既是库函数又是系统调用的事实更加坦率。但是,我确实发现clone()第 2 节中的内容有些误导,而不是第 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);

和,

本页描述了 glibcclone() 包装函数和它所基于的底层系统调用。正文描述了包装函数;原始系统调用的不同之处在本页末尾进行了描述。

最后,

原始clone()系统调用更接近地对应fork(2)于子系统从调用点继续执行。因此,clone()包装函数的 fn 和 arg 参数被省略。此外,参数顺序发生变化。

于 2013-09-19T21:06:00.437 回答
15

@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只需正确设置其标志、子堆栈等的原始系统调用。

于 2015-08-30T14:57:44.377 回答