版本

clone3()系统调用首先出现在Linux 5.3中。

CLONE - Linux手册页

Linux程序员手册 第2部分
更新日期: 2020-06-09

语法

/* Prototype for the glibc wrapper function */

#define _GNU_SOURCE
#include <sched.h>

int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
          /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

/* For the prototype of the raw clone() system call, see NOTES */

long clone3(struct clone_args *cl_args, size_t size);

注意:clone3()还没有glibc包装器;请参阅注释。

说明

这些系统调用以类似于fork(2)的方式创建一个新的("子")进程。

fork(2)相比,这些系统调用可以更精确地控制在调用进程和子进程之间共享哪些执行上下文。例如,使用这些系统调用,调用者可以控制两个进程是否共享虚拟地址空间,文件描述符表和信号处理程序表。这些系统调用还允许将新的子进程放置在单独的名称空间(7)中。

请注意,在本手册页中,"呼叫过程"通常对应于"父过程"。但是请参阅下面的CLONE_PARENT和CLONE_THREAD的描述。

此页面描述以下接口:

*
glibc clone()包装函数和它所基于的基础系统调用。正文描述了包装函数;原始系统调用的差异将在本页尾进行描述。
*
较新的clone3()系统调用。

在此页面的其余部分,当注意到适用于所有这些接口的详细信息时,将使用术语"克隆调用",

The clone() wrapper function

当使用clone()包装函数创建子进程时,它将通过调用参数fn所指向的函数开始执行。 (这与fork(2)不同,在fork(2)中,执行从fork(2)调用开始在子级中继续。)arg参数作为函数fn的参数传递。

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

stack参数指定子进程使用的堆栈的位置。由于子进程和调用进程可能共享内存,因此子进程不可能与调用进程在同一堆栈中执行。因此,调用过程必须为子堆栈设置内存空间,并将指向该空间的指针传递给clone()。堆栈在所有运行Linux的处理器(HP PA处理器除外)上向下增长,因此堆栈通常指向为子堆栈设置的内存空间的最高地址。注意,clone()没有提供一种方法,调用方可以通过该方法将堆栈区域的大小通知内核。

下面讨论了clone()的其余参数。

clone3()

clone3()系统调用提供了较旧的clone()接口功能的超集。它还提供了许多API改进,包括:附加标志位的空间;运用各种论点进行更清洁的分离;以及指定子堆栈区域大小的功能。

fork(2)一样,clone3()返回父级和子级。它在子进程中返回0,并在父进程中返回子进程的PID。

clone3()的cl_args参数是以下形式的结构:

struct clone_args {
    u64 flags;        /* Flags bit mask */
    u64 pidfd;        /* Where to store PID file descriptor
                         (pid_t *) */
    u64 child_tid;    /* Where to store child TID,
                         in child's memory (pid_t *) */
    u64 parent_tid;   /* Where to store child TID,
                         in parent's memory (int *) */
    u64 exit_signal;  /* Signal to deliver to parent on
                         child termination */
    u64 stack;        /* Pointer to lowest byte of stack */
    u64 stack_size;   /* Size of stack */
    u64 tls;          /* Location of new TLS */
    u64 set_tid;      /* Pointer to a pid_t array
                         (since Linux 5.5) */
    u64 set_tid_size; /* Number of elements in set_tid
                         (since Linux 5.5) */
    u64 cgroup;       /* File descriptor for target cgroup
                         of child (since Linux 5.7) */
};

提供给clone3()的size参数应初始化为此结构的大小。 (size参数的存在允许将来对clone_args结构进行扩展。)

子进程的堆栈是通过cl_args.stack(指向堆栈区域的最低字节)和cl_args.stack_size(以字节为单位)指定的。如果指定了CLONE_VM标志(见下文),则必须显式分配和指定堆栈。否则,可以将这两个字段指定为NULL和0,这将导致子级使用与父级相同的堆栈区域(在子级自己的虚拟地址空间中)。

cl_args参数中的其余字段将在下面讨论。

Equivalence between clone() and clone3() arguments

与较旧的clone()接口(其中的参数分别传递)不同,在较新的clone3()接口中,参数被打包到上述的clone_args结构中。这种结构允许通过clone()参数传递的信息的超集。

下表显示了clone()的参数与提供给clone3()的clone_args参数中的字段之间的等效关系:

clone()clone3()注释
cl_args字段
flags和ti0xffflags对于大多数标志; 详细信息
parent_tidpidfd请参阅CLONE_PIDFD
child_tidchild_tid请参阅CLONE_CHILD_SETTID
parent_tidparent_tid请参阅CLONE_PARENT_SETTID
标志和0xff退出信号
堆栈堆栈
---stack_size
tlstls请参阅CLONE_SETTLS
---set_tid详细信息,请参见下文
---set_tid_size
---cgroup请参阅CLONE_INTO_CGROUP

The child termination signal

