# io_context、seccomp、rseq 与更多可共享资源

> 这是 [../task-struct.md](/concepts/process/task-struct.md) 的资源对象总览的补充篇。除了大家熟悉的 mm/files/signal/cred/nsproxy 之外,`task_struct` 还挂着一批**容易被忽略但同样可共享的资源对象**——异步 IO 上下文、系统调用过滤器、重启序列等。本篇逐项拆解。

## 零、总图：task_struct 挂着什么——完整版

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<t>> #E3F2FD
  BorderColor<<t>> #1976D2
  BackgroundColor<<r>> #C8E6C9
  BorderColor<<r>> #388E3C
  BackgroundColor<<b>> #FFE0B2
  BorderColor<<b>> #EF6C00
  BackgroundColor<<s>> #F3E5F5
  BorderColor<<s>> #7B1FA2
}
rectangle "task_struct" <<t>> as T
rectangle "★ 五大数据资源对象\n(已独立成篇)" <<r>> as BIG5 {
  rectangle "mm (mm_struct)\n地址空间"
  rectangle "files (files_struct)\n打开文件表"
  rectangle "fs (fs_struct)\n文件系统上下文"
  rectangle "signal / sighand"
  rectangle "cred"
}
rectangle "隔离与限额" as ISO {
  rectangle "nsproxy\n命名空间"
  rectangle "cgroups\n资源限额"
}
rectangle "本次补齐的共享资源" <<b>> as NEW {
  rectangle "io_context\n异步IO上下文"
  rectangle "seccomp\n系统调用过滤"
  rectangle "rseq\n重启序列"
  rectangle "robust_list\nFutex 健壮列表"
}
rectangle "线程私有数据" <<s>> as PRIV {
  rectangle "各种私有字段\n(pid/state/调度/统计...)\n 见 task-private.md"
}
T --> BIG5
T --> ISO
T --> NEW
T --> PRIV
note bottom of T : 共享规则各不同:\nio_context → 线程共享(CLONE_IO)\nseccomp → 线程共享(CLONE_SECCOMP)\nrseq → 每线程独立
@enduml
```

## 一、io_context —— 异步 IO 的上下文

### 1.1 是什么

`task->io_context` 是**进程级别**的异步 IO（AIO）上下文,记录这个进程的所有异步 IO 操作的状态。每个使用 Linux AIO（`libaio`/`io_submit`/`io_getevents`）或 block layer 的 IO 优先级调度都需要它。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<ioc>> #E3F2FD
  BorderColor<<ioc>> #1976D2
  BackgroundColor<<ctx>> #C8E6C9
  BorderColor<<ctx>> #388E3C
}
rectangle "io_context (每进程一份)" <<ioc>> as IOC {
  rectangle "ioprio\n(IO 调度优先级)\nRT(0-7) / BE(0-7) / IDLE" as IOPRIO
  rectangle "aic (aio context 链表)\n异步 IO 请求" as AIC
  rectangle "cic (cfq io context 链表)\nper-块设备 IO 统计\n(seek/sector data)" as CIC
  rectangle "nr_tasks (引用计数)\n多少线程共享" as REF
}
note bottom of IOPRIO : ionice 命令设置\n例如: ionice -c 2 -n 4 ./myapp\n→ BE class, priority 4
note bottom of AIC : io_submit/io_getevents 在这里分配/完成
@enduml
```

### 1.2 IO 调度优先级（ionice）

| Class | 值 | 说明 | 使用场景 |
|-------|-----|------|---------|
| `IOPRIO_CLASS_RT` | 1 | 实时 IO,先于所有人 (priority 0-7) | 绝对不能等的延迟敏感 IO |
| `IOPRIO_CLASS_BE` | 2 | Best-effort,默认 (priority 0-7) | 绝大多数进程 |
| `IOPRIO_CLASS_IDLE` | 3 | 只在没人用磁盘时才跑 | 后台批量任务（备份/日志压缩） |

```bash
# 查看/设置 IO 优先级
ionice -p <pid>          # 读
ionice -c 2 -n 0 ./app   # BE 最高优先级
ionice -c 3 ./backup.sh  # IDLE 级, 不抢前台 IO
```

> **CFQ/BFQ 调度器下才完全生效**, `mq-deadline`/`kyber` 等多队列调度器下含义不完全等同。不过 `ionice` 命令设的值会被存到 `io_context` 里。

### 1.3 共享规则

- 线程**共享** `io_context`（`CLONE_IO` flag）
- fork 子进程**继承父的 io_context**（引用计数 +1）
- `execve` **不清除** io_context
- 一个进程只有一个 io_context,所有线程的 AIO 合并统计

