我对分叉和克隆有一些困惑。我已经看到:
fork用于进程,clone用于线程
fork只是调用clone,clone用于所有进程和线程
这些是否准确?这 2 个系统调用与 2.6 Linux 内核有什么区别?
答案1
fork()
是最初的 UNIX 系统调用。它只能用于创建新进程,不能用于创建线程。而且,它是便携式的。
在 Linux 中,clone()
是一个新的多功能系统调用,可用于创建新的执行线程。根据传递的选项,新的执行线程可以遵循 UNIX 进程、POSIX 线程、介于两者之间的语义或完全不同的语义(例如不同的容器)。您可以指定各种选项来决定是否共享或复制内存、文件描述符、各种命名空间、信号处理程序等。
由于是超集系统调用, glibc中系统调用包装器clone()
的实现实际上调用了,但这是程序员不需要了解的实现细节。出于向后兼容性的原因,实际的系统调用仍然存在于 Linux 内核中,尽管它已经变得多余,因为使用非常旧版本的 libc 或除 glibc 之外的另一个 libc 的程序可能会使用它。fork()
clone()
fork()
clone()
还用于实现pthread_create()
创建线程的POSIX函数。
可移植程序应该调用fork()
and pthread_create()
,而不是clone()
。
答案2
clone()
Linux 2.6 中似乎有两件事
有一个系统调用:
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
这就是 do 描述的“clone()” man 2 clone
。
如果您足够仔细地阅读该手册页,您将看到以下内容:
It is actually a library function layered on top of the
underlying clone() system call.
显然,您应该使用“库函数”来实现线程,该“库函数”位于令人困惑的同名系统调用之上。
我写了一个小程序:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int
main(int ac, char **av)
{
pid_t cpid;
switch (cpid = fork()) {
case 0: // Child process
break;
case -1: // Error
break;
default: // parent process
break;
}
return 0;
}
使用: 编译它c99 -Wall -Wextra
,并运行它strace -f
以查看系统调用 forking 实际执行的操作。我strace
在 Linux 2.6.18 机器(x86_64 CPU)上得到了这个:
20097 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b4ee9213770) = 20098
20097 exit_group(0) = ?
20098 exit_group(0)
输出中没有出现“fork”调用strace
。输出clone()
中显示的调用与strace
手册页克隆具有非常不同的参数。child_stack=0
因为第一个参数不同于int (*fn)(void *)
.
看起来fork(2)
系统调用是按照以下方式实现的真实的 clone()
,就像“库函数”的clone()
实现一样。这真实的 clone()
具有与手册页克隆不同的参数集。
fork()
简单地说,您关于和的两个明显矛盾的陈述clone()
都是正确的。不过,所涉及的“克隆”是不同的。
答案3
fork()
只是系统调用的一组特定标志clone()
。clone()
足够通用,可以创建“进程”或“线程”,甚至可以创建进程和线程之间的奇怪事物(例如,共享相同文件描述符表的不同“进程”)。
本质上,对于与内核中的执行上下文关联的每种“类型”信息,clone()
您可以选择为该信息设置别名或复制它。线程对应于别名,进程对应于复制。通过将标志的中间组合指定为clone()
,您可以创建非线程或进程的奇怪事物。你通常不应该这样做,我想在 Linux 内核的开发过程中,关于它是否应该允许像clone()
.