Layer 0 Spec — process Family, CPU Affinity (sched_setaffinity)
Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS). Extension
of the process family.
Position
This document specifies the CPU affinity primitive of layer 0 — pinning a
task to a subset of logical CPUs. Gap identified while specifying layer 1:
air-thread::cpu_affinity needs it and no
sched_setaffinity/sched_getaffinity wrapper existed.
Placement: process family. Affinity is a property of a task’s
scheduling (identified by a Tid); it therefore lives alongside gettid,
clone3, waitid, setpgid… in air-sys-syscall::process. The mask
reuses CpuSet (already defined in air-sys-types::system in Temps 3a for
IORING_REGISTER_IOWQ_AFF) — a shared type, not recreated. (Rejected
alternative: the system family — but system covers machine information
(uname, sysinfo, entropy), not the scheduling of a specific task.)
Emergence (doc-first method). Like inotify, this gap was revealed by the specification of layer 1 — consuming layer 0 surfaces its holes. No prior spec: this document is produced before the implementation.
Surface
#![allow(unused)]
fn main() {
/// `sched_setaffinity(2)` — sets a task's CPU affinity: it will only be
/// scheduled on the CPUs present in `cpus`. `tid = None` = calling task
/// (kernel sentinel `0` typed as `Option`, ADR-021 conv. 1).
pub fn set_cpu_affinity(tid: Option<Tid>, cpus: &CpuSet) -> Result<(), Errno>;
/// `sched_getaffinity(2)` — reads the current affinity **into** `cpus` (buffer
/// supplied by the caller, zero allocation). `tid = None` = calling task.
pub fn get_cpu_affinity(tid: Option<Tid>, cpus: &mut CpuSet) -> Result<(), Errno>;
}
CpuSetreused (air-sys-types::system,CPU_SETSIZE = 1024bits / 128 bytes,cpu_set_tlayout):new/set/clear/contains/count/is_empty/as_bytes.get_cpu_affinitywrites into the mask via a pointer to the&mut CpuSet(#[repr(C)]without padding ⇒ every byte pattern valid) — no new accessor added to the type.None= calling task: translated topid = 0(never exposed as a magic integer — ADR-021 conv. 1).- EINTR:
sched_*affinityare not interruptible; no hidden retry (ADR-021 conv. 2 remains the general rule).
Underlying syscalls (verified against uapi 6.12, per arch)
| Syscall | x86_64 | aarch64 |
|---|---|---|
sched_setaffinity | 203 | 122 |
sched_getaffinity | 204 | 123 |
set: the kernel reads len bytes of the mask (asm readonly). get: the
kernel writes into the mask (asm without readonly) and returns the
number of bytes filled (≥ 0) or -errno. The size passed is
size_of::<CpuSet>() (128 bytes) ≥ the kernel size of cpumask — the kernel
fills what it has and leaves the rest at zero.
Preconditions / errors
set_cpu_affinity:- [
Errno::EINVAL]:cpuscontains no allowed / online CPU. - [
Errno::ESRCH]: no task bears thistid. - [
Errno::EPERM]: insufficient privileges (changing the affinity of a task belonging to another user, or a cpuset cgroup constraint).
- [
get_cpu_affinity:- [
Errno::ESRCH]: nonexistenttid. - [
Errno::EINVAL]: inconsistent mask size — does not occur with aCpuSetofCPU_SETSIZEbits (always ≥ the kernel size).
- [
Tests
- Round-trip (real):
getthe current affinity (count ≥ 1),setto a subset (CPU 0, always online),getand verifycontains(0)+count == 1, then restore the original affinity. NonevsSome(tid): both target the calling task (Some(gettid())).- Errors: empty mask →
EINVAL; nonexistenttid→ESRCH. - Cross-arch: numbers verified x86_64 + aarch64; tests run on both.
- Coverage 100 % lines + branches (the real
Ok/Errpaths cover both arms;affinity_pidNone/Somecovered); no expected exception.
Core decisions
- Affinity in
process(scheduling of a task), mask shared fromsystem::CpuSet— no type duplication. None= calling task (pid = 0typed), compliant with ADR-021 conv. 1.- Buffer supplied, zero allocation for
get(the caller owns theCpuSet). - Primitive unblocking layer 1 (
air-thread::cpu_affinity).
Document license: MPL 2.0
Status: Technical specification of CPU affinity (sched_set/getaffinity),
extension of the process family (layer 0), target kernel 6.12 LTS.