### 1.4 AIO 工作流程

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor #1976D2
}
participant "用户代码" as U
participant "libaio" as A
participant "内核 io_context" as I
participant "块层" as B
participant "磁盘" as D
U -> A : io_submit(ctx, nr, iocbs)
A -> I : sys_io_submit()
I -> I : 创建 aio_kiocb\n挂到 io_context->aic 链表
I -> B : submit_bio()
B -> D : 发起 DMA
note over U : 不阻塞, 立即返回
... 用户可以做别的事 ...
U -> A : io_getevents(ctx, ...)
A -> I : sys_io_getevents()
I -> U : 返回已完成的 IO 结果
@enduml
```

> **io_uring 不是用这套 io_context**：io_uring (5.1+) 有自己的 `io_ring_ctx` 结构,和传统 AIO 的 `io_context` 是两套体系。本文讲的是传统 AIO 那套。

## 二、seccomp —— 系统调用过滤器

### 2.1 是什么

seccomp（SECure COMPuting）允许进程**限制自己能调用哪些系统调用**——是 Linux 沙箱机制的基石。Chrome/Docker/Firejail/systemd 都在用。

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor #1976D2
}
participant "用户程序" as U
participant "seccomp filter" as S
participant "seccomp actions" as A
participant "内核" as K
U -> K : syscall (如 open)
K -> S : 轮到 seccomp 检查
S -> S : BPF 程序逐条匹配\n参数/系统调用号/架构...
alt 匹配到规则
  S -> A : 规则指定的动作
  A -> A : SECCOMP_RET_KILL → 杀进程\nSECCOMP_RET_ERRNO → 返回 errno\nSECCOMP_RET_TRAP → 发 SIGSYS\nSECCOMP_RET_ALLOW → 放行
else 无匹配
  S -> K : 默认动作(通常 KILL)
end
@enduml
```

### 2.2 seccomp filter 即 BPF 程序

seccomp-bpf 用经典的 **cBPF 字节码**（不是 eBPF）写过滤规则——在用户态编译成 BPF 指令数组,用 `prctl(PR_SET_SECCOMP, ...)` 安装到内核：

```c
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
// BPF 程序：只允许 read/write/exit, 其余全杀
struct sock_filter filter[] = {
    // 检查架构(防止 32/64 位绕过)
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    // 检查系统调用号
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),  // 默认杀
};
struct sock_fprog prog = {
    .len = sizeof(filter) / sizeof(filter[0]),
    .filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);  // 必须先设这个
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
```

### 2.3 两种模式

| 模式 | 设置方法 | 行为 |
|------|---------|------|
| `SECCOMP_MODE_STRICT` | `prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)` | 只允许 read/write/_exit/sigreturn——最简单最严格 |
| `SECCOMP_MODE_FILTER` | `prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)` | 用 BPF 程序自定义规则 |

### 2.4 共享规则

- 线程**共享** seccomp filter（`CLONE_SECCOMP`）,过滤器**只能加严,不能放宽**
- `PR_SET_NO_NEW_PRIVS` 必须先设——防止 setuid 程序绕过
- 装了 filter 后,**fork 的子进程也继承**这些 filter
- `execve` **不消除** filter（这是安全关键——保证"沙箱牢不可破"）

### 2.5 seccomp 在实际场景中的使用

```bash
# Docker 默认 seccomp profile 拦截约 44 个危险 syscall
# 容器中不生效的例子:
docker run --security-opt seccomp=unconfined ...  # 关掉 seccomp
# systemd 服务单元
[Service]
SystemCallFilter=~@mount @reboot     # 禁止 mount/reboot 相关调用
SystemCallArchitectures=native       # 禁止 32 位系统调用
# 观测某个进程的 seccomp 状态
cat /proc/<pid>/status | grep Seccomp
# Seccomp: 0=禁用 / 1=STRICT / 2=FILTER
```

## 三、rseq —— 可重启序列（Restartable Sequences）

### 3.1 是什么

`rseq` 是 Linux 4.18+ 引入的**无锁快速 per-CPU 数据访问机制**。它让用户态线程可以原子地读写 per-CPU 数据而不用系统调用。

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor #1976D2
}
participant "用户代码 rseq 临界区" as U
participant "内核" as K
U -> U : ① PREPARE: 读 rseq->cpu_id\n确认在哪个核
U -> U : ② COMMIT (关键): 用一条指令\n**原子地**写入数据 + 确认 cpu_id 没变
alt cpu_id 没变 (正常)
  note over U : 完成, 零开销
else cpu_id 变了(被迁移/NMI/信号打断)
  K -> U : 内核自动跳转到 abort handler\n→ 重新执行临界区
