# Restartable Sequences (rseq) 机制

## 1. 概述

Restartable Sequences（rseq，可重启序列）是一种用户态与内核协作的机制，用于实现高效的 per-CPU 数据访问。它允许用户态程序在不使用传统同步原语（如锁或原子操作）的情况下，安全地访问和修改 per-CPU 数据结构。

### 1.1 设计目标

rseq 的核心目标是提供一种**乐观并发**机制：

- 用户态代码可以假设自己不会被打断，直接操作 per-CPU 数据
- 如果确实被打断（抢占、信号等），内核负责将执行重定向到恢复路径
- 这种"要么完整执行，要么从头开始"的语义，避免了传统锁的开销

### 1.2 典型应用场景

- **内存分配器**：tcmalloc、jemalloc 等使用 per-CPU 缓存加速分配
- **引用计数**：per-CPU 引用计数可避免缓存行争用
- **统计计数器**：per-CPU 计数器的无锁更新
- **RCU 读侧临界区**：快速获取当前 CPU 信息

## 2. 核心概念

### 2.1 临界区（Critical Section）

rseq 临界区是一段用户态代码，具有以下特征：

```
┌─────────────────────────────────────────────────────────────┐
│                     rseq 临界区                              │
│                                                             │
│  start_ip ──► ┌─────────────────────────────────┐           │
│               │  1. 读取 cpu_id                  │           │
│               │  2. 使用 cpu_id 索引 per-CPU 数据 │           │
│               │  3. 执行操作（读/改/写）          │           │
│               │  4. 提交点（commit point）        │           │
│  end_ip ────► └─────────────────────────────────┘           │
│                         │                                   │
│                         │ 被打断时跳转                       │
│                         ▼                                   │
│  abort_ip ──► ┌─────────────────────────────────┐           │
│               │  恢复/重试逻辑                    │           │
│               └─────────────────────────────────┘           │
└─────────────────────────────────────────────────────────────┘
```

- **start_ip**：临界区起始地址
- **post_commit_offset**：从 start_ip 到提交点的偏移量
- **abort_ip**：中断恢复地址，必须位于临界区外

### 2.2 用户态数据结构

用户态需要在 TLS（线程本地存储）中维护一个 `struct rseq` 结构：

| 字段 | 大小 | 说明 |
|------|------|------|
| cpu_id_start | u32 | 进入临界区时的 CPU ID |
| cpu_id | u32 | 当前 CPU ID（内核更新） |
| rseq_cs | u64 | 指向当前临界区描述符的指针 |
| flags | u32 | 标志位（保留） |
| node_id | u32 | NUMA 节点 ID |
| mm_cid | u32 | 内存管理上下文 ID |

### 2.3 临界区描述符

`struct rseq_cs` 描述一个具体的临界区：

| 字段 | 大小 | 说明 |
|------|------|------|
| version | u32 | 版本号，必须为 0 |
| flags | u32 | 标志位 |
| start_ip | u64 | 临界区起始地址 |
| post_commit_offset | u64 | 临界区长度 |
| abort_ip | u64 | 中断恢复地址 |

## 3. 工作原理

### 3.1 注册流程

```
用户态                                    内核态
  │                                         │
  │  sys_rseq(rseq_ptr, len, 0, sig)       │
  │ ──────────────────────────────────────► │
  │                                         │ 1. 验证参数
  │                                         │ 2. 记录注册信息
  │                                         │ 3. 设置 NEED_RSEQ 标志
  │                                         │
  │  返回 0（成功）                          │
  │ ◄────────────────────────────────────── │
  │                                         │
```

### 3.2 临界区执行

正常执行时，用户态代码：

1. 将临界区描述符地址写入 `rseq->rseq_cs`
2. 读取 `rseq->cpu_id` 获取当前 CPU
3. 使用该 CPU ID 访问 per-CPU 数据
4. 完成操作后，清除 `rseq->rseq_cs`

### 3.3 内核干预时机

内核在以下事件发生后，返回用户态前进行检查和修正：

```
┌──────────────────────────────────────────────────────────────┐
│                    触发 rseq 处理的事件                        │
├──────────────────────────────────────────────────────────────┤
│  抢占（Preemption）                                           │
│    └─► 调度器切换进程时设置 PREEMPT 事件                        │
│                                                              │
│  信号递送（Signal Delivery）                                   │
│    └─► 设置信号帧前设置 SIGNAL 事件                            │
│                                                              │
│  CPU 迁移（Migration）                                        │
│    └─► 进程被迁移到其他 CPU 时设置 MIGRATE 事件                 │
└──────────────────────────────────────────────────────────────┘
```

### 3.4 返回用户态前的处理

当进程即将返回用户态时，内核执行以下步骤：

