clone() - Unix 和 Linux 系统调用
Tutorials Point


  Unix 初学者指南
  Unix Shell 编程
  高级 Unix
  Unix 有用参考
  Unix 有用资源
  精选阅读

版权所有 © 2014 tutorialspoint



  首页     参考     讨论论坛     关于 TP  

clone() - Unix 和 Linux 系统调用


previous next AddThis Social Bookmark Button

广告

名称

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_clonesys_clone 的描述在本页的末尾给出。

fork(2) 不同,这些调用允许子进程与其调用进程共享其执行上下文的一部分,例如内存空间、文件描述符表和信号处理程序表。(请注意,在本手册页中,“调用进程”通常对应于“父进程”。但请参见下面 CLONE_PARENT 的描述。)

clone() 的主要用途是实现线程:程序中多个并发运行在共享内存空间中的控制线程。

当使用 clone() 创建子进程时,它会执行函数应用 fn(arg)。(这与 fork(2) 不同,在 fork(2) 调用中,子进程从调用点继续执行。)fn 参数是指向一个函数的指针,该函数在子进程执行开始时由子进程调用。arg 参数传递给 fn 函数。

fn(arg) 函数应用返回时,子进程终止。fn 返回的整数是子进程的退出代码。子进程也可以通过调用 exit(2) 或在接收到致命信号后显式终止。

child_stack 参数指定子进程使用的堆栈位置。由于子进程和调用进程可能会共享内存,因此子进程无法在与调用进程相同的堆栈中执行。因此,调用进程必须为子进程堆栈设置内存空间,并将指向此空间的指针传递给 clone()。在所有运行 Linux 的处理器上(除了 HP PA 处理器),堆栈都向下增长,因此 child_stack 通常指向为子进程堆栈设置的内存空间的最顶端地址。

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 起)在新的命名空间中启动子进程。

每个进程都存在于一个命名空间中。进程的 命名空间 是描述该进程所见的文件层次结构的数据(挂载集)。在 fork(2) 或 clone(2) 之后(其中未设置 CLONE_NEWNS 标志),子进程与父进程位于相同的命名空间中。系统调用 mount(2) 和 umount(2) 会更改调用进程的命名空间,因此会影响位于同一命名空间中的所有进程,但不会影响不同命名空间中的进程。

clone(2) 之后(其中设置了 CLONE_NEWNS 标志),克隆的子进程将在新的命名空间中启动,该命名空间使用父进程命名空间的副本进行初始化。

只有特权进程(拥有 CAP_SYS_ADMIN 功能的进程)才能指定 CLONE_NEWNS 标志。不允许在同一个 clone() 调用中同时指定 CLONE_NEWNSCLONE_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_THREAD 的情况下调用 clone() 时,生成的线程将放置在一个新的线程组中,该线程组的 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 开始)在父进程和子进程的内存中,将子线程 ID 存储在parent_tidptr指向的位置。(在 Linux 2.5.32-2.5.48 中,有一个名为 CLONE_SETTID 的标志执行此操作。)
CLONE_CHILD_SETTID(从 Linux 2.5.49 开始)在子进程的内存中,将子线程 ID 存储在child_tidptr指向的位置。
CLONE_CHILD_CLEARTID(从 Linux 2.5.49 开始)当子进程退出时,擦除子进程内存中child_tidptr指向位置的子线程 ID,并在该地址处的 futex 上执行唤醒操作。相关地址可以通过set_tid_address(2)系统调用更改。这被线程库使用。

sys_clone

sys_clone系统调用与fork(2)更相似,因为子进程中的执行从调用的位置继续。因此,sys_clone只需要flagschild_stack参数,这两个参数与clone()中的含义相同。(注意,这两个参数的顺序与clone()不同。)

sys_clone的另一个区别是child_stack参数可以为零,在这种情况下,写时复制语义确保当任一进程修改堆栈时,子进程获得堆栈页面的单独副本。在这种情况下,为了正确操作,不应指定CLONE_VM选项。

从 Linux 2.5.49 开始,系统调用具有五个参数。这两个新参数是parent_tidptr,它指向在父进程和子进程内存中子线程 ID 将写入的位置(如果指定了 CLONE_PARENT_SETTID),以及child_tidptr,它指向在子进程内存中子线程 ID 将写入的位置(如果指定了 CLONE_CHILD_SETTID)。

返回值

成功时,子进程的线程 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_FSCLONE_NEWNS
EINVAL 当为child_stack指定零值时,由clone()返回。
ENOMEM 无法分配足够的内存来为子进程分配任务结构,或复制需要复制的调用者上下文的那些部分。
EPERM 非根进程(没有 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);

参见



previous next Printer Friendly

广告


  

广告



广告