为什么 sys_fork 没有被 glibc 的 fork 实现使用?
Why is sys_fork not used by glibc's implementation of fork?
在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()
?
简而言之:为什么不呢?
您有一个保证在所有平台上都存在的系统调用(您确实意识到 Intel 不是唯一的平台,对吧?),另一个已被弃用,因为它是不必要的。它们都带有完全相同的语义。当您只调用保证存在的代码时,您的代码会更加紧凑。
我会详细说明一下。
Fork 由 Posix 定义,而 clone 是 Linux 特定的。但是,Linux 有时会采用 Posix 定义的 "system calls" 并在用户 space 中实现它们。 fork(以及 vfork 和 pthread_create)就是这种情况。它们都是通过调用 "clone".
在用户 space 中实现的
因此,fork 在内核级别被认为是不必要的。如果瘦用户 space 包装器可以实现它,那么内核就可以了。因此,在Linux上,clone保证存在于所有平台上,而fork可能存在也可能不存在,具体取决于特定平台。
我查看了 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 的 cruft 极端积累的又一个例子!如果你想找到一些漂亮、干净、维护良好的代码,请继续前进并获得看看 musl libc!)
在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()
?
简而言之:为什么不呢?
您有一个保证在所有平台上都存在的系统调用(您确实意识到 Intel 不是唯一的平台,对吧?),另一个已被弃用,因为它是不必要的。它们都带有完全相同的语义。当您只调用保证存在的代码时,您的代码会更加紧凑。
我会详细说明一下。
Fork 由 Posix 定义,而 clone 是 Linux 特定的。但是,Linux 有时会采用 Posix 定义的 "system calls" 并在用户 space 中实现它们。 fork(以及 vfork 和 pthread_create)就是这种情况。它们都是通过调用 "clone".
在用户 space 中实现的因此,fork 在内核级别被认为是不必要的。如果瘦用户 space 包装器可以实现它,那么内核就可以了。因此,在Linux上,clone保证存在于所有平台上,而fork可能存在也可能不存在,具体取决于特定平台。
我查看了 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 的 cruft 极端积累的又一个例子!如果你想找到一些漂亮、干净、维护良好的代码,请继续前进并获得看看 musl libc!)