```
                    返回用户态前处理流程
                           │
                           ▼
                  ┌─────────────────┐
                  │ 检查 NEED_RSEQ  │
                  │    标志位       │
                  └────────┬────────┘
                           │ 已设置
                           ▼
                  ┌─────────────────┐
                  │  读取 rseq_cs   │
                  │  指针           │
                  └────────┬────────┘
                           │
              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
        rseq_cs == 0              rseq_cs != 0
        (不在临界区)               (在临界区)
              │                         │
              │                         ▼
              │                 ┌───────────────┐
              │                 │ 当前 IP 在    │
              │                 │ 临界区内？    │
              │                 └───────┬───────┘
              │                    是   │   否
              │              ┌──────────┴──────────┐
              │              ▼                     ▼
              │      ┌───────────────┐     ┌───────────────┐
              │      │ 修改返回地址   │     │ 清除 rseq_cs  │
              │      │ 为 abort_ip   │     │ (lazy clear)  │
              │      └───────────────┘     └───────────────┘
              │              │                     │
              └──────────────┴─────────────────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ 更新 cpu_id 等  │
                    │ TLS 字段        │
                    └─────────────────┘
                             │
                             ▼
                      返回用户态
```

## 4. 安全机制

### 4.1 签名验证

注册时用户提供一个 32 位签名值（sig），内核在处理临界区时会验证：

- 读取 `abort_ip - 4` 处的 4 字节
- 必须与注册时的签名匹配
- 防止恶意构造的临界区描述符

### 4.2 地址验证

内核对所有用户态地址进行严格验证：

- `start_ip`、`abort_ip` 必须在用户地址空间内
- `start_ip + post_commit_offset` 不能溢出
- `abort_ip` 必须在临界区外

### 4.3 错误处理

当检测到以下错误时，内核向进程发送 SIGSEGV：

- 用户内存访问失败
- 签名不匹配
- 地址验证失败
- 版本号不为 0

## 5. 与进程生命周期的集成

### 5.1 fork

- **CLONE_VM（线程）**：子线程需要重新注册 rseq
- **fork（进程）**：子进程继承父进程的 rseq 注册状态

### 5.2 execve

执行新程序时，rseq 注册状态被清除，新程序需要重新注册。

### 5.3 exit

进程退出时，rseq 状态随 PCB 一起释放，无需特殊处理。

## 6. 系统调用接口

### sys_rseq

```c
long sys_rseq(struct rseq *rseq, u32 rseq_len, int flags, u32 sig);
```

**参数：**
- `rseq`：用户态 rseq 结构的地址
- `rseq_len`：结构长度（至少 32 字节）
- `flags`：0 表示注册，RSEQ_FLAG_UNREGISTER (1) 表示注销
- `sig`：签名值

**返回值：**
- 成功：0
- 失败：负的错误码

**错误码：**
| 错误码 | 说明 |
|--------|------|
| EINVAL | 参数无效（长度、对齐、flags 等） |
| EPERM | 签名不匹配 |
| EBUSY | 已注册（重复注册相同参数） |
| EFAULT | 地址无效 |

## 7. 辅助向量（auxv）

内核通过 ELF 辅助向量向用户态传递 rseq 支持信息：

| 类型 | 值 | 说明 |
|------|-----|------|
| AT_RSEQ_FEATURE_SIZE | 27 | rseq 结构大小（32） |
| AT_RSEQ_ALIGN | 28 | rseq 对齐要求（32） |

用户态库（如 glibc）使用这些信息来：
- 确定内核是否支持 rseq
- 正确分配和对齐 TLS 中的 rseq 结构

## 8. 使用示例

以下伪代码展示了 rseq 的典型使用模式：

```c
// 1. 注册 rseq
struct rseq *rseq_ptr = &__rseq_abi;  // TLS 中的 rseq 结构
syscall(SYS_rseq, rseq_ptr, sizeof(*rseq_ptr), 0, RSEQ_SIG);

// 2. 定义临界区描述符
struct rseq_cs cs = {
    .version = 0,
    .flags = 0,
    .start_ip = (uintptr_t)&&start,
    .post_commit_offset = (uintptr_t)&&commit - (uintptr_t)&&start,
    .abort_ip = (uintptr_t)&&abort,
};

// 3. 执行临界区
retry:
    rseq_ptr->rseq_cs = (uintptr_t)&cs;
start:
    cpu = rseq_ptr->cpu_id;
    // 使用 cpu 访问 per-CPU 数据
    per_cpu_data[cpu].counter++;
commit:
    rseq_ptr->rseq_cs = 0;
    goto done;

abort:
    // 签名（必须紧挨在 abort 标签前）
    .int RSEQ_SIG
    rseq_ptr->rseq_cs = 0;
    goto retry;

done:
    // 操作完成
```

## 9. 参考资料

- [Linux rseq(2) man page](https://man7.org/linux/man-pages/man2/rseq.2.html)
- [LWN: Restartable sequences](https://lwn.net/Articles/697979/)
- Linux 6.6.21 kernel/rseq.c