end
@enduml
```

### 3.2 rseq 的核心价值

| 传统方案 | rseq |
|---------|------|
| 需要系统调用（sched_getcpu） | 读一块共享内存,零 syscall |
| 需要锁保护 per-CPU 操作 | 无锁,COMMIT 指令本身就是原子保证 |
| 被迁移后需要全部重做 | 内核自动 abort+重启——用户只需写一个"重做循环" |

> glibc 2.35+ 已内置 rseq 支持,`sched_getcpu()` 内部优先走 rseq 而不是 vsyscall。

### 3.3 共享规则

- rseq **每线程独立注册**（`rseq` 系统调用 per-task）
- `clone`/`fork` **不继承** rseq 注册——子进程需重新注册
- 每个 task 只能在**一个 CPU 上**同时拥有 rseq 临界区

### 3.4 观测

```bash
# 检查内核是否支持
grep CONFIG_RSEQ /boot/config-$(uname -r)   # CONFIG_RSEQ=y
# glibc 是否初始化了 rseq（2.35+）
cat /proc/<pid>/maps | grep rseq            # 会在堆中看到 rseq 段
```

## 四、robust_list / compat_robust_list —— Futex 健壮列表

`task->robust_list` 记录这个线程持有的所有 **robust futex**（PI futex 的一种）。如果线程在没有释放 futex 的情况下去世（崩溃/被杀）,内核会自动代为解锁：

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor #1976D2
}
participant "线程 A\n持有 robust futex" as A
participant "内核 exit" as K
participant "futex" as F
participant "线程 B\n等在 futex 上" as B
A -> A : 持有 mutex (futex=2)
A -> K : 异常退出(crash/kill)
K -> K : do_exit → exit_robust_list()
K -> K : 遍历 thread->robust_list
K -> F : futex_wake + set FUTEX_OWNER_DIED
note over F : 锁被释放, 但标记为\n"持有者已死"(EOWNERDEAD)
B -> B : 被唤醒
B -> F : futex_lock 返回 EOWNERDEAD
@enduml
```

- pthread mutex 设为 `PTHREAD_MUTEX_ROBUST` 就会自动注册到 `robust_list`
- fork 子进程**清空** robust_list（子进程重新开始）——父子不共享
- 否则一个线程 crash 而 mutex 无人解锁 → 等待者**永远阻塞**

## 五、其他容易被忽略的相关结构

| task_struct 字段 | 类型 | 含义 | 共享 |
|-----------------|------|------|------|
| `sched_entity` / `sched_rt_entity` / `sched_dl_entity` | 内嵌结构 | 调度实体数据（vruntime/权重...） | 私有（每个 task 自己的调度数据） |
| `wake_q` | `struct wake_q_node` | 唤醒链表节点,lazy 成批唤醒优化 | 私有 |
| `journal_info` | `void *` | 文件系统日志事务上下文（ext4/xfs 用来追踪当前事务） | 私有（一个 task 同一时间只能在一个 FS 事务中） |
| `bio_list` | `struct bio_list` | 当前 task 攒着待提交的 block IO(plug IO 批量合并) | 私有 |
| `plug` | `struct blk_plug` | block layer 的"塞子",攒 IO 请求成批提交 | 私有 |
| `security` | `void *` | LSM（SELinux/AppArmor）安全上下文 | 私有 |
| `memcg_in_oom` | `struct mem_cgroup *` | 记录当前被 OOM 的 memcg（防止递归 OOM） | 私有 |
| `in_eventfd_ctx` | `bool` | 正在处理 eventfd 通知（防死循环唤醒） | 私有 |

## 六、完整共享规则速查表（含新增项）

| 资源对象 | 内核结构 | fork 子进程 | 同进程线程 | clone flag |
|---------|---------|------------|-----------|-----------|
| 地址空间 | `mm_struct` | 复制(CoW) | **共享** | `CLONE_VM` |
| 打开文件表 | `files_struct` | 复制 | **共享** | `CLONE_FILES` |
| 文件系统上下文 | `fs_struct` | 复制 | **共享** | `CLONE_FS` |
| 信号处理表 | `sighand_struct` | 复制 | **共享** | `CLONE_SIGHAND` |
| 线程组信号状态 | `signal_struct` | 复制 | **共享** | `CLONE_THREAD` |
| 凭证 | `cred` | 复制(继承) | 共享 | — |
| 命名空间 | `nsproxy` | 默认继承 | 共享 | `CLONE_NEW*` |
| **IO 上下文** | `io_context` | 继承(引用+1) | **共享** | `CLONE_IO` |
| **seccomp filter** | `seccomp` | 继承(只加严) | **共享** | `CLONE_SECCOMP` |
| **rseq** | `rseq` | **不继承** | 每线程独立注册 | — |
| **robust_list** | `robust_list_head` | **清空** | 每线程独立 | — |
| 调度实体 | `sched_entity` | 复制 | 私有（不同 task 各自调度） | — |
| 统计/计时 | `utime/stime/...` | 清零 | 私有 | — |

## 七、一句话总结

> **task_struct 挂着的远不止五个常见资源对象。`io_context`（AIO 上下文和 IO 调度优先级,线程共享）、`seccomp`（BPF 系统调用过滤器,只能加严不能放宽,Chrome/Docker 都用它做沙箱）、`rseq`（per-CPU 无锁操作,每线程独立注册）、`robust_list`（线程死后内核代解锁的 mutex 列表）——每一类都有自己的共享规则和 clone flag。对照共享规则速查表,看清 fork 是复制还是共享、clone flag 是谁在控制。**
