clone() - Unix、Linux系统调用
广告
名称
clone, __clone2 - 创建子进程
概要
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
int __clone2(int (*fn)(void *), void *child_stack_base,
size_t stack_size, int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
|
描述
clone() 创建一个新进程,方式类似于 fork(2)。它实际上是一个构建在底层 clone() 系统调用(下文称为 sys_clone)之上的库函数。sys_clone 的描述在页面末尾给出。
与 fork(2) 不同,这些调用允许子进程与其调用进程共享其执行上下文的部分内容,例如内存空间、文件描述符表和信号处理程序表。(请注意,在本手册页中,“调用进程”通常对应于“父进程”。但请参见下面 CLONE_PARENT 的描述。)
clone() 的主要用途是实现线程:程序中多个控制线程在共享内存空间中并发运行。
当使用 clone() 创建子进程时,它将执行函数应用 fn(arg)。(这与 fork(2) 不同,在 fork(2) 调用之后,子进程从调用点继续执行。)fn 参数是指向子进程在其执行开始时调用的函数的指针。arg 参数传递给 fn 函数。
当 fn(arg) 函数应用返回时,子进程终止。fn 返回的整数是子进程的退出代码。子进程也可以通过调用 exit(2) 或在接收到致命信号后显式终止。
child_stack 参数指定子进程使用的堆栈的位置。由于子进程和调用进程可能共享内存,因此子进程不可能在与调用进程相同的堆栈中执行。因此,调用进程必须为子进程堆栈设置内存空间,并将指向此空间的指针传递给 clone()。
flags 的低字节包含子进程死亡时发送到父进程的终止信号的编号。如果此信号指定为除 SIGCHLD 之外的任何其他信号,则父进程必须在使用 wait(2) 等待子进程时指定 __WALL 或 __WCLONE 选项。如果没有指定信号,则子进程终止时不会向父进程发送信号。 .
flags 也可以与零个或多个以下常量按位或运算,以指定调用进程和子进程之间共享的内容
标签 | 描述 |
CLONE_PARENT (自 Linux 2.3.12 起) | 如果设置了 CLONE_PARENT,则新子进程的父进程(由 getppid(2) 返回)将与调用进程的父进程相同。 如果没有设置 CLONE_PARENT,则(与 fork(2) 一样)子进程的父进程是调用进程。 请注意,当子进程终止时,由 getppid(2) 返回的父进程会收到信号,因此,如果设置了 CLONE_PARENT,则调用进程的父进程(而不是调用进程本身)将收到信号。 |
CLONE_FS | 如果设置了 CLONE_FS,则调用进程和子进程共享相同的系统信息。这包括文件系统的根目录、当前工作目录和 umask。调用进程或子进程执行的任何 chroot(2)、chdir(2) 或 umask(2) 调用也会影响另一个进程。 如果没有设置 CLONE_FS,则子进程在 clone() 调用时使用调用进程的文件系统信息的副本工作。后来由其中一个进程执行的 chroot(2)、chdir(2)、umask(2) 调用不会影响另一个进程。 |
CLONE_FILES | 如果设置了 CLONE_FILES,则调用进程和子进程共享相同的文件描述符表。调用进程或子进程创建的任何文件描述符在另一个进程中也是有效的。类似地,如果其中一个进程关闭文件描述符或更改其关联标志(使用 fcntl(2) F_SETFD 操作),则另一个进程也会受到影响。 如果没有设置 CLONE_FILES,则子进程继承在 clone() 时在调用进程中打开的所有文件描述符的副本。(子进程中复制的文件描述符引用与调用进程中相应文件描述符相同的打开文件描述符(参见 open(2))。)调用进程或子进程执行的后续打开或关闭文件描述符或更改文件描述符标志的操作不会影响另一个进程。 |
CLONE_NEWNS (自 Linux 2.4.19 起) | 在新命名空间中启动子进程。 每个进程都存在于一个命名空间中。进程的命名空间是描述该进程所看到的层级结构的数据(挂载集)。在没有设置 CLONE_NEWNS 标志的 fork(2) 或 clone(2) 之后,子进程与父进程位于相同的命名空间中。mount(2) 和 umount(2) 系统调用会更改调用进程的命名空间,因此会影响位于同一命名空间中的所有进程,但不会影响不同命名空间中的进程。 在设置了 CLONE_NEWNS 标志的 clone(2) 之后,克隆的子进程将在一个新的命名空间中启动,该命名空间使用父进程的命名空间的副本进行初始化。 只有特权进程(拥有 CAP_SYS_ADMIN 功能的进程)才能指定 CLONE_NEWNS 标志。不允许在同一个 clone() 调用中同时指定 CLONE_NEWNS 和 CLONE_FS。 |
CLONE_SIGHAND | 如果设置了 CLONE_SIGHAND,则调用进程和子进程共享相同的信号处理程序表。如果调用进程或子进程调用 sigaction(2) 来更改与信号关联的行为,则该行为也会在另一个进程中更改。但是,调用进程和子进程仍然具有不同的信号掩码和挂起信号集。因此,其中一个进程可以使用 sigprocmask(2) 来阻塞或解除阻塞某些信号,而不会影响另一个进程。 如果没有设置 CLONE_SIGHAND,则子进程在调用 clone() 时继承调用进程的信号处理程序的副本。后来由其中一个进程执行的 sigaction(2) 调用不会影响另一个进程。 自 Linux 2.6.0-test6 起,如果指定了 CLONE_SIGHAND,则 flags 还必须包含 CLONE_VM |
CLONE_PTRACE | 如果指定了 CLONE_PTRACE,并且正在跟踪调用进程,则也跟踪子进程(参见 ptrace(2))。 |
CLONE_UNTRACED (自 Linux 2.5.46 起) | 如果指定了 CLONE_UNTRACED,则跟踪进程无法对此子进程强制执行 CLONE_PTRACE。 |
CLONE_STOPPED (自 Linux 2.6.0-test2 起) | 如果设置了 CLONE_STOPPED,则子进程最初处于停止状态(好像它收到了 SIGSTOP 信号),并且必须通过向其发送 SIGCONT 信号来恢复。 |
CLONE_VFORK | 如果设置了 CLONE_VFORK,则调用进程的执行将暂停,直到子进程通过调用 execve(2) 或 _exit(2) 释放其虚拟内存资源(与 vfork(2) 一样)。 如果没有设置 CLONE_VFORK,则调用后调用进程和子进程都是可调度的,并且应用程序不应依赖于以任何特定顺序发生的执行。 |
CLONE_VM | 如果设置了 CLONE_VM,则调用进程和子进程在相同的内存空间中运行。特别是,调用进程或子进程执行的内存写入在另一个进程中也是可见的。此外,子进程或调用进程使用 mmap(2) 或 munmap(2) 执行的任何内存映射或取消映射也会影响另一个进程。 如果没有设置 CLONE_VM,则子进程在 clone() 时在调用进程的内存空间的单独副本中运行。与 fork(2) 一样,由其中一个进程执行的内存写入或文件映射/取消映射不会影响另一个进程。 |
CLONE_PID (已过时) | 如果设置了 CLONE_PID,则子进程将使用与调用进程相同的进程 ID 创建。这对于入侵系统很有用,但在其他情况下没什么用。自 2.3.21 起,只有系统引导进程 (PID 0) 才能指定此标志。它在 Linux 2.5.16 中消失了。 |
CLONE_THREAD (自 Linux 2.4.0-test8 起) | 如果设置了CLONE_THREAD,则子进程将与调用进程位于同一个线程组中。为了使后续关于CLONE_THREAD的讨论更易于理解,本文使用“线程”来指代线程组内的进程。 线程组是 Linux 2.4 中添加的一项功能,用于支持 POSIX 线程的概念,即共享单个 PID 的一组线程。在内部,这个共享的 PID 是线程组的所谓的线程组标识符 (TGID)。从 Linux 2.4 开始,对getpid(2) 的调用返回调用者的 TGID。 组内的线程可以通过其(系统范围内的)唯一线程 ID (TID) 来区分。新线程的 TID 可作为clone() 调用者返回的函数结果获得,线程可以使用gettid(2) 获取自身的 TID。 如果调用clone() 时未指定CLONE_THREAD,则生成的线程将放入一个新的线程组中,其 TGID 与线程的 TID 相同。此线程是新线程组的领导者。 使用CLONE_THREAD创建的新线程与clone() 调用者的父进程相同(即,类似于CLONE_PARENT),因此对getppid(2) 的调用将为线程组中的所有线程返回相同的值。当CLONE_THREAD线程终止时,使用clone() 创建它的线程不会收到SIGCHLD(或其他终止)信号;也不能使用wait(2) 获取此线程的状态。(据说该线程是分离的。) 线程组中的所有线程终止后,线程组的父进程将收到SIGCHLD(或其他终止)信号。 如果线程组中的任何线程执行execve(2),则除线程组领导者之外的所有线程都将终止,并且新程序将在线程组领导者中执行。 如果线程组中的一个线程使用fork(2) 创建子进程,则组中的任何线程都可以使用wait(2) 等待该子进程。 从 Linux 2.5.35 开始,如果指定了CLONE_THREAD,则flags 也必须包含CLONE_SIGHAND。 可以使用kill(2) 将信号发送到整个线程组(即 TGID),或者使用tgkill(2) 发送到特定线程(即 TID)。 信号处理和操作是进程范围的:如果未处理的信号传递到一个线程,则它将影响(终止、停止、继续、忽略)线程组的所有成员。 每个线程都有自己的信号掩码,由sigprocmask(2) 设置,但信号可以挂起:对于整个进程(即,可传递到线程组的任何成员),当使用kill(2) 发送时;或者对于单个线程,当使用tgkill(2) 发送时。对sigpending(2) 的调用返回一个信号集,该信号集是整个进程的挂起信号和调用线程的挂起信号的并集。 如果使用kill(2) 将信号发送到线程组,并且线程组已为该信号安装了处理程序,则处理程序将恰好在一个任意选择的线程组成员中被调用,该成员未阻塞该信号。如果组中的多个线程正在等待使用sigwaitinfo(2) 接收相同的信号,则内核将任意选择其中一个线程来接收使用kill(2) 发送的信号。 |
CLONE_SYSVSEM(从 Linux 2.5.10 开始) | 如果设置了CLONE_SYSVSEM,则子进程和调用进程共享单个 System V 信号量撤销值列表(参见semop(2))。如果没有设置此标志,则子进程将拥有一个单独的撤销列表,该列表最初为空。 |
CLONE_SETTLS(从 Linux 2.5.32 开始) | newtls 参数是新的 TLS(线程局部存储)描述符。(参见set_thread_area(2)。) |
CLONE_PARENT_SETTID(从 Linux 2.5.49 开始) | 在父进程和子进程内存中的位置parent_tidptr处存储子线程ID。(在 Linux 2.5.32-2.5.48 中,有一个标志 CLONE_SETTID 执行此操作。) |
CLONE_CHILD_SETTID(从 Linux 2.5.49 开始) | 在子进程内存中的位置child_tidptr处存储子线程ID。 |
CLONE_CHILD_CLEARTID(从 Linux 2.5.49 开始) | 当子进程退出时,擦除子进程内存中位置child_tidptr处的子线程ID,并在该地址的 futex 上执行唤醒操作。所涉及的地址可以通过set_tid_address(2) 系统调用更改。这被线程库使用。 |
sys_clonesys_clone 系统调用与fork(2) 更接近,因为子进程中的执行从调用的位置继续。因此,sys_clone 只需要flags 和child_stack 参数,它们与clone() 的含义相同。(请注意,这些参数的顺序与clone() 不同。)sys_clone 的另一个区别是child_stack 参数可以为零,在这种情况下,写时复制语义确保当任一进程修改堆栈时,子进程获得堆栈页面的单独副本。在这种情况下,为了正确操作,不应指定CLONE_VM 选项。 从 Linux 2.5.49 开始,系统调用有五个参数。两个新参数是parent_tidptr,它指向在指定 CLONE_PARENT_SETTID 的情况下将写入子线程 ID 的位置(在父进程和子进程内存中),以及child_tidptr,它指向在指定 CLONE_CHILD_SETTID 的情况下将写入子线程 ID 的位置(在子进程内存中)。 返回值成功时,子进程的线程 ID 将返回到调用者的线程执行中。失败时,将返回 -1 到调用者的上下文,不会创建子进程,并且errno 将被相应地设置。错误
标签 | 描述 |
EAGAIN | 已经有太多进程正在运行。 |
EINVAL |
指定了CLONE_SIGHAND,但未指定CLONE_VM。(从 Linux 2.6.0-test6 开始。) |
EINVAL |
指定了CLONE_THREAD,但未指定CLONE_SIGHAND。(从 Linux 2.5.35 开始。) |
EINVAL | 在flags中同时指定了CLONE_FS和CLONE_NEWNS。 |
EINVAL | 当为child_stack指定零值时,由clone() 返回。 |
ENOMEM | 无法分配足够的内存来为子进程分配任务结构,或复制需要复制的调用者上下文的部分。 |
EPERM |
非 root 进程(没有 CAP_SYS_ADMIN 权限的进程)指定了CLONE_NEWNS。 |
EPERM |
非 0 进程指定了CLONE_PID。 |
版本
libc5 中没有clone() 的条目。glibc2 提供了如本手册页中所述的clone()。
符合标准
clone() 和sys_clone 调用是 Linux 特定的,不应在旨在可移植的程序中使用。
备注
在内核 2.4.x 系列中,CLONE_THREAD 通常不会使新线程的父进程与调用进程的父进程相同。但是,对于内核版本 2.4.7 到 2.4.18,CLONE_THREAD 标志暗示CLONE_PARENT 标志(如内核 2.6 中那样)。
曾经有过CLONE_DETACHED(在 2.5.32 中引入):父进程不需要子进程退出信号。在 2.6.2 中,需要与CLONE_THREAD一起使用的情况消失了。此标志仍然已定义,但没有效果。在 x86 上,不应通过 vsyscall 调用clone(),而应直接通过int $0x80 调用。在 IA-64 上,使用不同的系统调用。
int __clone2(int (*fn)(void *), void *child_stack_base,
size_t stack_size, int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
|
__clone2() 系统调用的操作方式与clone() 相同,只是child_stack_base 指向子进程堆栈区域的最低地址,而stack_size 指定child_stack_base指向的堆栈的大小。
错误
包含 NPTL 线程库的 GNU C 库版本包含一个用于getpid(2) 的包装函数,该函数执行 PID 的缓存。在针对此类库链接的程序中,即使未使用CLONE_THREAD 创建线程(因此不在同一个线程组中),对getpid(2) 的调用也可能返回相同的值。要获得真实结果,可能需要使用如下代码:
#include <syscall.h>
pid_t mypid;
mypid = syscall(SYS_getpid);
|
参见
广告
|