当子进程终止时,可以将信号发送给父进程。终止信号在标志的低字节(clone())或cl_args.exit_signal(clone3())中指定。如果将此信号指定为SIGCHLD以外的任何其他信号,则在使用wait(2)等待子级时,父进程必须指定__WALL或__WCLONE选项。如果未指定任何信号(即零),则在子进程终止时不发信号通知父进程。

The set_tid array

默认情况下,内核会在存在该进程的每个PID名称空间中为新进程选择下一个顺序PID。使用clone3()创建进程时,可以使用set_tid数组(从Linux 5.5开始可用)在存在该进程的某些或所有PID名称空间中为该进程选择特定的PID。如果仅应为当前PID名称空间或新创建的PID名称空间设置新创建进程的PID(如果标志包含CLONE_NEWPID),则set_tid数组中的第一个元素必须是所需的PID,set_tid_size必须为1 。

如果新创建的进程的PID在多个PID名称空间中应具有某个值,则set_tid数组可以具有多个条目。第一个条目在嵌套最深的PID名称空间中定义PID,随后的每个条目在相应的祖先PID名称空间中包含PID。应该在其中设置PID的PID名称空间的数量由set_tid_size定义,该值不能大于当前嵌套的PID名称空间的数量。

要在PID名称空间层次结构中使用以下PID创建进程:

PID NS级别请求的PID注释
031496最外层PID名称空间
142
27最里面的PID名称空间

将数组设置为:

set_tid[0] = 7;
set_tid[1] = 42;
set_tid[2] = 31496;
set_tid_size = 3;

如果仅需要指定两个最里面的PID名称空间中的PID,请将数组设置为:

set_tid[0] = 7;
set_tid[1] = 42;
set_tid_size = 2;

将在两个最里面的PID名称空间之外的PID名称空间中选择PID,方法与选择任何其他PID相同。

set_tid功能在目标PID名称空间的所有拥有用户名称空间中需要CAP_SYS_ADMIN。

如果给定PID名称空间中已经存在初始化进程(即PID为1的进程),则调用者只能选择大于1的PID。否则,此PID名称空间的PID条目必须为1。

The flags mask

clone()和clone3()都允许使用标志位掩码,以修改其行为,并允许调用方指定在调用进程和子进程之间共享的内容。该位掩码-将clone()的flags参数或传递给clone3()的cl_args.flags字段-在本页面的其余部分中称为标志掩码。

标志掩码指定为以下所列常数的零或更大的按位或。除非如下所述,否则这些标志在clone()和clone3()中均可用(并具有相同的作用)。

CLONE_CHILD_CLEARTID(since Linux 2.5.49)
子项退出时,清除子内存中child_tid(clone())或cl_args.child_tid(clone3())指向的位置的子线程ID(零),并在该地址的futex上进行唤醒。可以通过set_tid_address(2)系统调用来更改所涉及的地址。线程库使用它。
CLONE_CHILD_SETTID(since Linux 2.5.49)
将子线程ID存储在子内存中child_tid(clone())或cl_args.child_tid(clone3())指向的位置。在克隆调用将控制权返回给子进程中的用户空间之前,存储操作完成。 (请注意,克隆操作在父进程中返回之前,存储操作可能尚未完成,如果还使用了CLONE_VM标志,这将是相关的。)
CLONE_CLEAR_SIGHAND(since Linux 5.5)
默认情况下,子线程中的信号处理与父线程中的相同。如果指定了此标志,那么在父级中处理的所有信号都将重置为其子级中的默认配置(SIG_DFL)。
将此标志与CLONE_SIGHAND一起指定是荒谬的,也是不允许的。
CLONE_DETACHED(historical)
有一段时间(在Linux 2.5开发系列中)有一个CLONE_DETACHED标志,该标志导致父级在子级终止时没有收到信号。最终,此标志的作用包含在CLONE_THREAD标志下,并且到Linux 2.6.0发行时,该标志已无效。从Linux 2.6.2开始,不再需要将此标志与CLONE_THREAD一起使用。
这个标志仍然被定义,但是在调用clone()时通常会被忽略。但是,请参阅CLONE_PIDFD的说明以了解某些例外情况。
CLONE_FILES(since Linux 2.0)
如果设置了CLONE_FILES,则调用进程和子进程共享相同的文件描述符表。由调用进程或子进程创建的任何文件描述符在其他进程中也有效。同样,如果其中一个进程关闭文件描述符或更改其关联标志(使用fcntl(2)F_SETFD操作),则另一个进程也会受到影响。如果共享文件描述符表的进程调用execve(2),则其文件描述符表将被复制(未共享)。
如果未设置CLONE_FILES,则子进程将继承克隆调用时在调用进程中打开的所有文件描述符的副本。由调用进程或子进程执行的打开或关闭文件描述符或更改文件描述符标志的后续操作不会影响其他进程。但是请注意,子级中重复的文件描述符与调用过程中的相应文件描述符引用相同的打开文件描述,因此共享文件偏移量和文件状态标志(请参见open(2))。
CLONE_FS(since Linux 2.0)
如果设置了CLONE_FS,则调用方和子进程共享相同的文件系统信息。这包括文件系统的根目录,当前的工作目录和umask。调用进程或子进程对chroot(2),chdir(2)或umask(2)的任何调用也会影响另一个进程。
如果未设置CLONE_FS,则子进程在克隆调用时将处理调用进程的文件系统信息的副本。一个进程稍后执行的对chroot(2),chdir(2)或umask(2)的调用不会影响另一个进程。
CLONE_INTO_CGROUP(since Linux 5.7)
默认情况下,子进程与其父进程位于同一版本2 cgroup中。 CLONE_INTO_CGROUP标志允许在另一个版本2 cgroup中创建子进程。 (请注意,CLONE_INTO_CGROUP仅对版本2 cgroup有效。)
为了将子进程放置在不同的cgroup中,调用者在cl_args.flags中指定CLONE_INTO_CGROUP,并在cl_args.cgroup字段中传递引用版本2 cgroup的文件描述符。 (可以使用O_RDONLY或O_PATH标志打开cgroup v2目录来获取此文件描述符。)请注意,将进程放入版本2 cgroup中的所有通常限制(在cgroups(7)中进行了描述)都适用。
Among the possible use cases for CLONE_INTO_CGROUP

