$RodHat_
Syscall reference · Scheduling

rseq(2)

Register a per-thread struct that the kernel keeps updated with the current CPU ID, enabling lock-free per-CPU data updates via restartable critical sections.

Since
Linux 4.18check your kernel →
glibc
none (use syscall(2)); glibc 2.35+ registers automatically and exposes __rseq_offset/__rseq_size
System
Linux
Man page
rseq(2)

Synopsis

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

long syscall(SYS_rseq, struct rseq *rseq, uint32_t rseq_len,
             int flags, uint32_t sig);

Rod's take

glibc 2.35 has been registering rseq in every thread you spawn since March 2022 and most people have no idea. The kernel updates cpu_id on every context switch; reading it costs one load. The critical-section mechanism is genuinely clever: you define a code window, and if you get preempted inside it, the kernel jumps you to your abort handler rather than letting a half-done per-CPU write commit. Fast path is pure userspace, slow path is rare. musl doesn't use it, which is fine until you're writing an allocator. If you are, the infrastructure is already in your threads.

Flags and arguments that matter

NameWhat it does
RSEQ_FLAG_UNREGISTERUnregister the previously registered rseq struct for this thread.
struct rseq.cpu_idKernel-maintained current CPU ID; RSEQ_CPU_ID_UNINITIALIZED (-1) before registration, RSEQ_CPU_ID_REGISTRATION_FAILED (-2) on failure.
struct rseq.rseq_csPointer to an active struct rseq_cs critical section descriptor. If set when the kernel migrates or preempts the thread, execution jumps to rseq_cs.abort_ip and rseq_cs is cleared.
struct rseq_cs.start_ip / post_commit_offsetDefine the critical-section code window [start_ip, start_ip + post_commit_offset). Preemption inside this window triggers the abort.
struct rseq_cs.abort_ipInstruction to jump to on abort. The 4 bytes immediately before this address must equal the sig passed to sys_rseq.

Errors you will actually hit

errnoMeaning
EINVALrseq_len is not sizeof(struct rseq), or flags has unknown bits set (without RSEQ_FLAG_UNREGISTER).
EBUSYThread already has a registered rseq struct; unregister with RSEQ_FLAG_UNREGISTER first.
EFAULTrseq pointer is NULL or not readable/writable by the process.
EPERMRSEQ_FLAG_UNREGISTER: sig does not match the sig used at registration.

Example

/* read current CPU without a syscall (glibc 2.35+) */
#include <sys/rseq.h>
#include <pthread.h>

struct rseq *r = (struct rseq *)((char *)pthread_self() + __rseq_offset);
int cpu = r->cpu_id;

/* manual registration on older glibc or musl */
#include <linux/rseq.h>
#include <sys/syscall.h>
#define RSEQ_SIG 0x53053053   /* x86-64 */
static __thread struct rseq rseq_abi = { .cpu_id = RSEQ_CPU_ID_UNINITIALIZED };
syscall(SYS_rseq, &rseq_abi, sizeof(rseq_abi), 0, RSEQ_SIG);

Trimmed from the full walkthrough in rseq: per-CPU data without a kernel call.

BSD equivalent

No equivalent on FreeBSD, OpenBSD, or NetBSD. Per-CPU userspace data structures at this layer are Linux-specific. Portable alternative: sched_getcpu(3) on Linux, thread-local storage everywhere else.

The long version, from the console

Sources

  1. rseq(2), man7.org
  2. Linux restartable sequences, EfficiOS (Mathieu Desnoyers)

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