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
| Name | What it does |
|---|---|
| CLONE_PIDFD | Write 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.3 | Reset all signal handlers to SIG_DFL in the child; SIG_IGN handlers are not touched (they follow exec semantics). |
| CLONE_INTO_CGROUPLinux 5.7 | Place 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.5 | Assign 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 size | Third 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
| errno | Meaning |
|---|---|
| ENOSYS | Kernel is older than 5.3. Fall back to fork(2) or clone(2). |
| E2BIG | size is larger than the kernel knows and a field beyond the known size is non-zero. |
| EINVAL | Inconsistent or unknown flags, bad stack/size combination, or invalid pidfd pointer. |
| ENOMEM | Insufficient kernel memory for the new task. |
| EPERM | CLONE_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
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.