are the following:

*
将进程生成到与父级cgroup不同的cgroup中,使服务管理器可以直接将新服务生成为专用cgroup。这样就消除了如果子进程首先在与父进程相同的cgroup中创建然后移入目标cgroup时将引起的计费抖动。此外,直接将子进程生成到目标cgroup中要比在创建子进程后将其移动到目标cgroup中便宜得多。
*
CLONE_INTO_CGROUP标志还允许通过将冻结的子进程生成为冻结的cgroup来创建冻结的子进程。 (有关冷冻机控制器的说明,请参阅cgroups(7)。)
*
对于线程应用程序(甚至使用cgroup限制单个线程的线程实现),可以在将每个线程直接产生到其目标cgroup之前建立固定的cgroup布局。
CLONE_IO(since Linux 2.6.25)
如果设置了CLONE_IO,则新进程与调用进程共享一个I / O上下文。如果未设置此标志,则新进程(与fork(2)一样)具有其自己的I / O上下文。
I / O上下文是磁盘调度程序的I / O范围(即,I / O调度程序用于对进程的I / O进行调度的模型)。如果进程共享相同的I / O上下文,则I / O调度程序会将它们视为一个。结果,他们可以共享磁盘时间。对于某些I / O调度程序,如果两个进程共享一个I / O上下文,则将允许它们交错访问其磁盘。如果多个线程代表同一进程进行I / O(例如aio_read(3)),则它们应使用CLONE_IO以获得更好的I / O性能。
如果未使用CONFIG_BLOCK选项配置内核,则此标志为no-op。
CLONE_NEWCGROUP(since Linux 4.6)
在新的cgroup命名空间中创建进程。如果未设置此标志,则(与fork(2)一样)在与调用进程相同的cgroup命名空间中创建进程。
有关cgroup命名空间的更多信息,请参见cgroup_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)才能使用CLONE_NEWCGROUP。
CLONE_NEWIPC(since Linux 2.6.19)
如果设置了CLONE_NEWIPC,则在新的IPC名称空间中创建进程。如果未设置此标志,则(与fork(2)一样),将在与调用进程相同的IPC名称空间中创建该进程。
有关IPC名称空间的更多信息,请参见ipc_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)才能使用CLONE_NEWIPC。不能与CLONE_SYSVSEM一起指定此标志。
CLONE_NEWNET(since Linux 2.6.24)
(仅在内核版本2.6.29左右才完成此标志的实现。)
如果设置了CLONE_NEWNET,则在新的网络名称空间中创建进程。如果未设置此标志,则(与fork(2)一样)在与调用进程相同的网络名称空间中创建进程。
有关网络名称空间的更多信息,请参见network_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)才能使用CLONE_NEWNET。
CLONE_NEWNS(since Linux 2.4.19)
如果设置了CLONE_NEWNS,则克隆的子级将在新的装载名称空间中启动,并使用父级名称空间的副本进行初始化。如果未设置CLONE_NEWNS,则子级与父级位于相同的装载名称空间中。
有关安装名称空间的更多信息,请参见namespaces(7)和mount_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)才能使用CLONE_NEWNS。不允许在同一克隆调用中同时指定CLONE_NEWNS和CLONE_FS。
CLONE_NEWPID(since Linux 2.6.24)
如果设置了CLONE_NEWPID,则在新的PID名称空间中创建进程。如果未设置此标志,则(与fork(2)一样)在与调用进程相同的PID名称空间中创建进程。
有关PID名称空间的更多信息,请参见namespaces(7)和pid_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)可以使用CLONE_NEWPID。不能与CLONE_THREAD或CLONE_PARENT一起指定此标志。
CLONE_NEWUSER
(此标志首先对于Linux 2.6.23中的clone()有意义,当前的clone()语义已在Linux 3.5中合并,而使用户名称空间完全可用的最后部分已在Linux 3.8中合并。)
如果设置了CLONE_NEWUSER,则在新的用户名称空间中创建进程。如果未设置此标志,则(与fork(2)一样)在与调用进程相同的用户名称空间中创建进程。
有关用户名称空间的更多信息,请参见namespaces(7)和user_namespaces(7)。
在Linux 3.8之前,使用CLONE_NEWUSER要求调用者具有三个功能:CAP_SYS_ADMIN,CAP_SETUID和CAP_SETGID。从Linux 3.8开始,不需要特权就可以创建用户名称空间。
不能与CLONE_THREAD或CLONE_PARENT一起指定此标志。出于安全原因,不能将CLONE_NEWUSER与CLONE_FS一起指定。
CLONE_NEWUTS(since Linux 2.6.19)
如果设置了CLONE_NEWUTS,则在新的UTS命名空间中创建进程,该新的UTS命名空间是通过从调用进程的UTS命名空间复制标识符来初始化其标识符的。如果未设置此标志,则(与fork(2)一样)在与调用进程相同的UTS名称空间中创建进程。
有关UTS名称空间的更多信息,请参见uts_namespaces(7)。
只有特权进程(CAP_SYS_ADMIN)才能使用CLONE_NEWUTS。
CLONE_PARENT(since Linux 2.3.12)
如果设置了CLONE_PARENT,则新子级的父级(由getppid(2)返回)将与调用进程的父级相同。
如果未设置CLONE_PARENT,则(与fork(2)一样)子进程的父进程是调用进程。
请注意,它是父进程,由getppid(2)返回,在子进程终止时发出信号,因此,如果设置了CLONE_PARENT,则将发出信号通知调用进程的父进程,而不是调用进程本身。
全局init进程(初始PID名称空间中的PID 1)和其他PID名称空间中的init进程不能在克隆调用中使用CLONE_PARENT标志。此限制可防止在初始PID名称空间中创建多根进程树以及创建不可收割的僵尸。
CLONE_PARENT_SETTID(since Linux 2.5.49)
将子线程ID存储在父代内存中的parent_tid(clone())或cl_args.parent_tid(clone3())指向的位置。 (在Linux 2.5.32-2.5.48中,有一个标志CLONE_SETTID做到了这一点。)存储操作在克隆调用将控制权返回给用户空间之前完成。
CLONE_PID(Linux 2.0 to 2.5.15)
如果设置了CLONE_PID,则将使用与调用进程相同的进程ID创建子进程。这对于黑客入侵系统很有用,但在其他方面没有太大用处。从Linux 2.3.21开始,只能由系统引导过程(PID 0)指定此标志。该标志在Linux 2.5.16中从内核源完全消失了。随后,如果在标志掩码中指定了该位,内核将静默忽略该位。后来,该位被回收以用作CLONE_PIDFD标志。
CLONE_PIDFD(since Linux 5.2)

