$RodHat_
Syscall reference · Processes

clone3(2)

Fork a child process using a struct-based API; write a pidfd atomically with CLONE_PIDFD.

Since
Linux 5.3check your kernel →
glibc
none (use syscall(2))
System
Linux
Man page
clone3(2)

Synopsis

#include <linux/sched.h>
#include <sys/syscall.h>
#include <unistd.h>

long syscall(SYS_clone3, struct clone_args *args, size_t size);

Rod's take

clone(2) has been there since Linux 2.0 and the calling convention is different on x86-64 and ARM, documented in a footnote everyone ignores until production burns. clone3 replaced the whole mess with a struct: zero-fill it, set what you need, pass sizeof. The one flag worth the upgrade by itself is CLONE_PIDFD: the kernel writes a pidfd to args.pidfd before returning in the parent, so there is no window between fork and pidfd_open where the child can exit, get reaped, and hand the PID to a stranger. CLONE_CLEAR_SIGHAND is the other one you probably want: inherited signal handlers in a worker subprocess are a quiet source of misbehavior that fork never warned you about. Handle ENOSYS and fall back to fork on old kernels. Everything else is just less architecture-specific footgunning.

Flags and arguments that matter

NameWhat it does
CLONE_PIDFDWrite a pidfd to args.pidfd on return in the parent; the handle is written before any return, eliminating the PID-reuse race of fork+pidfd_open.
CLONE_CLEAR_SIGHANDLinux 5.3Reset all signal handlers to SIG_DFL in the child; SIG_IGN handlers are not touched (they follow exec semantics).
CLONE_INTO_CGROUPLinux 5.7Place the child in the cgroup v2 specified by args.cgroup (a cgroup fd) from the first instruction; no fork-then-move race.
set_tid / set_tid_sizeLinux 5.5Assign a specific TID in each namespace level listed in args.set_tid. Used by checkpoint/restore tools (CRIU). Requires CAP_SYS_ADMIN in each namespace.
struct clone_args sizeThird argument to clone3; the version number. Zero-fill the struct and pass sizeof(struct clone_args). Unknown non-zero fields → E2BIG.

Errors you will actually hit

errnoMeaning
ENOSYSKernel is older than 5.3. Fall back to fork(2) or clone(2).
E2BIGsize is larger than the kernel knows and a field beyond the known size is non-zero.
EINVALInconsistent or unknown flags, bad stack/size combination, or invalid pidfd pointer.
ENOMEMInsufficient kernel memory for the new task.
EPERMCLONE_INTO_CGROUP requires CAP_SYS_ADMIN; some flag combinations require privilege.

Example

#include <linux/sched.h>
#include <sys/syscall.h>
#include <unistd.h>

int child_pidfd = -1;
struct clone_args args = {
    .flags      = CLONE_PIDFD | CLONE_CLEAR_SIGHAND,
    .pidfd      = (uint64_t)(uintptr_t)&child_pidfd,
    .exit_signal = SIGCHLD,
};
pid_t pid = syscall(SYS_clone3, &args, sizeof(args));
if (pid < 0 && errno == ENOSYS) {
    pid = fork();           /* kernel < 5.3 */
    if (pid > 0)
        child_pidfd = syscall(SYS_pidfd_open, pid, 0);
} else if (pid < 0) {
    err(1, "clone3");
}
if (pid == 0) {
    execvp(argv[0], argv); /* child */
    err(127, "exec");
}

Trimmed from the full walkthrough in clone3: fork a process and get a stable handle in the same syscall.

BSD equivalent

FreeBSD: pdfork(2) (FreeBSD 9.0) writes a process descriptor on fork in a single call; pdkill(2), pdwait4(2), and EVFILT_PROCDESC in kqueue(2) complete the API. OpenBSD: none.

The long version, from the console

Sources

  1. clone3(2), man7.org
  2. pdfork(2), FreeBSD

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.