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
| Name | What it does |
|---|---|
| RSEQ_FLAG_UNREGISTER | Unregister the previously registered rseq struct for this thread. |
| struct rseq.cpu_id | Kernel-maintained current CPU ID; RSEQ_CPU_ID_UNINITIALIZED (-1) before registration, RSEQ_CPU_ID_REGISTRATION_FAILED (-2) on failure. |
| struct rseq.rseq_cs | Pointer 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_offset | Define the critical-section code window [start_ip, start_ip + post_commit_offset). Preemption inside this window triggers the abort. |
| struct rseq_cs.abort_ip | Instruction 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
| errno | Meaning |
|---|---|
| EINVAL | rseq_len is not sizeof(struct rseq), or flags has unknown bits set (without RSEQ_FLAG_UNREGISTER). |
| EBUSY | Thread already has a registered rseq struct; unregister with RSEQ_FLAG_UNREGISTER first. |
| EFAULT | rseq pointer is NULL or not readable/writable by the process. |
| EPERM | RSEQ_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
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.