If this flag is specified,
a PID file descriptor referring to the child process is allocated
and placed at a specified location in the parent's memory.
The close-on-exec flag is set on this new file descriptor.
PID file descriptors can be used for the purposes described in
pidfd_open(2).

*
使用clone3()时,PID文件描述符放置在cl_args.pidfd指向的位置。
*
使用clone()时,PID文件描述符放置在parent_tid指向的位置。由于parent_tid参数用于返回PID文件描述符,因此在调用clone()时,CLONE_PIDFD不能与CLONE_PARENT_SETTID一起使用。
当前无法将此标志与CLONE_THREAD一起使用。这意味着由PID文件描述符标识的进程将始终是线程组负责人。
如果在调用clone()时在CLONE_PIDFD旁边指定了过时的CLONE_DETACHED标志,则返回错误。如果在调用clone3()时指定了CLONE_DETACHED,也会导致错误。此错误行为确保了将来可以将与CLONE_DETACHED相对应的位重新用于其他PID文件描述符功能。
CLONE_PTRACE(since Linux 2.2)
如果指定了CLONE_PTRACE,并且正在跟踪调用进程,则还要跟踪子级(请参阅ptrace(2))。
CLONE_SETTLS(since Linux 2.5.32)
tls(线程本地存储)描述符设置为tls。
对tls的解释及其产生的效果取决于体系结构。在x86上,tls解释为结构user_desc *(请参见set_thread_area(2))。在x86-64上,它是为%fs基址寄存器设置的新值(请参见arch_prctl(2)的ARCH_SET_FS参数)。在具有专用TLS寄存器的体系结构上,它是该寄存器的新值。
使用此标志需要详细的知识,通常除非在实现线程的库中使用,否则不应使用它。
CLONE_SIGHAND(since Linux 2.0)
如果设置了CLONE_SIGHAND,则调用进程和子进程共享相同的信号处理程序表。如果调用进程或子进程调用sigaction(2)来更改与信号关联的行为,则该行为在其他进程中也会更改。但是,调用进程和子进程仍具有不同的信号掩码和未决信号集。因此,其中一个可以使用sigprocmask(2)阻止或取消阻止信号,而不会影响其他过程。
如果未设置CLONE_SIGHAND,则子进程在克隆调用时将继承调用进程的信号处理程序的副本。一个进程稍后执行的对sigaction(2)的调用不会影响另一个进程。
从Linux 2.6.0开始,如果指定了CLONE_SIGHAND,则标志掩码还必须包括CLONE_VM。
CLONE_STOPPED(since Linux 2.6.0)
如果设置了CLONE_STOPPED,则该子级最初会停止(就像它已发送了SIGSTOP信号一样),并且必须通过向其发送SIGCONT信号来恢复。
从Linux 2.6.25开始不推荐使用此标志,并且在Linux 2.6.38中将其完全删除。从那时起,内核将无提示地无提示地忽略它。从Linux 4.6开始,CLONE_NEWCGROUP标志重用了同一位。
CLONE_SYSVSEM(since Linux 2.5.10)
如果设置了CLONE_SYSVSEM,则子进程和调用进程共享System V信号量调整(semadj)值的单个列表(请参阅semop(2))。在这种情况下,共享列表将在共享该列表的所有进程之间累积信号量值,并且仅当共享列表的最后一个进程终止(或使用unshare(2)停止共享列表)时才进行信号量调整。如果未设置此标志,则该子项具有一个单独的semadj列表,该列表最初为空。
CLONE_THREAD(since Linux 2.4.0)
如果设置了CLONE_THREAD,则将子级放置在与调用进程相同的线程组中。为了使CLONE_THREAD讨论的其余部分更具可读性,术语"线程"用于指代线程组中的进程。
线程组是Linux 2.4中添加的一项功能,用于支持共享单个PID的一组线程的POSIX线程概念。在内部,此共享PID是线程组的所谓线程组标识符(TGID)。从Linux 2.4开始,对getpid(2)的调用将返回调用者的TGID。
组中的线程可以通过其(系统范围内)唯一线程ID(TID)进行区分。新线程的TID可用作返回给调用方的函数结果,并且线程可以使用gettid(2)获得其自己的TID。
如果在未指定CLONE_THREAD的情况下进行克隆调用,则结果线程将被放置在新线程组中,该新线程组的TGID与线程的TID相同。该线程是新线程组的负责人。
使用CLONE_THREAD创建的新线程与进行克隆调用的进程具有相同的父进程(即类似CLONE_PARENT),因此对getppid(2)的调用将为线程组中的所有线程返回相同的值。当CLONE_THREAD线程终止时,不会向创建该线程的线程发送SIGCHLD(或其他终止)信号。也无法使用wait(2)获得此类线程的状态。 (据说该线程是分离的。)
线程组中的所有线程终止后,将向该线程组的父进程发送SIGCHLD(或其他终止)信号。
如果线程组中的任何线程执行execve(2),则将终止除线程组领导者之外的所有其他线程,并在线程组领导者中执行新程序。
如果线程组中的一个线程使用fork(2)创建了一个子代,则该组中的任何线程都可以等待(2)该子代。
从Linux 2.5.35开始,如果指定了CLONE_THREAD,则标志掩码还必须包含CLONE_SIGHAND(请注意,从Linux 2.6.0开始,CLONE_SIGHAND也要求包含CLONE_VM)。
信号的处理和操作是整个过程的:如果将未处理的信号传递给线程,则它将影响(终止,停止,继续,忽略其中的)线程组的所有成员。
每个线程都有自己的信号掩码,由sigprocmask(2)设置。
信号可以是过程控制或线程控制的。过程控制的信号以线程组(即TGID)为目标,并从不阻塞信号的线程中传递到任意选择的线程。信号可能是过程定向的,因为它是由内核出于硬件异常以外的原因生成的,或者是由于它是使用kill(2)或sigqueue(3)发送的。线程定向信号的目标是(即传递到)特定线程。信号可能是线程定向的,因为它是使用tgkill(2)或pthread_sigqueue(3)发送的,或者是因为该线程执行了触发硬件异常的机器语言指令(例如,无效的内存访问触发了SIGSEGV或浮点异常触发了SIGFPE)。
sigpending(2)的调用返回一个信号集,该信号集是未决的过程控制信号和调用线程的未决信号的并集。
如果将过程控制的信号传递给线程组,并且该线程组已安装了该信号的处理程序,则该处理程序将在线程组中一个未被阻塞的任意选择的成员中被调用。如果组中的多个线程正在等待使用sigwaitinfo(2)接受同一信号,则内核将随意选择这些线程之一来接收信号。
CLONE_UNTRACED(since Linux 2.5.46)
如果指定了CLONE_UNTRACED,则跟踪进程无法在此子进程上强制CLONE_PTRACE。
CLONE_VFORK(since Linux 2.2)
如果设置了CLONE_VFORK,则调用过程的执行将被挂起,直到子代通过对execve(2)或_exit(2)的调用释放其虚拟内存资源(与vfork(2)一样)。
如果未设置CLONE_VFORK,则在调用之后可以同时调度调用进程和子进程,并且应用程序不应依赖于以任何特定顺序进行的执行。
CLONE_VM(since Linux 2.0)
如果设置了CLONE_VM,则调用进程和子进程在同一内存空间中运行。特别地,由调用过程或子过程执行的存储器写操作在其他过程中也是可见的。此外,子进程或调用进程使用mmap(2)或munmap(2)执行的任何内存映射或取消映射也会影响其他进程。
如果未设置CLONE_VM,则子进程在克隆调用时将在调用进程的内存空间的单独副本中运行。像fork(2)一样,由一个进程执行的内存写入或文件映射/取消映射不会影响另一个进程。

