CLONE_INTO_CGROUP Places a Child in Its Target cgroup at Creation

A process created in one cgroup and moved to another has a short but real interval in the original cgroup. During that interval, accounting, resource controls, and freezer state come from the initial placement rather than the destination. Linux provides CLONE_INTO_CGROUP so clone3() can place the child in a cgroup v2 target as part of process creation.

This changes the placement boundary. Instead of creating a task and repairing its cgroup membership afterward, the caller identifies the destination before the child exists.

The target is represented by a file descriptor

CLONE_INTO_CGROUP uses the cgroup field in struct clone_args. The field contains a file descriptor referring to a cgroup v2 directory. The directory can be opened with O_RDONLY or O_PATH.

int cgfd = open("/sys/fs/cgroup/workload",
                O_RDONLY | O_DIRECTORY | O_CLOEXEC);

struct clone_args args = {
    .flags = CLONE_INTO_CGROUP,
    .cgroup = cgfd,
    .exit_signal = SIGCHLD,
};

pid_t pid = syscall(SYS_clone3, &args, sizeof(args));

The descriptor makes the target an already-resolved kernel object at the point of the call. The operation does not depend on re-resolving a pathname after child creation.

This is consistent with other Linux interfaces that use file descriptors to carry object identity across a system-call boundary. It does not remove cgroup permission checks: the usual cgroup v2 restrictions on placing a process into the target still apply.

Placement becomes part of creation

Without this flag, a child normally begins in the same cgroup v2 as its parent. A manager that needs another placement can write the child’s PID to the destination’s cgroup.procs after creation.

That sequence has two distinct state transitions:

create child in parent cgroup
            |
            v
child can exist under parent accounting
            |
            v
move child to target cgroup

With CLONE_INTO_CGROUP, the requested placement is attached to the creation operation:

open target cgroup
        |
        v
clone3(CLONE_INTO_CGROUP)
        |
        v
child exists in target cgroup

The distinction matters even if the later migration happens quickly. CPU time or other activity before migration belongs to the initial cgroup, and a manager has to coordinate a task that already exists while changing its membership. Direct placement removes that intermediate membership state.

Frozen cgroups change the child’s first runnable state

A target cgroup can be frozen through the cgroup v2 freezer interface. Creating a child directly into that target permits the child to be born into the frozen cgroup rather than running first and being moved into it later.

That property is useful for managers that need placement established before execution proceeds. It is different from creating a process, stopping it through a separate mechanism, moving it, and then resuming it. The cgroup state is part of the placement selected for creation.

The freezer remains a cgroup mechanism, not a new clone3() scheduling guarantee. CLONE_INTO_CGROUP selects the target; the target’s cgroup state determines the resulting constraint.

The flag does not bypass cgroup topology rules

Direct placement is not privileged migration hidden inside clone3(). The kernel still validates the target according to cgroup v2 rules. A caller cannot use the flag to place a child somewhere that the corresponding cgroup operation would forbid.

The target must be a cgroup v2 cgroup. The flag has no placement effect for a cgroup v1 hierarchy. This scope is important for software that supports hosts with different cgroup configurations: the presence of clone3() alone does not imply that a requested hierarchy can accept CLONE_INTO_CGROUP.

Error handling therefore belongs at the creation boundary. If the target descriptor or requested placement is invalid, process creation can fail instead of producing a child that a later migration step then has to clean up.

A creation-time cgroup is distinct from a cgroup namespace

CLONE_INTO_CGROUP and CLONE_NEWCGROUP control different state. The former selects cgroup membership for the new task. The latter creates a new cgroup namespace, changing the cgroup paths visible through namespace-aware interfaces.

A caller may care about one without needing the other. Service managers commonly need to place a process into a resource-control subtree while leaving its namespace arrangement unchanged. Container runtimes may combine placement with namespace creation, but those are separate dimensions in the clone3() request.

Keeping the distinction explicit prevents cgroup membership from being confused with the namespace that controls the process’s view of cgroup paths.

Creation-time placement narrows a lifecycle race

The main effect of CLONE_INTO_CGROUP is not a new resource controller. It moves an existing cgroup placement decision into the operation that creates the task.

For service managers and container runtimes, that removes a lifecycle gap between process existence and intended cgroup membership. Accounting starts in the selected target, a frozen target can constrain the child from its initial placement, and failure to establish the requested membership is reported at creation rather than after a separately visible child has appeared.

The boundary remains narrow: cgroup v2 policy, permissions, controller configuration, and namespace semantics still apply independently. The flag changes when membership is established, not the rules that govern that membership.