【问题标题】:Why is sys_fork not used by glibc's implementation of fork?为什么 glibc 的 fork 实现不使用 sys_fork?
【发布时间】:2016-09-07 00:20:37
【问题描述】:

在 eglibc 的 nptl/sysdeps/unix/sysv/linux/i386/fork.c 中有一个定义:

#define ARCH_FORK() \
  INLINE_SYSCALL (clone, 5,                           \
          CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, 0,     \
          NULL, NULL, &THREAD_SELF->tid)

在实际中使用__libc_fork() 作为实现的核心。但是例如在 Linux 的 arch/x86/entry/syscalls/syscall_32.tbl 中存在一个 sys_fork 条目,在 syscalls_64.tbl 中也是如此。因此,显然 Linux 确实有针对 fork 的特殊系统调用。

所以我现在想知道:如果内核已经提供了 fork 系统调用,为什么 glibc 会根据 clone 实现 fork()

【问题讨论】:

    标签: c linux fork glibc


    【解决方案1】:

    简而言之:为什么不呢?

    您有一个系统调用可以保证在所有平台上都存在(您确实意识到英特尔并不是唯一的平台,对吗?),另一个因为没有必要而被弃用。它们都具有完全相同的语义。当您只调用保证存在的代码时,您的代码会更加紧凑。

    我会详细说明一下。

    Fork 是由 Posix 定义的,而 clone 是特定于 Linux 的。但是,Linux 有时会采用 Posix 定义的“系统调用”并在用户空间中实现它们。 fork(以及 vfork 和 pthread_create)就是这种情况。它们都是通过调用“克隆”在用户空间实现的。

    因此,fork 在内核级别被认为是不必要的。如果一个瘦用户空间包装器可以实现它,那么内核就可以了。因此,在 Linux 上,clone 保证存在于所有平台上,而 fork 可能存在也可能不存在,具体取决于特定平台。

    【讨论】:

    • 我相信fork(2) 早于clone(2)
    • 这既正确又无关紧要。我已经编辑了答案以解释原因。
    【解决方案2】:

    我查看了 Ulrich Drepper 将该代码添加到 glibc 的提交,但在提交日志(或其他地方)中没有任何解释。

    看看 Linux 对 fork 的实现,不过:

    return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
    

    这里是clone:

    return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
    

    显然,它们几乎完全相同。唯一的区别是调用clone时,可以设置各种标志,可以为新进程指定堆栈大小等。fork不带任何参数。

    查看 Drepper 的代码,clone 标志是 CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD。如果使用fork,则唯一的标志将是SIGCHLD

    下面是 clone 手册页中关于这些额外标志的内容:

    CLONE_CHILD_CLEARTID (since Linux 2.5.49)
              Erase child thread ID at location ctid in child memory when  the  child
              exits,  and  do  a  wakeup  on  the futex at that address.  The address
              involved may be changed by the set_tid_address(2) system call.  This is
              used by threading libraries.
    
    CLONE_CHILD_SETTID (since Linux 2.5.49)
              Store child thread ID at location ctid in child memory.
    

    ...你可以看到他确实传递了一个指针,指向内核应该首先存储子线程 ID 的位置,然后再进行 futex 唤醒。 glibc 是否在某处对该地址进行 futex 等待?我不知道。如果是这样,那就可以解释为什么 Drepper 选择使用clone

    (如果不是,那将只是我们钟爱的 glibc 的极端积累的又一个例子!如果你想找到一些漂亮、干净、维护良好的代码,请继续前进并去看看在 musl libc!)

    【讨论】:

    • pthread_join() 进行唤醒。与此类似,如果您使用线程链接,glibc 会调用 set_tid_address(),因此可以加入“主”线程:stackoverflow.com/questions/6975098/…
    • @usr:新进程可以产生线程。如果你希望这些线程能够在新进程的主线程上pthread_join(),glibc 需要 futex 的地址。
    • @usr: execve()通过调用set_tid_address()来处理这个问题。顺便说一句,如果您立即执行,则有 vfork()posix_spawn(),如果您的 libc 提供了。
    猜你喜欢
    • 2018-12-28
    • 1970-01-01
    • 2019-06-12
    • 1970-01-01
    • 2015-05-06
    • 2016-03-01
    • 2019-01-23
    • 2018-04-04
    • 2017-06-21
    相关资源
    最近更新 更多