备注

这些系统调用的一种用途是实现线程:程序中的多个控制流在共享地址空间中同时运行。

Glibc不提供clone3()的包装;使用syscall(2)调用它。

请注意,在调用clone()系统调用之前,glibc clone()包装函数会对堆栈指向的内存进行一些更改(为子项正确设置堆栈所需的更改)。因此,在使用clone()递归创建子代的情况下,请勿将用于父代堆栈的缓冲区用作子代的堆栈。

kcmp(2)系统调用可用于测试两个进程是否共享各种资源,例如文件描述符表,System V信号量撤消操作或虚拟地址空间。

在克隆调用期间不会执行使用pthread_atfork(3)注册的处理程序。

在Linux 2.4.x系列中,CLONE_THREAD通常不会使新线程的父代与调用进程的父代相同。但是,对于2.4.7至2.4.18内核版本,CLONE_THREAD标志隐含了CLONE_PARENT标志(与Linux 2.6.0及更高版本一样)。

在i386上,不应通过vsyscall调用clone(),而应直接通过int $ 0x80进行调用。

C library/kernel differences

原始clone()系统调用与fork(2)更为接近,因为子级中的执行从调用点开始继续。这样,将忽略clone()包装函数的fn和arg参数。

与glibc包装器相比,原始clone()系统调用接受NULL作为堆栈参数(而clone3()同样允许cl_args.stack为NULL)。在这种情况下,孩子使用父母的堆栈的副本。 (写时复制的语义确保当任一进程修改堆栈时,子进程都获得堆栈页面的单独副本。)在这种情况下,为了正确操作,不应指定CLONE_VM选项。 (如果由于使用CLONE_VM标志而使孩子共享了父母的内存,则不会发生写时复制复制,并且很可能导致混乱。)

参数的顺序在原始系统调用中也有所不同,并且各体系结构中的参数也有所不同,如以下各段所述。

x86-64和其他一些体系结构(包括sh,tile和alpha)上的原始系统调用接口为:

long clone(unsigned long flags, void *stack,
           int *parent_tid, int *child_tid,
           unsigned long tls);

在x86-32和其他几种常见体系结构(包括score,ARM,ARM 64,PA-RISC,arc,Power PC,xtensa和MIPS)上,最后两个参数的顺序相反:

long clone(unsigned long flags, void *stack,
          int *parent_tid, unsigned long tls,
          int *child_tid);

在cris和s390架构上,前两个参数的顺序相反:

long clone(void *stack, unsigned long flags,
           int *parent_tid, int *child_tid,
           unsigned long tls);

在微火焰架构上,提供了一个附加参数:

long clone(unsigned long flags, void *stack,
           int stack_size,         /* Size of stack */
           int *parent_tid, int *child_tid,
           unsigned long tls);

blackfin, m68k, and sparc

blackfin,m68k和sparc上的参数传递约定与上面的描述不同。有关详细信息,请参见内核(和glibc)源。

ia64

在ia64上,使用了不同的接口:

int __clone2(int (*fn)(void *),
             void *stack_base, size_t stack_size,
             int flags, void *arg, ...
          /* pid_t *parent_tid, struct user_desc *tls,
             pid_t *child_tid */ );

上面显示的原型用于glibc包装器函数;对于系统调用本身,原型可以描述如下(与microblaze上的clone()原型相同):

long clone2(unsigned long flags, void *stack_base,
            int stack_size,         /* Size of stack */
            int *parent_tid, int *child_tid,
            unsigned long tls);

__clone2()的操作与clone()相同,不同之处在于stack_base指向子堆栈区域的最低地址,而stack_size指定stack_base指向的堆栈大小。

Linux 2.4 and earlier

在Linux 2.4和更早版本中,clone()不接受参数parent_tid,tls和child_tid。

BUGS

GNU C库2.3.4之前的版本(包括2.24)包含用于getpid(2)的包装函数,该函数执行PID的缓存。这种缓存依赖于glibc包装器对clone()的支持,但是实现的局限性意味着在某些情况下缓存不是最新的。特别是,如果在clone()调用之后立即将信号传递给子级,则在调用该信号的处理程序中对getpid(2)的调用可能返回调用进程的PID("父级"),如果克隆包装程序尚未有机会更新子级中的PID缓存。 (此讨论忽略了使用CLONE_THREAD创建子项的情况,此时getpid(2)应该在子项中以及在调用clone()的过程中返回相同的值,因为调用者和子项位于同一线程组中。如果flags参数包含CLONE_VM,也不会发生过时的缓存问题。)要获得真实的信息,有时有必要使用如下代码:

#include <syscall.h>

pid_t mypid;

mypid = syscall(SYS_getpid);

由于过时的缓存问题以及getpid(2)中指出的其他问题,glibc 2.25中删除了PID缓存功能。

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/

示例

以下程序演示了如何使用clone()创建在单独的UTS名称空间中执行的子进程。子级在其UTS名称空间中更改主机名。然后,父级和子级都显示系统主机名,从而可以看到父级和子级的UTS名称空间中的主机名不同。有关此程序的使用示例,请参见setns(2)。

在示例程序中,出于以下原因,我们使用mmap(2)而不是malloc(3)分配了用于子堆栈的内存:

*
mmap(2)分配一个内存块,该内存块从页面边界开始,并且是页面大小的倍数。如果我们想使用mprotect(2)在堆栈的末尾建立一个保护页面(一个带有PROT_NONE保护的页面),这将很有用。
*
我们可以指定MAP_STACK标志来请求适合于堆栈的映射。目前,该标志在Linux上是禁止操作的,但是它存在并且在其他一些系统上也有作用,因此我们应该将其包括在内以实现可移植性。

Program source

#define _GNU_SOURCE
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

static int              /* Start function for cloned child */
childFunc(void *arg)
{
    struct utsname uts;

    /* Change hostname in UTS namespace of child */

    if (sethostname(arg, strlen(arg)) == -1)
        errExit("sethostname");

    /* Retrieve and display hostname */

    if (uname(&uts) == -1)
        errExit("uname");
    printf("uts.nodename in child:  %s\n", uts.nodename);

    /* Keep the namespace open for a while, by sleeping.
       This allows some experimentation--for example, another
       process might join the namespace. */

    sleep(200);

    return 0;           /* Child terminates now */
}

#define STACK_SIZE (1024 * 1024)    /* Stack size for cloned child */

int
main(int argc, char *argv[])
{
    char *stack;                    /* Start of stack buffer */
    char *stackTop;                 /* End of stack buffer */
    pid_t pid;
    struct utsname uts;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
        exit(EXIT_SUCCESS);
    }

    /* Allocate memory to be used for the stack of the child */

    stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
    if (stack == MAP_FAILED)
        errExit("mmap");

    stackTop = stack + STACK_SIZE;  /* Assume stack grows downward */

    /* Create child that has its own UTS namespace;
       child commences execution in childFunc() */

    pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);
    if (pid == -1)
        errExit("clone");
    printf("clone() returned %ld\n", (long) pid);

    /* Parent falls through to here */

    sleep(1);           /* Give child time to change its hostname */

    /* Display hostname in parentaqs UTS namespace. This will be
       different from hostname in childaqs UTS namespace. */

    if (uname(&uts) == -1)
        errExit("uname");
    printf("uts.nodename in parent: %s\n", uts.nodename);

    if (waitpid(pid, NULL, 0) == -1)    /* Wait for child */
        errExit("waitpid");
    printf("child has terminated\n");

    exit(EXIT_SUCCESS);
}

名称

clone,__ clone2,clone3-创建子进程

另外参见

fork(2),futex(2),getpid(2),gettid(2),kcmp(2),mmap(2),pidfd_open(2),set_thread_area(2),set_tid_address(2),setns(2), tkill(2),unshare(2),wait(2),功能(7),名称空间(7),pthreads(7)

错误说明

EAGAIN
已经有太多进程在运行;参见fork(2)。
EBUSY(clone3() only)
在cl_args.flags中指定了CLONE_INTO_CGROUP,但在cl_args.cgroup中指定的文件描述符是指在其中启用了域控制器的版本2 cgroup。
EEXIST(clone3() only)
set_tid中指定的一个(或多个)PID已存在于相应的PID名称空间中。
EINVAL
在标志掩码中指定了CLONE_SIGHAND和CLONE_CLEAR_SIGHAND。
EINVAL
在标志掩码中指定了CLONE_SIGHAND,但未指定CLONE_VM。 (从Linux 2.6.0开始。)
EINVAL
在标志掩码中指定了CLONE_THREAD,但没有指定CLONE_SIGHAND。 (从Linux 2.5.35开始。)
EINVAL
在标志掩码中指定了CLONE_THREAD,但是当前进程以前使用CLONE_NEWPID标志调用了unshare(2)或使用setns(2)将其自身与PID名称空间重新关联。
EINVAL
在标志掩码中指定了CLONE_FS和CLONE_NEWNS。
EINVAL(since Linux 3.9)
在标志掩码中指定了CLONE_NEWUSER和CLONE_FS。
EINVAL
在标志掩码中指定了CLONE_NEWIPC和CLONE_SYSVSEM。
EINVAL
在标志掩码中指定了CLONE_NEWPID或CLONE_NEWUSER中的一个(或两个)和CLONE_THREAD或CLONE_PARENT中的一个(或两个)。
EINVAL(since Linux 2.6.32)
指定了CLONE_PARENT,并且调用方是一个初始化进程。
EINVAL
当fn或stack被指定为NULL时,由glibc clone()包装函数返回。
EINVAL
在标志掩码中指定了CLONE_NEWIPC,但未使用CONFIG_SYSVIPC和CONFIG_IPC_NS选项配置内核。
EINVAL
在标志掩码中指定了CLONE_NEWNET,但未使用CONFIG_NET_NS选项配置内核。
EINVAL
在标志掩码中指定了CLONE_NEWPID,但未使用CONFIG_PID_NS选项配置内核。
EINVAL
在标志掩码中指定了CLONE_NEWUSER,但是未使用CONFIG_USER_NS选项配置内核。
EINVAL
在标志掩码中指定了CLONE_NEWUTS,但未使用CONFIG_UTS_NS选项配置内核。
EINVAL
堆栈未与该体系结构的合适边界对齐。例如,在aarch64上,堆栈必须为16的倍数。
EINVAL(clone3() only)
在标志掩码中指定了CLONE_DETACHED。
EINVAL(clone() only)
在标志掩码中与CLONE_DETACHED一起指定了CLONE_PIDFD。
EINVAL
在标志掩码中指定了CLONE_PIDFD和CLONE_THREAD。
EINVAL (clone() only)
在标志掩码中指定了CLONE_PIDFD和CLONE_PARENT_SETTID。
EINVAL(clone3() only)
set_tid_size大于嵌套的PID名称空间的数量。
EINVAL(clone3() only)
set_tid中指定的PID之一无效。
EINVAL(AArch64 only, Linux 4.6 and earlier)
堆栈未与126位边界对齐。
ENOMEM
无法分配足够的内存来为子级分配任务结构,或复制调用方上下文中需要复制的那些部分。
ENOSPC(since Linux 3.7)
在标志掩码中指定了CLONE_NEWPID,但PID命名空间的嵌套深度限制已被超出。参见pid_namespaces(7)。
ENOSPC(since Linux 4.9; beforehand EUSERS)
在标志掩码中指定了CLONE_NEWUSER,该调用将导致嵌套用户名称空间的数量超出限制。参见user_namespaces(7)。
从Linux 3.11到Linux 4.8,在这种情况下诊断的错误是EUSERS。
ENOSPC(since Linux 4.9)
标志掩码中的值之一指定了新用户名称空间的创建,但是这样做将导致超出/ proc / sys / user中相应文件定义的限制。有关更多详细信息,请参见namespaces(7)。
EOPNOTSUPP(clone3() only)
在cl_args.flags中指定了CLONE_INTO_CGROUP,但是在cl_args.cgroup中指定的文件描述符引用了处于域无效状态的版本2 cgroup。
EPERM
CLONE_NEWCGROUPCLONE_NEWIPCCLONE_NEWNETCLONE_NEWNS,CLONE_NEWPID或CLONE_NEWUTS由未特权的进程(没有CAP_SYS_ADMIN的进程)指定。
EPERM
CLONE_PID由进程0以外的进程指定。(此错误仅在Linux 2.5.15和更早版本上发生。)
EPERM
在标志掩码中指定了CLONE_NEWUSER,但是调用者的有效用户ID或有效组ID在父名称空间中没有映射(请参阅user_namespaces(7))。
EPERM(since Linux 3.9)
在标志掩码中指定了CLONE_NEWUSER,并且调用者处于chroot环境中(即,调用者的根目录与它所在的安装名称空间的根目录不匹配)。
EPERM(clone3() only)
set_tid_size大于零,并且调用者在拥有相应PID命名空间的一个或多个用户命名空间中缺少CAP_SYS_ADMIN功能。
ERESTARTNOINTR(since Linux 2.6.17)
系统调用已被信号中断,将重新启动。 (这只能在跟踪期间看到。)
EUSERS(Linux 3.11 to Linux 4.8)
在标志掩码中指定了CLONE_NEWUSER,并且将超出嵌套用户名称空间数量的限制。请参阅上面有关ENOSPC错误的讨论。

遵循规范

这些系统调用是特定于Linux的,不应在可移植的程序中使用。

返回值

成功后,子进程的线程ID将在调用者的执行线程中返回。失败时,将在调用者的上下文中返回-1,不会创建任何子进程,并且将正确设置errno。

日期:2019-08-20 17:58:36 来源:oir作者:oir