首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

RK3588 平台关联与使用场景详解

RK3588 平台关联与使用场景详解

源码路径rk3588/kernel-6.1/ipc/(通用实现)
文档目录linuxDoc/ipc/


目录


一、平台结论

说明
Rockchip 补丁
架构 ARM64 使用标准 syscall 表,不依赖ipc()
默认 Kconfig SYSVIPCIPC_NSPOSIX_MQUEUE 在通用 defconfig 中多为 y

RK3588 行为与上游 Linux 6.1 一致。


二、内核配置建议

场景 建议
最小嵌入式固件 =n SYSVIPCPOSIX_MQUEUE 减体积(需确认无依赖软件)
Docker / LXC 保留 IPC_NS + SYSVIPC
实时音频/控制 更常用 pthread + futexeventfdmemfd,非 SysV
数据库/遗留中间件 保留 shm + sem

POSIX_MQUEUE 依赖 NET:裁网络时要连带注意。


三、与 SeagullYpcEncode / 嵌入式

机制 编解码场景相关性
SysV shm :现代方案用 DMA-BUF、ION、drm prime
SysV msg/sem 低–中:老进程间控制;新服务多用 socket/dbus
POSIX mqueue :部分 RTOS 移植代码可能使用
IPC NS :多实例容器化部署时需要隔离

VPU/RGA 零拷贝 不走 ipc/shm.c;详见驱动与 linuxDoc/mm


四、容器与调试

1
2
3
4
5
6
7
# 主机
ipcs -a
ls /proc/sysvipc/
readlink /proc/self/ns/ipc

# 容器内独立 IPC NS 时,主机 ipcs 看不到容器内对象
docker run --ipc private ...
问题 方向
Cannot allocate memory msg 增大 msgmnb/msgmni
shm 残留 ipcrmshm_rmid_forced
mqueue 打不开 检查挂载与 fs.mqueue.* 限额

关联文档

ipc/ 进程间通信机制与原理详解

ipc/ 进程间通信机制与原理详解

源码路径rk3588/kernel-6.1/ipc/
内核版本:Linux 6.1(RK3588 / ARM64)
文档目录linuxDoc/ipc/

内核 IPC 子系统实现两类机制:System V IPC(信号量、消息队列、共享内存)与 POSIX 消息队列mqueue 文件系统)。现代应用更多使用 管道、Unix socket、memfd、io_uring,但 SysV/POSIX mq 仍广泛用于遗留服务、数据库与 容器命名空间隔离


目录


一、子系统职责

机制 用户 API 内核实现
信号量 semget semop semctl sem.c
消息队列 msgget msgsnd msgrcv msgctl msg.c + msgutil.c
共享内存 shmget shmat shmdt shmctl shm.c
旧接口 ipc(call, …) syscall.c(部分 arch 仍保留)
POSIX mq mq_open mq_timedsend mqueue.cmqueue fs)
隔离 unshare -i / Docker namespace.c + IPC_NS

二、架构关系

用户态ipc/其它子系统SysV APImq_* APIutil.c 公共 ID/权限sem.cmsg.cshm.cmqueue.cnamespace.cmm VMA 页缓存VFS mqueuefsLSM security_ipc

三、统一 ID 与权限模型

3.1 IPC ID 编码(util.h

  • index(低 15 位,或扩展模式 24 位)+ sequence(防 id 重用 UAF)
  • ipc_addid / ipc_rmid 管理 idripc_findkeyrhashtable

3.2 创建路径 ipcget

  1. IPC_PRIVATE:直接 getnew
  2. IPC_CREAT | keyipcget_public 查表或创建
  3. 权限ipcperms + LSM security_*_associate

3.3 锁(util.c 头注释)

  • RCU 查找 kern_ipc_perm
  • 每对象 kern_ipc_perm.lock 做修改类操作
  • ids->rwsem:创建/删除/遍历 id 集、/proc/sysvipc

四、与 RK3588 关系

  • 无平台补丁;ARM64 使用独立 syscall(semop 等),一般不依赖旧 ipc()
  • 嵌入式若裁掉 SYSVIPC 可缩小内核,但 glibc/部分中间件 可能仍需要
  • 容器/Android 命名空间 依赖 IPC_NS 时本目录为必需

详见 07


功能组专题

编号 文档
01 SysV-IPC公共框架与ID管理机制与实现详解.md
02 信号量机制与实现详解.md
03 SysV消息队列机制与实现详解.md
04 共享内存机制与实现详解.md
05 POSIX消息队列机制与实现详解.md
06 命名空间sysctl与兼容层机制与实现详解.md
07 RK3588平台关联与使用场景详解.md

ipc 文档索引

ipc 文档索引

Linux 6.1(RK3588)内核 进程间通信(IPC) 子系统源码分析与原理说明。

总览文档

文档 说明
ipc进程间通信机制与原理详解.md 总览:SysV IPC、POSIX mqueue、命名空间、RK3588
源码目录与模块索引.md ipc/ 文件清单、Kconfig/Makefile

功能组专题(原理 + 实现)

编号 文档 源码重点
01 SysV-IPC公共框架与ID管理机制与实现详解.md util.cutil.hipcgetipc_addid
02 信号量机制与实现详解.md sem.csemop/semget
03 SysV消息队列机制与实现详解.md msg.cmsgutil.c
04 共享内存机制与实现详解.md shm.cshmat/shmget
05 POSIX消息队列机制与实现详解.md mqueue.cmq_open
06 命名空间sysctl与兼容层机制与实现详解.md namespace.cipc_sysctl.ccompat.c
07 RK3588平台关联与使用场景详解.md 配置、容器、与编解码场景

源码路径

1
2
3
rk3588/kernel-6.1/ipc/
include/linux/ipc_namespace.h
include/linux/msg.h、sem.h、shm.h

阅读顺序建议

总览01 公共框架02 信号量03 SysV 消息04 共享内存05 POSIX mqueue06 命名空间 / sysctl07 RK3588

速查

机制 系统调用 主要文件
信号量 semget semop semctl sem.c
消息队列 msgget msgsnd msgrcv msgctl msg.c
共享内存 shmget shmat shmdt shmctl shm.c
旧合一入口 ipc() syscall.c
POSIX mq mq_open mq_send mq_receive mqueue.c
命名空间 CLONE_NEWIPC namespace.c

关联文档

  • 内存管理(shm 页、VMA):linuxDoc/mm
  • 命名空间总览:kernel/nsproxy.c(不在 ipc/
  • 启动初始化:device_initcall(ipc_init)linuxDoc/init

ipc/ 源码目录与模块索引

ipc/ 源码目录与模块索引

源码路径rk3588/kernel-6.1/ipc/(13 个文件)
内核版本:Linux 6.1(RK3588 / ARM64)
文档目录linuxDoc/ipc/


一、文件清单

文件 条件编译 职责
util.c CONFIG_SYSVIPC ID 管理、ipcget、proc /proc/sysvipc、热插拔 notifier
util.h 头文件 公共 API、ID 位域、ipc_ops
sem.c CONFIG_SYSVIPC System V 信号量
msg.c CONFIG_SYSVIPC System V 消息队列
msgutil.c SYSVIPC 或 POSIX_MQUEUE msg_msg 分配、init_ipc_ns
shm.c CONFIG_SYSVIPC System V 共享内存
syscall.c CONFIG_SYSVIPC + __ARCH_WANT_SYS_IPC ipc() 多路复用
compat.c CONFIG_SYSVIPC_COMPAT 32 位 compat 路径
namespace.c CONFIG_IPC_NS ipc_namespace 创建/销毁
mqueue.c CONFIG_POSIX_MQUEUE POSIX 消息队列 + mqueuefs
ipc_sysctl.c CONFIG_SYSVIPC_SYSCTL kernel.shm*kernel.msg*kernel.sem
mq_sysctl.c CONFIG_POSIX_MQUEUE_SYSCTL fs.mqueue.*
Makefile 对象链接规则

平台:无 Rockchip 专用代码。


二、Makefile

1
2
3
4
5
6
obj-$(CONFIG_SYSVIPC)         += util.o msgutil.o msg.o sem.o shm.o syscall.o
obj-$(CONFIG_SYSVIPC_SYSCTL) += ipc_sysctl.o
obj-$(CONFIG_SYSVIPC_COMPAT) += compat.o
obj-$(CONFIG_POSIX_MQUEUE) += mqueue.o msgutil.o
obj-$(CONFIG_IPC_NS) += namespace.o
obj-$(CONFIG_POSIX_MQUEUE_SYSCTL) += mq_sysctl.o

msgutil.o 可被 SysV 与 mqueue 共享编译


三、Kconfig(init/Kconfig

配置 默认 说明
SYSVIPC 通常 y 三类 SysV IPC
SYSVIPC_SYSCTL y(依赖 SYSCTL) /proc/sys/kernel 中 ipc 参数
SYSVIPC_COMPAT y(COMPAT) ARM32 compat 等
POSIX_MQUEUE 常 y 依赖 NET
POSIX_MQUEUE_SYSCTL y mqueue 限额 sysctl
IPC_NS y CLONE_NEWIPC 隔离

四、ipc_namespace 与三类 ID

include/linux/ipc_namespace.h

1
2
3
4
5
6
struct ipc_namespace {
struct ipc_ids ids[3]; // SEM, MSG, SHM
// sem_ctls[], msg_ctl*, shm_ctl*
struct vfsmount *mq_mnt; // POSIX mqueue
...
};
索引 类型 文件
IPC_SEM_IDS 信号量集 sem.c
IPC_MSG_IDS 消息队列 msg.c
IPC_SHM_IDS 共享内存段 shm.c

每类使用 struct ipc_idsidr(按 id 查找)+ rhashtable(按 key 查找)+ rwsem


五、初始化顺序

阶段 函数 说明
device_initcall ipc_init() in util.c proc_mkdir("sysvipc")sem_init msg_init shm_init
每 NS create_ipc_ns() mq_init_nsmsg_init_nssem_init_nsshm_init_ns

六、专题对照

主题 文档
util / ID 01
sem 02
msg 03
shm 04
mqueue 05
ns / sysctl 06
RK3588 07

kernel/bpf 内核 BPF 机制与原理详解

kernel/bpf 内核 BPF 机制与原理详解

源码路径rk3588/kernel-6.1/kernel/bpf/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE)

该目录实现 Linux 内核 eBPF(extended BPF) 子系统的核心代码。eBPF 允许用户空间加载经过严格验证的字节码程序,在内核中安全、高效地执行,用于网络过滤、追踪、安全策略、 cgroup 管控等场景。所有操作通过 bpf() 系统调用及文件描述符(prog/map/link fd)完成。


目录


一、源码目录结构

1.1 编译依赖关系(Kconfig)

1
2
3
4
5
6
7
BPF (基础 classic BPF 解释器)
└── BPF_SYSCALL (menuconfig "BPF subsystem")
├── BPF_JIT ← JIT 编译器
├── BPF_JIT_ALWAYS_ON ← 永久启用 JIT,移除解释器
├── BPF_UNPRIV_DEFAULT_OFF ← 默认禁止非特权 BPF
├── BPF_LSM ← LSM 安全钩子挂载
└── BPF_PRELOAD ← 启动时预加载 BPF 程序

关键配置项:

配置项 说明
CONFIG_BPF 基础 BPF 支持(classic socket filter 依赖)
CONFIG_BPF_SYSCALL 启用 bpf() 系统调用
CONFIG_BPF_JIT JIT 编译,将 eBPF 字节码编译为本地机器码
CONFIG_HAVE_EBPF_JIT 架构支持 eBPF JIT(ARM64 支持)
CONFIG_BPF_JIT_ALWAYS_ON 仅 JIT,无解释器(防侧信道)
CONFIG_DEBUG_INFO_BTF 内核 BTF 调试信息(CO-RE、fentry 依赖)
CONFIG_CGROUP_BPF cgroup 附加 BPF 程序
CONFIG_BPF_LSM BPF LSM 安全策略
CONFIG_BPF_EVENTS BPF 挂载 tracepoint/kprobe(在 trace 子系统)

1.2 编译单元(Makefile)

编译单元 源文件 规模 功能
核心解释器 core.c ~2,781 行 classic/eBPF 解释器、bpf_prog_run、程序分配
系统调用 syscall.c ~5,378 行 bpf() syscall、prog/map/link 生命周期
验证器 verifier.c ~15,726 行 静态分析、类型检查、安全验证
BTF btf.c ~8,045 行 BPF Type Format 解析、验证、内核 BTF
Cgroup cgroup.c ~2,578 行 cgroup BPF 附加与执行
Hash Map hashtab.c ~2,547 行 HASH / LRU / PERCPU hash 实现
Helper helpers.c ~1,745 行 通用 helper 函数实现
Array Map arraymap.c ~1,359 行 ARRAY / PERCPU_ARRAY / PROG_ARRAY 等
Trampoline trampoline.c ~1,080 行 fentry/fexit/modify_return 跳板
Devmap devmap.c ~1,137 行 网络设备重定向 map
BPF Iter bpf_iter.c ~778 行 BPF 迭代器框架
Ringbuf ringbuf.c ~795 行 BPF ring buffer map
Inode/FS inode.c ~820 行 bpffs 文件系统(对象 pin)
Dispatcher dispatcher.c ~174 行 多路分支直接调用优化
LSM bpf_lsm.c ~357 行 BPF LSM 钩子
Preload preload/ 启动时预加载迭代器 BPF

1.3 主要源文件分类

核心基础设施

文件 功能
core.c eBPF 解释器(___bpf_prog_run)、classic BPF、bpf_prog_alloc、JIT 辅助
syscall.c __sys_bpf()bpf_prog_load()map_create()、link 管理
verifier.c bpf_check() 静态验证器:CFG 分析、寄存器类型追踪、内存访问检查
helpers.c 通用 helper:map_lookup/update/delete、时间、随机数等
btf.c BTF 加载/验证/解析、vmlinux BTF、CO-RE relocation
log.c 验证器日志输出
tnum.c 追踪数值(tracked number)用于验证器常量传播
disasm.c eBPF 指令反汇编(调试用)
memalloc.c BPF 专用内存分配
inode.c bpffs 伪文件系统,支持 prog/map/link pin
dispatcher.c 避免 indirect call 的多路分支代码生成
relo_core.c CO-RE relocation 核心(来自 tools/lib/bpf)

Map 实现

文件 Map 类型
arraymap.c ARRAY, PERCPU_ARRAY, PROG_ARRAY, PERF_EVENT_ARRAY, CGROUP_ARRAY
hashtab.c HASH, PERCPU_HASH, LRU_HASH, LRU_PERCPU_HASH, HASH_OF_MAPS
lpm_trie.c LPM_TRIE(最长前缀匹配)
map_in_map.c ARRAY_OF_MAPS, HASH_OF_MAPS
queue_stack_maps.c QUEUE, STACK
ringbuf.c RINGBUF, USER_RINGBUF
bloom_filter.c BLOOM_FILTER
stackmap.c STACK_TRACE
reuseport_array.c REUSEPORT_SOCKARRAY
local_storage.c CGROUP_STORAGE, PERCPU_CGROUP_STORAGE
bpf_local_storage.c SK_STORAGE
bpf_task_storage.c TASK_STORAGE
bpf_inode_storage.c INODE_STORAGE

网络相关

文件 功能
devmap.c DEVMAP / DEVMAP_HASH — XDP 设备重定向
cpumap.c CPUMAP — XDP 跨 CPU 转发
offload.c 硬件 offload BPF
net_namespace.c 网络命名空间 BPF 隔离

挂载与迭代

文件 功能
trampoline.c BPF trampoline:fentry/fexit/modify_return
cgroup.c cgroup BPF 程序附加与 bpf_prog_run_array_cg()
bpf_lsm.c LSM 钩子 BPF 程序挂载
bpf_struct_ops.c struct_ops BPF(如 TCP congestion control)
bpf_iter.c BPF iterator 框架
map_iter.c 迭代所有 BPF map
prog_iter.c 迭代所有 BPF prog
task_iter.c 迭代 task 结构
link_iter.c 迭代 BPF link
cgroup_iter.c 迭代 cgroup

预加载

文件 功能
preload/bpf_preload_kern.c 启动时加载 skeleton BPF(map/prog 调试迭代器)
preload/iterators/ 预置 BPF 迭代器程序

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ libbpf / bpftool / clang -target bpf │
└──────────────────────────┬──────────────────────────────────┘
│ bpf() syscall + fd (prog/map/link)
┌──────────────────────────▼──────────────────────────────────┐
│ syscall.c │
│ __sys_bpf() │ prog_load │ map_create │ link_create │
└──────┬──────────┬──────────┬──────────┬─────────────────────┘
│ │ │ │
┌──────▼──┐ ┌─────▼────┐ ┌──▼──────┐ ┌─▼──────────────┐
│Verifier │ │ Map Ops │ │ BTF │ │ Trampoline/ │
│verifier │ │ array/ │ │ btf.c │ │ Attach/Link │
│ .c │ │ hashtab │ │ │ │ trampoline.c │
└──────┬──┘ └─────┬────┘ └──┬──────┘ └─┬──────────────┘
│ │ │ │
└──────────┴──────────┴──────────┘

┌────────────▼────────────┐
│ core.c + arch JIT │
│ 解释器 / JIT 机器码 │
│ bpf_prog_run() │
└────────────┬────────────┘

┌────────────▼────────────┐
│ 内核 Hook 点 │
│ XDP/cgroup/trace/LSM/ │
│ kprobe/fentry/... │
└─────────────────────────┘

2.2 核心设计原则

  • 安全优先:所有用户加载的程序必须经过 verifier 静态验证,保证有界执行、类型安全、无非法内存访问
  • JIT 加速:默认通过架构相关 JIT(ARM64: arch/arm64/net/bpf_jit_comp.c)编译为本地代码
  • Map 共享状态:prog 通过 fd 引用 map,实现内核与用户空间、prog 与 prog 之间的数据共享
  • Link 抽象:BPF link 统一管理 prog 与挂载点的生命周期
  • BTF 类型安全:携带 BTF 的程序支持 CO-RE、typed context access、fentry/fexit

三、核心数据结构

3.1 struct bpf_prog — BPF 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// include/linux/filter.h
struct bpf_prog {
u16 pages; // 程序占用页数
u16 jited:1, // 是否已 JIT
gpl_compatible:1,
jit_requested:1,
blinding_requested:1;
enum bpf_prog_type type; // 程序类型
enum bpf_attach_type expected_attach_type;
u32 len; // 指令数
struct bpf_insn *insnsi; // eBPF 指令数组
struct bpf_prog_aux *aux; // 扩展信息
unsigned int (*bpf_func)(const void *ctx,
const struct bpf_insn *insn);
// JIT 后 bpf_func 指向编译后的机器码
};

bpf_prog_aux 包含 BTF、used_maps、attach 信息、stats、line info 等。

3.2 struct bpf_map — BPF 映射

1
2
3
4
5
6
7
8
9
10
// include/linux/bpf.h
struct bpf_map {
const struct bpf_map_ops *ops; // 虚函数表
enum bpf_map_type map_type;
u32 key_size;
u32 value_size;
u32 max_entries;
atomic64_t writecnt; // 并发写计数
// ...
};

3.3 struct bpf_map_ops — Map 虚函数表

1
2
3
4
5
6
7
8
9
struct bpf_map_ops {
int (*map_alloc_check)(union bpf_attr *attr);
struct bpf_map *(*map_alloc)(union bpf_attr *attr);
void (*map_free)(struct bpf_map *map);
void *(*map_lookup_elem)(struct bpf_map *map, void *key);
int (*map_update_elem)(struct bpf_map *map, void *key, void *value, u64 flags);
int (*map_delete_elem)(struct bpf_map *map, void *key);
// ...
};

各 map 类型在 linux/bpf_types.h 中通过 BPF_MAP_TYPE 宏注册到 bpf_map_types[]

3.4 struct bpf_insn — eBPF 指令

1
2
3
4
5
6
7
8
// include/uapi/linux/linux/bpf.h
struct bpf_insn {
__u8 code; // 操作码
__u8 dst_reg:4; // 目标寄存器
__u8 src_reg:4; // 源寄存器
__s16 off; // 跳转偏移
__s32 imm; // 立即数
};

寄存器约定:

  • R0 — 返回值
  • R1-R5 — 参数传递(R1 = context 指针)
  • R6-R9 — callee saved
  • R10 — 帧指针(只读)
1
2
3
4
5
6
7
8
struct bpf_link {
atomic64_t refcnt;
u32 id;
enum bpf_link_type type;
struct bpf_link_ops *ops; // release/dealloc/update_map
struct bpf_prog *prog;
// ...
};

Link 替代旧的 BPF_PROG_ATTACH 方式,提供更清晰的生命周期管理。


四、bpf() 系统调用

4.1 入口

1
2
3
4
5
// syscall.c
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
{
return __sys_bpf(cmd, USER_BPFPTR(uattr), size);
}

4.2 命令列表

命令 功能
BPF_MAP_CREATE 创建 map,返回 fd
BPF_MAP_LOOKUP_ELEM 查找 map 元素
BPF_MAP_UPDATE_ELEM 更新 map 元素
BPF_MAP_DELETE_ELEM 删除 map 元素
BPF_MAP_GET_NEXT_KEY 遍历 map
BPF_MAP_FREEZE 冻结 map(只读)
BPF_MAP_*_BATCH 批量 lookup/update/delete
BPF_PROG_LOAD 加载 BPF 程序,返回 fd
BPF_PROG_ATTACH 附加程序(旧接口)
BPF_PROG_DETACH 分离程序
BPF_PROG_TEST_RUN 测试运行程序
BPF_PROG_BIND_MAP 绑定 map 到 prog
BPF_OBJ_PIN pin 到 bpffs
BPF_OBJ_GET 从 bpffs 获取
BPF_OBJ_GET_INFO_BY_FD 查询对象信息
BPF_*_GET_FD_BY_ID 通过 ID 获取 fd
BPF_*_GET_NEXT_ID 遍历 ID
BPF_BTF_LOAD 加载 BTF 数据
BPF_RAW_TRACEPOINT_OPEN 打开 raw tracepoint
BPF_LINK_CREATE 创建 link
BPF_LINK_UPDATE 更新 link
BPF_LINK_DETACH 分离 link
BPF_ITER_CREATE 创建 BPF iterator
BPF_ENABLE_STATS 启用运行时统计
BPF_TASK_FD_QUERY 查询 task 关联的 fd

4.3 权限控制

1
2
3
4
5
6
// sysctl: /proc/sys/kernel/unprivileged_bpf_disabled
// 0 = 允许非特权 1 = 永久禁止 2 = 默认禁止(CONFIG_BPF_UNPRIV_DEFAULT_OFF)

// BPF_MAP_CREATE 和 BPF_PROG_LOAD 需要 CAP_BPF 或 CAP_SYS_ADMIN
if (!capable && (cmd == BPF_MAP_CREATE || cmd == BPF_PROG_LOAD))
return -EPERM;

五、程序加载与验证器

5.1 加载流程

1
2
3
4
5
6
7
8
9
10
11
12
bpf_prog_load()
├── 检查 license(GPL 兼容才能调用 GPL-only helper)
├── 检查 prog_type 权限(网络类需 CAP_NET_ADMIN 等)
├── bpf_prog_alloc() — 分配 prog 结构
├── copy_from_user(insns) — 复制字节码
├── bpf_prog_select_runtime() — 选择解释器或 JIT
├── bpf_check() — 验证器(verifier.c)
│ ├── 第一遍:CFG 分析(DAG、无环、可达性)
│ └── 第二遍:寄存器类型状态追踪
├── bpf_prog_load_btf() — 加载关联 BTF
├── bpf_prog_alloc_id() — 分配全局 ID
└── fd_install() — 返回文件描述符

5.2 验证器核心逻辑

verifier.cbpf_check() 是 eBPF 安全的核心:

第一遍 — CFG 验证:

  • 程序必须是 DAG(无循环)
  • 指令数 ≤ BPF_MAXINSNS(4096,特权用户更高)
  • 所有跳转目标合法

第二遍 — 状态追踪:

  • 每个寄存器维护类型状态(PTR_TO_CTXPTR_TO_MAP_VALUESCALAR_VALUE 等)
  • 追踪算术运算对指针类型的影响
  • 检查 helper 调用的参数类型约束
  • 检查 map 访问边界(key/value size)
  • 检查 null 指针解引用(通过 branch 类型细化)

寄存器类型示例:

1
2
3
4
5
6
程序入口: R1 = PTR_TO_CTX
BPF_MOV64_REG(R2, R10) → R2 = FRAME_PTR
BPF_ALU64_IMM(ADD, R2, -4) → R2 = PTR_TO_STACK (offset -4)
BPF_LD_MAP_FD(R1, map_fd) → R1 = CONST_PTR_TO_MAP
BPF_CALL(map_lookup_elem) → 检查 arg1=MAP, arg2=STACK key
→ R0 = PTR_TO_MAP_VALUE_OR_NULL

Reference 类型追踪:

  • PTR_TO_SOCKET_OR_NULL 等内核资源引用
  • 必须在程序所有路径上正确释放(如 bpf_sk_release

5.3 程序类型验证器

每种 BPF_PROG_TYPE 有独立的 bpf_verifier_ops

1
2
3
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
#include <linux/bpf_types.h> // BPF_PROG_TYPE 宏展开
};

提供:get_func_proto(helper 原型)、is_valid_access(context 访问)、check_attach 等。


六、程序执行引擎

6.1 bpf_prog_run

1
2
3
4
5
// include/linux/filter.h
static __always_inline u32 bpf_prog_run(const struct bpf_prog *prog, const void *ctx)
{
return prog->bpf_func(ctx, prog->insnsi);
}
  • JIT 模式prog->bpf_func 指向 JIT 编译后的机器码入口
  • 解释器模式prog->bpf_func = __bpf_prog_run,内部调用 ___bpf_prog_run()

6.2 eBPF 解释器

core.c___bpf_prog_run() 使用 computed goto 跳转表实现高效解释执行:

1
2
3
4
5
6
7
8
9
10
11
static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
{
static const void * const jumptable[256] = { ... };
select_insn:
goto *jumptable[insn->code];
ALU(ADD, +)
ALU(SUB, -)
// ... 所有 eBPF 指令
BPF_JMP | BPF_CALL: // helper 调用
BPF_JMP | BPF_EXIT: // 程序退出,返回 R0
}

6.3 JIT 编译

架构相关 JIT 位于 arch/arm64/net/bpf_jit_comp.c(RK3588):

  • 将 eBPF 指令翻译为 ARM64 机器码
  • 处理 tail call、helper 调用、map 访问
  • 支持 hardening(常量 blinding)
  • 生成 line info 用于 stack trace

控制接口:

1
2
3
/proc/sys/net/core/bpf_jit_enable    # 0=解释器 1=JIT 2=JIT+debug
/proc/sys/net/core/bpf_jit_harden # hardening 级别
/proc/sys/net/core/bpf_jit_kallsyms # JIT 代码加入 kallsyms

6.4 Dispatcher — 避免间接调用

dispatcher.c 为多 prog 挂载点生成 multiway branch 代码,将 indirect call 转为 direct call,在 retpoline 启用时显著提升性能。


七、Map 映射类型

7.1 通用 Map

Map 类型 源文件 特点
HASH hashtab.c 通用 hash 表,O(1) 查找
ARRAY arraymap.c 索引数组,key 为 u32
PERCPU_HASH hashtab.c per-CPU hash,无锁
PERCPU_ARRAY arraymap.c per-CPU 数组
LRU_HASH hashtab.c LRU 淘汰策略
LPM_TRIE lpm_trie.c 最长前缀匹配(路由表)
QUEUE / STACK queue_stack_maps.c 队列/栈语义
RINGBUF ringbuf.c 单生产者/单消费者 ring buffer
BLOOM_FILTER bloom_filter.c 布隆过滤器

7.2 特殊用途 Map

Map 类型 用途
PROG_ARRAY tail call 跳转表
PERF_EVENT_ARRAY perf event 重定向
CGROUP_ARRAY cgroup 引用数组
ARRAY_OF_MAPS / HASH_OF_MAPS map 嵌套
STACK_TRACE 存储栈回溯
CGROUP_STORAGE per-cgroup 数据
SK_STORAGE per-socket 数据
TASK_STORAGE per-task 数据
INODE_STORAGE per-inode 数据

7.3 网络 Map

Map 类型 源文件 用途
DEVMAP / DEVMAP_HASH devmap.c XDP 重定向到网络设备
CPUMAP cpumap.c XDP 转发到其他 CPU
XSKMAP (net/xdp) AF_XDP socket 重定向
SOCKMAP / SOCKHASH (net/core) socket 重定向/负载均衡
REUSEPORT_SOCKARRAY reuseport_array.c SO_REUSEPORT 选 socket

八、Helper 辅助函数

8.1 通用 Helper(helpers.c)

Helper 功能
bpf_map_lookup_elem 查找 map 元素
bpf_map_update_elem 更新 map 元素
bpf_map_delete_elem 删除 map 元素
bpf_map_push/pop/peek_elem queue/stack 操作
bpf_get_smp_processor_id 当前 CPU ID
bpf_get_current_pid_tgid 当前 pid/tgid
bpf_get_current_comm 当前进程名
bpf_ktime_get_ns/boot_ns/coarse_ns 时间获取
bpf_get_prandom_u32 伪随机数
bpf_printk 内核日志输出

8.2 Helper 原型约束

每个 helper 通过 bpf_func_proto 声明参数/返回类型约束,供 verifier 检查:

1
2
3
4
5
6
const struct bpf_func_proto bpf_map_lookup_elem_proto = {
.func = bpf_map_lookup_elem,
.ret_type = RET_PTR_TO_MAP_VALUE_OR_NULL,
.arg1_type = ARG_CONST_MAP_PTR,
.arg2_type = ARG_PTR_TO_MAP_KEY,
};

8.3 类型特定 Helper

各 prog type 在各自 verifier ops 的 get_func_proto() 中提供额外 helper,例如:

  • XDPbpf_xdp_adjust_head/meta/room
  • Tracingbpf_probe_read*bpf_get_stackid
  • Cgroupbpf_get_current_cgroup_id
  • LSMbpf_inode_storage_*

九、BTF 类型系统

9.1 概述

BTF(BPF Type Format)描述 BPF 程序/ map 的数据类型,存储在 ELF .BTF section 或独立加载。

btf.c 功能:

  • 解析 BTF type section 和 string section
  • 两遍验证(收集 type_id → 检查引用)
  • 内核 vmlinux BTF 支持(CONFIG_DEBUG_INFO_BTF
  • CO-RE relocation 处理

9.2 主要用途

用途 说明
fentry/fexit 通过 BTF 获取函数签名,实现 typed 参数访问
CO-RE Compile Once — Run Everywhere,跨内核版本移植
map 类型安全 map 的 key/value BTF 类型检查
struct_ops 替换内核 struct ops(如 TCP CC)

9.3 BTF ID 集合

1
2
3
4
// bpf_lsm.c 示例
BTF_SET_START(bpf_lsm_hooks)
#include <linux/lsm_hook_defs.h> // 自动生成所有 LSM hook 的 BTF ID
BTF_SET_END(bpf_lsm_hooks)

十、程序挂载与 Trampoline

10.1 挂载方式对比

方式 接口 特点
BPF Link BPF_LINK_CREATE 推荐,统一生命周期
BPF_PROG_ATTACH 旧接口 逐步被 link 替代
Trampoline fentry/fexit/modify_return 基于 ftrace direct call

10.2 BPF Trampoline

trampoline.c 实现函数级 BPF 挂载:

1
2
3
4
5
6
被追踪函数入口
→ ftrace direct call
→ bpf_trampoline(跳板代码)
→ BPF_PROG_TYPE_TRACING (fentry)
→ 原始函数(可选)
→ BPF_PROG_TYPE_TRACING (fexit)

支持类型:

  • BPF_TRACE_FENTRY — 函数入口
  • BPF_TRACE_FEXIT — 函数返回
  • BPF_MODIFY_RETURN — 修改返回值

10.3 程序类型与挂载点

Prog Type 典型挂载点
BPF_PROG_TYPE_SOCKET_FILTER 经典 socket 过滤
BPF_PROG_TYPE_KPROBE kprobe 探针
BPF_PROG_TYPE_TRACEPOINT 内核 tracepoint
BPF_PROG_TYPE_XDP 网络驱动 XDP hook
BPF_PROG_TYPE_SCHED_CLS/ACT TC (traffic control)
BPF_PROG_TYPE_CGROUP_* cgroup 各 hook 点
BPF_PROG_TYPE_LSM LSM 安全钩子
BPF_PROG_TYPE_TRACING fentry/fexit/raw_tp
BPF_PROG_TYPE_STRUCT_OPS 内核 struct ops
BPF_PROG_TYPE_SYSCALL 允许 bpf() 的 prog

十一、Cgroup BPF 与 LSM

11.1 Cgroup BPF

cgroup.c 实现将 BPF 程序附加到 cgroup 层级:

1
2
3
4
5
6
7
cgroup/
├── BPF_CGROUP_INET_INGRESS/EGRESS ← 网络 ingress/egress
├── BPF_CGROUP_INET4/6_BIND/CONNECT ← socket 操作
├── BPF_CGROUP_UDP4/6_SENDMSG/RECVMSG
├── BPF_CGROUP_SOCK_OPS ← socket 生命周期
├── BPF_CGROUP_DEVICE ← 设备访问控制
└── BPF_CGROUP_SYSCTL ← sysctl 访问控制

执行路径:

1
2
3
4
5
// cgroup.c
bpf_prog_run_array_cg(cgrp, atype, ctx, run_prog, retval, ret_flags)
→ 遍历 effective[atype] prog 数组
→ 依次 bpf_prog_run(prog, ctx)
→ 累积返回值(如 -EPERM 拒绝)

11.2 BPF LSM

bpf_lsm.c 为每个 LSM hook 创建 nop 函数 bpf_lsm_<hookname>(),BPF 程序通过 trampoline 挂载:

  • 支持 MAC(强制访问控制)策略
  • 支持 Audit 策略
  • 通过 BTF 匹配 hook 签名
  • 部分 hook 通过 cgroup shim 执行

12.1 BPF Iterator

bpf_iter.c 提供内核对象迭代框架:

  • 用户通过 BPF_ITER_CREATE 创建 iterator link
  • 读取 /proc/self/fd/<iter_fd> 触发 BPF prog 对每个对象执行
  • 预置 target:map、prog、task、cgroup、link

预加载迭代器(preload/)在启动时自动注册 maps.debugprogs.debug link。

12.2 bpffs 文件系统

inode.c 实现 bpffs 伪文件系统:

1
2
3
mount -t bpf bpf /sys/fs/bpf
echo <prog_fd> > /sys/fs/bpf/my_prog # pin prog
cat /sys/fs/bpf/my_prog # 获取 fd

支持 pin prog、map、link,实现跨进程共享和持久化。


十三、网络相关 Map

13.1 Devmap — 设备重定向

devmap.c 实现 XDP 帧重定向到指定网络设备:

1
2
3
XDP prog: bpf_redirect_map(devmap, ifindex, 0)
→ 查找 devmap[ifindex]
→ 将 skb/xdp_frame 发送到目标设备 TX 队列

13.2 CPUMAP — 跨 CPU 转发

cpumap.c 实现 XDP 帧转发到其他 CPU 处理:

1
2
3
4
XDP prog: bpf_redirect_map(cpumap, cpu_id, 0)
→ 将帧放入目标 CPU 的 cpumap 队列
→ 目标 CPU kthread 取出并运行 BPF prog
→ 最终通过 devmap 或直接 TX 发送

十四、完整加载执行时序

kprobe BPF 程序 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[用户空间 - libbpf]
bpf_object__open("prog.o")
bpf_object__load()
→ bpf(BPF_BTF_LOAD) // 加载 BTF
→ bpf(BPF_MAP_CREATE) × N // 创建 maps
→ bpf(BPF_PROG_LOAD) // 加载 prog(含 verifier)
→ bpf(BPF_LINK_CREATE) // 创建 kprobe link

[内核 - BPF_PROG_LOAD]
bpf_prog_load()
→ copy insns from userspace
→ bpf_check() // verifier 验证
→ bpf_int_jit_compile() // ARM64 JIT
→ 返回 prog fd

[内核 - BPF_LINK_CREATE]
link_create()
→ 注册 kprobe/uprobe/tracepoint
→ 关联 prog 到挂载点

[内核 - 运行时触发]
被 probe 的函数执行
→ kprobe handler
→ bpf_prog_run(prog, ctx)
→ JIT 机器码 / 解释器
→ helper 调用(如 bpf_printk)
→ 返回 R0

[用户空间 - 读取结果]
读取 trace_pipe / ringbuf map / perf buffer

十五、RK3588 平台调试建议

15.1 确认 BPF 支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 检查内核配置
zcat /proc/config.gz | grep -E 'BPF|BTF|CGROUP_BPF'

# 关键项(ARM64 RK3588 典型配置)
# CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_JIT=y
# CONFIG_HAVE_EBPF_JIT=y
# CONFIG_DEBUG_INFO_BTF=y
# CONFIG_CGROUP_BPF=y

# 检查 JIT 状态
cat /proc/sys/net/core/bpf_jit_enable # 应为 1

# 检查 bpffs
mount | grep bpf
ls /sys/fs/bpf/

15.2 常用调试工具

工具 用途
bpftool prog/map/link show 查看已加载对象
bpftool prog dump xlated/jited 查看 JIT 代码
bpftool btf dump 查看 BTF 信息
bpftool prog tracelog 查看验证器日志
bpf_printk / trace_pipe prog 内日志

15.3 RK3588 典型应用场景

场景 Prog Type 说明
网络包过滤 XDP / SCHED_CLS 在驱动层或 TC 层过滤
容器网络策略 CGROUP_SKB/SOCK cgroup 级网络管控
内核函数追踪 TRACING (fentry) 基于 BTF 的函数追踪
系统调用审计 TRACEPOINT / LSM 安全审计
性能 profiling PERF_EVENT perf 集成

15.4 注意事项

  • RK3588 默认可能启用 CONFIG_BPF_UNPRIV_DEFAULT_OFF,需要 root/CAP_BPF 权限
  • big.LITTLE 环境下 per-CPU map 在 A76/A55 核心间独立,注意数据一致性
  • JIT 代码位于 module_alloc 区域,可通过 bpftool prog dump jited 查看
  • 验证失败时查看 dmesgbpftool prog tracelog 获取详细日志

十六、总结

kernel/bpf 是 Linux 内核 eBPF 子系统的核心实现,其设计要点:

  1. Verifier — 静态分析保证安全,是指令级类型检查器而非简单 sandbox
  2. Map — 提供 prog 间、内核与用户间的共享状态机制,多种专用 map 适配不同场景
  3. JIT — ARM64 JIT 将字节码编译为本地代码,接近原生性能
  4. BTF — 类型系统支撑 CO-RE、fentry、struct_ops 等高级特性
  5. Link — 统一的挂载生命周期管理
  6. Trampoline — 基于 ftrace 的高效函数级挂载

在 RK3588 平台上,eBPF 可用于网络加速(XDP/TC)、容器安全(cgroup/LSM)、内核可观测性(fentry/tracepoint)等,是嵌入式 Linux 系统调优和安全加固的重要工具。


附录:源文件完整清单

文件 行数 分类
verifier.c 15,726 验证器
btf.c 8,045 BTF 类型
syscall.c 5,378 系统调用
core.c 2,781 解释器/JIT 入口
cgroup.c 2,578 Cgroup BPF
hashtab.c 2,547 Hash map
helpers.c 1,745 Helper 函数
arraymap.c 1,359 Array map
trampoline.c 1,080 Trampoline
devmap.c 1,137 Devmap
task_iter.c 864 Task 迭代器
cpumap.c 820 CPUMAP
inode.c 820 bpffs
ringbuf.c 795 Ring buffer
bpf_iter.c 778 Iterator 框架
lpm_trie.c 745 LPM trie
offload.c 709 HW offload
bpf_local_storage.c 707 Local storage
bpf_struct_ops.c 701 Struct ops
dispatcher.c ~174 Dispatcher
bpf_lsm.c ~357 LSM

kernel/cgroup Cgroup 机制与原理详解

kernel/cgroup Cgroup 机制与原理详解

源码路径rk3588/kernel-6.1/kernel/cgroup/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE)

该目录实现 Linux cgroup(Control Group) 子系统的核心框架及部分内置控制器。Cgroup 将进程组织成层次化分组,对 CPU、内存、I/O、设备等资源进行限制、隔离与统计,是容器(Docker/LXC)、systemd、Android 资源管控的基础。

说明:cgroup 采用插件化设计。本目录提供统一框架(层级管理、任务迁移、kernfs 接口、统计等),各资源控制器分散在内核其他目录实现,通过 cgroup_subsys 注册接入。


目录


一、源码目录结构

1.1 编译单元(Makefile)

1
2
3
4
5
6
7
8
obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o freezer.o

obj-$(CONFIG_CGROUP_FREEZER) += legacy_freezer.o
obj-$(CONFIG_CGROUP_PIDS) += pids.o
obj-$(CONFIG_CGROUP_RDMA) += rdma.o
obj-$(CONFIG_CPUSETS) += cpuset.o
obj-$(CONFIG_CGROUP_MISC) += misc.o
obj-$(CONFIG_CGROUP_DEBUG) += debug.o
编译单元 源文件 规模 功能
核心框架 cgroup.c ~7,083 行 层级管理、css/css_set、迁移、kernfs、挂载
Cpuset cpuset.c ~4,246 行 CPU/内存节点亲和性与隔离
Cgroup v1 cgroup-v1.c ~1,308 行 v1 挂载、release agent、pidlist
递归统计 rstat.c ~549 行 可扩展 per-CPU 递归资源统计
RDMA rdma.c ~610 行 RDMA 资源限制
Misc misc.c ~424 行 杂项资源(如 AMD SEV ASID)
Pids pids.c ~387 行 进程数限制
Debug debug.c ~381 行 调试控制器
Legacy Freezer legacy_freezer.c ~487 行 v1 freezer 控制器
Freezer freezer.c ~323 行 v2 cgroup 冻结
Namespace namespace.c ~151 行 cgroup 命名空间
内部头文件 cgroup-internal.h ~299 行 框架内部类型与 API

1.2 子系统清单(cgroup_subsys.h)

所有控制器在 include/linux/cgroup_subsys.h 中枚举:

子系统 实现位置 功能
cpuset kernel/cgroup/cpuset.c CPU/内存节点绑定
cpu kernel/sched/core.c CFS 带宽/权重控制
cpuacct kernel/sched/cpuacct.c CPU 使用时间统计
io block/blk-cgroup.c 块 I/O 带宽/权重
memory mm/memcontrol.c 内存限制与回收
devices security/device_cgroup.c 设备访问控制
freezer kernel/cgroup/freezer.c 进程冻结(v2)
net_cls net/core/ 网络 classid 标记
perf_event kernel/events/core.c perf 事件限制
net_prio net/core/ 网络优先级
hugetlb mm/hugetlb_cgroup.c 大页内存限制
pids kernel/cgroup/pids.c 进程数限制
rdma kernel/cgroup/rdma.c RDMA 资源限制
misc kernel/cgroup/misc.c 杂项硬件资源
debug kernel/cgroup/debug.c 调试接口

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ systemd / Docker / crictl / echo > cgroup.procs │
└──────────────────────────┬──────────────────────────────────┘
│ 读写 /sys/fs/cgroup/ (kernfs)
┌──────────────────────────▼──────────────────────────────────┐
│ cgroup.c + cgroup-v1.c (核心框架) │
│ cgroup_root │ cgroup │ css_set │ 迁移 │ 挂载 │ 文件接口 │
└──────┬──────────┬──────────┬──────────┬─────────────────────┘
│ │ │ │
┌──────▼──┐ ┌─────▼────┐ ┌──▼──────┐ ┌─▼──────────────┐
│ cpuset │ │ cpu/mem │ │ freezer │ │ pids/rdma/ │
│ (本目录) │ │ (sched/mm)│ │ (本目录)│ │ misc (本目录)│
└──────┬──┘ └─────┬────┘ └──┬──────┘ └─┬──────────────┘
│ │ │ │
└──────────┴──────────┴──────────┘

┌────────────▼────────────┐
│ task_struct │
│ task->cgroups (css_set)│
│ fork/exit 钩子 │
└─────────────────────────┘

2.2 关键锁

1
2
3
4
// cgroup.c
DEFINE_MUTEX(cgroup_mutex); // 层级结构修改主锁
DEFINE_SPINLOCK(css_set_lock); // task->cgroups、css_set 链表
DEFINE_PERCPU_RWSEM(cgroup_threadgroup_rwsem); // 线程组迁移
保护对象
cgroup_mutex cgroup 树创建/销毁、控制器 enable/disable、迁移准备
css_set_lock task->cgroups 指针、css_set 哈希表、任务链表
cgroup_threadgroup_rwsem 线程组级 attach 操作

2.3 默认层级

1
2
3
// cgroup.c
struct cgroup_root cgrp_dfl_root; // unified v2 默认层级
LIST_HEAD(cgroup_roots); // 所有层级根(含 v1 多层级)
  • cgroup v2(default hierarchy):单一统一层级,挂载于 /sys/fs/cgroup
  • cgroup v1(legacy):可多层级,每层级绑定不同控制器组合

三、核心数据结构

3.1 struct cgroup — 控制组节点

1
2
3
4
5
6
7
8
9
10
11
12
13
// include/linux/cgroup-defs.h
struct cgroup {
struct cgroup_subsys_state self; // 自身 css(ss == NULL)
unsigned long flags; // CGRP_FREEZE, CGRP_FROZEN 等
int level; // 树深度(root = 0)
struct kernfs_node *kn; // kernfs 目录节点
struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];
u16 subtree_control; // 子 cgroup 启用的控制器
u16 subtree_ss_mask; // 实际生效的控制器掩码
struct cgroup_root *root;
struct cgroup *dom_cgrp; // 域 cgroup(threaded 模式)
// ...
};

每个 cgroup 在文件系统中对应一个目录(如 /sys/fs/cgroup/system.slice/)。

3.2 struct cgroup_subsys_state (css) — 控制器状态

1
2
3
4
5
6
7
8
9
10
11
struct cgroup_subsys_state {
struct cgroup *cgroup; // 所属 cgroup
struct cgroup_subsys *ss; // 所属子系统
struct percpu_ref refcnt; // 引用计数
struct list_head sibling; // 兄弟 css 链表
struct list_head children; // 子 css 链表
struct cgroup_subsys_state *parent;
int id; // 子系统内唯一 ID
u64 serial_nr; // 全局递增序号
// ...
};

每个 (cgroup, subsystem) 对一个 css 实例。例如启用 memory 控制器的 cgroup 有 memory css。

3.3 struct css_set — 任务 cgroup 成员关系

1
2
3
4
5
6
7
8
9
struct css_set {
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; // 各子系统 css 指针
refcount_t refcount;
struct css_set *dom_cset; // 域 css_set(threaded 模式)
struct cgroup *dfl_cgrp; // 默认层级 cgroup
struct list_head tasks; // 属于此 cset 的任务链表
struct list_head cgrp_links; // 与 cgroup 的 M:N 关联
// ...
};

设计要点:一个任务的 task->cgroups 指向一个 css_set。多个任务可共享同一 css_set(当它们在所有层级上的 cgroup 成员关系完全相同时),fork 时只需增减 css_set 引用计数,避免逐控制器更新。

3.4 struct cgroup_root — 层级根

1
2
3
4
5
6
7
8
9
struct cgroup_root {
struct kernfs_root *kf_root; // kernfs 根
struct cgroup cgrp; // 根 cgroup
int hierarchy_id;
u16 subsys_mask; // v1: 挂载的控制器掩码
unsigned int flags; // CGRP_ROOT_* 标志
char release_agent_path[PATH_MAX]; // v1 release agent
// ...
};

3.5 struct cgroup_subsys — 控制器插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct cgroup_subsys {
struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);
int (*css_online)(struct cgroup_subsys_state *css);
void (*css_offline)(struct cgroup_subsys_state *css);
void (*css_free)(struct cgroup_subsys_state *css);
int (*can_attach)(struct cgroup_taskset *tset);
void (*attach)(struct cgroup_taskset *tset);
void (*fork)(struct task_struct *task);
void (*exit)(struct task_struct *task);
struct cftype *dfl_cftypes; // v2 接口文件
struct cftype *legacy_cftypes; // v1 接口文件
const char *name;
bool implicit_on_dfl; // v2 隐式启用
bool threaded; // 支持 threaded 模式
// ...
};
1
2
3
cgroup A ←── cgrp_cset_link ──→ css_set X ←── task 1, task 2
cgroup B ←── cgrp_cset_link ──→ css_set X
cgroup C ←── cgrp_cset_link ──→ css_set Y ←── task 3

一个 css_set 可关联多个 cgroup(v1 多层级),一个 cgroup 也可关联多个 css_set(不同任务组合)。


四、Cgroup v1 与 v2

4.1 对比

特性 cgroup v1 cgroup v2(Unified)
层级数量 多个独立层级 单一统一层级
控制器绑定 挂载时绑定到层级 通过 cgroup.subtree_control 按需启用
进程规则 父子可同时有进程 默认 domain 模式:非 root 内部不能有进程
接口路径 每控制器独立文件 统一 cgroup.* 接口
挂载点 /sys/fs/cgroup/<controller> /sys/fs/cgroup
文件系统 cgroup cgroup2
Freezer legacy_freezer(独立控制器) 内置 cgroup.freeze

4.2 v2 Domain 与 Threaded 模式

  • Domain cgroup:资源隔离边界,可启用所有控制器,内部不应直接持有进程(leaf 除外)
  • Threaded cgroup:仅支持 cpumemorypidsperf_event 等 threaded 控制器,用于线程级分组

通过 cgroup.type 文件设置:domaindomain threadedthreaded

4.3 v2 控制器启用流程

1
2
3
4
5
6
# 在父 cgroup 启用 memory 和 cpu 控制器供子 cgroup 使用
echo "+memory +cpu" > /sys/fs/cgroup/cgroup.subtree_control

# 在子 cgroup 中设置限制
echo "max 200000" > /sys/fs/cgroup/mygroup/cpu.max
echo "256M" > /sys/fs/cgroup/mygroup/memory.max

五、子系统注册与生命周期

5.1 注册机制

1
2
3
4
// cgroup.c — 编译期生成子系统指针数组
struct cgroup_subsys *cgroup_subsys[] = {
#include <linux/cgroup_subsys.h> // SUBSYS(cpu) → &cpu_cgrp_subsys
};

每个控制器在各自源文件中定义:

1
2
3
4
5
6
7
8
9
10
11
12
// kernel/sched/core.c
struct cgroup_subsys cpu_cgrp_subsys = {
.css_alloc = cpu_css_alloc,
.css_online = cpu_css_online,
.css_offline = cpu_css_offline,
.css_free = cpu_css_free,
.can_attach = cpu_cgroup_can_attach,
.attach = cpu_cgroup_attach,
.fork = cpu_cgroup_fork,
.dfl_cftypes = cpu_files,
.name = "cpu",
};

5.2 css 生命周期

1
2
3
4
5
6
7
8
9
cgroup 创建子目录
→ css_alloc(parent_css) // 分配子 css
→ css_online(css) // 激活,CSS_ONLINE 置位
→ 用户写入接口文件配置限制

cgroup 删除(rmdir)
→ css_offline(css) // 停用,拒绝新任务
→ css_released(css) // 引用归零
→ css_free(css) // 释放(可能异步 via workqueue)

5.3 任务生命周期钩子

1
2
3
4
5
6
7
// include/linux/cgroup.h
void cgroup_fork(struct task_struct *p); // fork 时继承 css_set
int cgroup_can_fork(...); // fork 前检查(pids 等)
void cgroup_post_fork(...); // fork 后通知控制器
void cgroup_exit(struct task_struct *p); // 退出时递减计数
void cgroup_release(struct task_struct *p);
void cgroup_free(struct task_struct *p);

六、任务迁移机制

6.1 迁移 API

1
2
3
// 用户写入 cgroup.procs 或 cgroup.threads 触发
int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
bool threadgroup);

6.2 迁移流程

1
2
3
4
5
6
7
8
9
10
cgroup_attach_task()
├── cgroup_migrate_add_src() // 收集源 css_set
├── cgroup_migrate_prepare_dst() // 预创建/查找目标 css_set
└── cgroup_migrate()
├── cgroup_migrate_add_task() // 任务加入 mg_tasks 链表
└── cgroup_migrate_execute()
├── can_attach() // 各控制器预检查
├── css_set_move_task() // 切换 task->cgroups(commit)
├── attach() // 通知控制器迁移完成
└── post_attach()

核心 commit 点(cgroup_migrate_execute):

1
2
3
4
5
6
7
8
spin_lock_irq(&css_set_lock);
list_for_each_entry(cset, &tset->src_csets, mg_node) {
list_for_each_entry_safe(task, tmp_task, &cset->mg_tasks, cg_list) {
css_set_move_task(task, from_cset, to_cset, true);
cgroup_freezer_migrate_task(task, from_cset->dfl_cgrp, to_cset->dfl_cgrp);
}
}
spin_unlock_irq(&css_set_lock);

原子性保证:所有 can_attach() 通过后才会 commit;任一失败则全部回滚。

6.3 常用迁移方式

方式 接口 说明
写入 procs echo PID > cgroup.procs 迁移进程(thread group leader)
写入 threads echo PID > cgroup.threads 迁移单个线程
clone 直接加入 clone3(CLONE_INTO_CGROUP) 创建时直接进入目标 cgroup
批量迁移 cgroup.transfer_tasks v1 控制器间迁移

七、递归统计 rstat

7.1 设计目标

rstat.c 实现 可扩展递归统计(scalable recursive statistics)

  • 每个 cgroup 维护 per-CPU 统计(cgroup_rstat_cpu
  • 写入时仅标记本 cgroup 为 “updated”
  • 读取时惰性向上传播到祖先,复杂度 O(活跃子树大小) 而非 O(总子树)

7.2 数据结构

1
2
3
4
5
6
7
struct cgroup_rstat_cpu {
struct u64_stats_sync bsync;
struct cgroup_base_stat bstat; // CPU 时间等基础统计
struct cgroup_base_stat last_bstat; // 上次读取快照
struct cgroup *updated_children; // 有更新的子 cgroup 链表
struct cgroup *updated_next;
};

7.3 使用场景

  • cgroup.stat — 显示 CPU 使用统计
  • memory.currentmemory.peak 等(memory 控制器扩展 rstat)
  • PSI(Pressure Stall Information)压力指标

八、本目录内置控制器

8.1 Cpuset(cpuset.c)

最复杂的内置控制器(~4,246 行),管理 CPU 和内存节点亲和性:

接口文件 功能
cpuset.cpus 允许使用的 CPU 集合
cpuset.mems 允许使用的内存节点
cpuset.cpu_exclusive CPU 独占
cpuset.mem_exclusive 内存节点独占
cpuset.cpus.effective 有效 CPU(考虑父级约束)

RK3588 big.LITTLE 场景下,cpuset 可将任务绑定到 A76 或 A55 核心簇。

8.2 Freezer(freezer.c + legacy_freezer.c)

v2 freezerfreezer.c):

接口 功能
cgroup.freeze 写 1 冻结 cgroup 内所有任务
cgroup.events 读取 frozen 状态

冻结机制:设置 CGRP_FREEZE 标志,对任务发送 SIGSTOP,CGRP_FROZEN 表示实际已冻结。支持递归:所有子 cgroup 都 frozen 时父 cgroup 也标记 frozen。

v1 legacy_freezerlegacy_freezer.c):独立的 freezer 控制器,freezer.state 文件。

8.3 Pids(pids.c)

限制 cgroup 内进程数量:

接口 功能
pids.max 最大进程数(”max” = 无限制)
pids.current 当前进程数
pids.events 超限事件计数

fork() 时通过 can_fork() 检查,超限返回 -EAGAIN

8.4 RDMA(rdma.c)

限制 RDMA 资源使用:

资源 说明
hca_handle HCA 句柄数
hca_object HCA 对象数

按 cgroup × RDMA 设备维护 resource pool。

8.5 Misc(misc.c)

杂项硬件资源限制(如 AMD SEV/SEV-ES ASID 数量),采用 Limits 分配模型。

8.6 Debug(debug.c)

调试控制器,仅在 cgroup_debug=1 时启用,提供额外诊断接口。


九、分散实现的控制器

9.1 CPU 控制器(kernel/sched/core.c)

接口 功能
cpu.max CFS 带宽上限(v2)
cpu.weight 相对权重(默认 100)
cpu.stat usage、user、system 等
cpu.pressure CPU PSI 压力

与 CFS 调度器深度集成,通过 task_group 实现带宽控制。

9.2 Memory 控制器(mm/memcontrol.c)

接口 功能
memory.max 内存硬上限
memory.high 内存软上限(触发 reclaim)
memory.current 当前使用量
memory.swap.max swap 上限
memory.events oom、max 等事件
memory.stat 详细内存统计

通过 mem_cgroup 结构跟踪 page cache、anon、swap 等。

9.3 IO 控制器(block/blk-cgroup.c)

接口 功能
io.max 设备级 I/O 带宽/IOPS 上限
io.weight 相对 I/O 权重
io.stat 读写统计
io.pressure I/O PSI 压力

9.4 其他

控制器 位置 功能
cpuacct kernel/sched/cpuacct.c CPU 时间记账(v1 为主)
devices security/device_cgroup.c 字符/块设备访问 whitelist
net_cls net/core/ 设置 sk->classid
net_prio net/core/ 设置 socket 优先级
hugetlb mm/hugetlb_cgroup.c 大页内存限制
perf_event kernel/events/core.c perf 事件作用域

十、Cgroup Namespace

namespace.c 实现 cgroup namespace(CLONE_NEWCGROUP):

1
2
3
4
5
struct cgroup_namespace {
struct ns_common ns;
struct user_namespace *user_ns;
struct css_set *root_cset; // 命名空间内的"根" css_set
};
  • 容器内 /proc/self/cgroup 显示相对于 namespace root 的路径
  • 创建需要 CAP_SYS_ADMIN
  • 与 mount namespace 配合实现容器 cgroup 视图隔离

十一、用户空间接口

11.1 v2 统一接口(每个 cgroup 目录)

文件 功能
cgroup.procs 读写:迁移进程
cgroup.threads 读写:迁移线程
cgroup.controllers 只读:可用控制器
cgroup.subtree_control 读写:子 cgroup 启用的控制器
cgroup.type domain / threaded 类型
cgroup.max.depth 最大子 cgroup 深度
cgroup.max.descendants 最大子 cgroup 数量
cgroup.stat CPU 使用统计
cgroup.freeze 冻结/解冻
cgroup.events frozen、populated 等事件
cpu.max / memory.max / … 各控制器限制

11.2 常用操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建 cgroup 并设置内存限制
mkdir /sys/fs/cgroup/myapp
echo "+memory +cpu" > /sys/fs/cgroup/cgroup.subtree_control
echo "256M" > /sys/fs/cgroup/myapp/memory.max
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max # 50ms/100ms = 50% CPU
echo $$ > /sys/fs/cgroup/myapp/cgroup.procs

# 查看 cgroup 路径
cat /proc/self/cgroup

# 冻结 cgroup
echo 1 > /sys/fs/cgroup/myapp/cgroup.freeze

# cpuset 绑定到 CPU 4-7(A76 大核)
echo "4-7" > /sys/fs/cgroup/myapp/cpuset.cpus
echo 0 > /sys/fs/cgroup/myapp/cpuset.mems
echo $$ > /sys/fs/cgroup/myapp/cgroup.procs

11.3 bpffs 与 cgroup BPF

cgroup BPF 程序(kernel/bpf/cgroup.c)可附加到 cgroup 的多种 hook 点(ingress/egress/bind/connect 等),实现网络策略。挂载通过 BPF_LINK_CREATE 或 cgroup 目录下的 BPF 接口。


十二、完整任务加入 Cgroup 时序

v2 写入 cgroup.procs 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[用户空间]
echo 1234 > /sys/fs/cgroup/mygroup/cgroup.procs

[内核 - VFS/kernfs]
cgroup_procs_write()
→ cgroup_attach_task(mygroup, task, true)

[内核 - 迁移准备]
cgroup_migrate_add_src(task_css_set(task), mygroup, &mgctx)
cgroup_migrate_prepare_dst(&mgctx)
→ 查找或创建目标 css_set

[内核 - 预检查]
cgroup_migrate_execute(&mgctx)
→ memory_can_attach() // 检查 memory 限制
→ cpu_can_attach() // 检查 cpu 约束
→ pids_can_attach() // 检查 pids 限制
→ cpuset_can_attach() // 检查 CPU 亲和性

[内核 - Commit]
→ css_set_move_task(task, from, to, true)
→ task->cgroups = to_cset
→ cgroup_freezer_migrate_task() // 处理 freeze 状态

[内核 - 通知控制器]
→ memory_attach() // 更新 memcg 计数
→ cpu_attach() // 更新 task_group
→ cpuset_attach() // 更新 CPU 亲和性 mask

[内核 - 调度影响]
下次 schedule() 时 task 在新 cgroup 的 CPU 权重/带宽下运行
内存分配计入新 memcg

十三、RK3588 / Android 平台应用

13.1 典型 cgroup 层级(Android/systemd)

1
2
3
4
5
6
7
/sys/fs/cgroup/
├── init.scope
├── system.slice/
│ ├── docker.service/
│ └── ...
├── user.slice/
└── ...

Android 使用 cgroup v2 管理应用进程资源(通过 libprocessgroup):

场景 使用的控制器
应用内存限制 memory.max
CPU 带宽控制 cpu.max
后台冻结 cgroup.freeze
核心绑定(游戏/性能模式) cpuset.cpus
进程数限制 pids.max

13.2 RK3588 big.LITTLE cpuset 配置

1
2
3
4
5
6
7
8
# 性能组:绑定 4 个 A76 大核 (CPU 4-7)
echo "4-7" > /sys/fs/cgroup/performance/cpuset.cpus

# 节能组:绑定 4 个 A55 小核 (CPU 0-3)
echo "0-3" > /sys/fs/cgroup/background/cpuset.cpus

# 查看有效 CPU
cat /sys/fs/cgroup/mygroup/cpuset.cpus.effective

13.3 调试命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看进程 cgroup  membership
cat /proc/<PID>/cgroup

# 查看 cgroup 内进程
cat /sys/fs/cgroup/<path>/cgroup.procs

# 查看内存使用
cat /sys/fs/cgroup/<path>/memory.current
cat /sys/fs/cgroup/<path>/memory.stat

# 查看 CPU 统计
cat /sys/fs/cgroup/<path>/cpu.stat

# 查看 PSI 压力
cat /sys/fs/cgroup/<path>/memory.pressure
cat /sys/fs/cgroup/<path>/cpu.pressure
cat /sys/fs/cgroup/<path>/io.pressure

13.4 确认配置

1
2
zcat /proc/config.gz | grep -E 'CGROUP|CPUSET|MEMCG|BLK_CGROUP'
mount | grep cgroup

RK3588 典型配置:

  • CONFIG_CGROUPS=y
  • CONFIG_CGROUP_V2=y(默认 unified hierarchy)
  • CONFIG_MEMCG=y
  • CONFIG_CGROUP_SCHED=y(cpu 控制器)
  • CONFIG_BLK_CGROUP=y(io 控制器)
  • CONFIG_CPUSETS=y

十四、总结

kernel/cgroup 是 Linux 资源管控的核心基础设施:

  1. 统一框架cgroup.c)— 层级管理、css_set 优化、任务迁移、kernfs 接口
  2. 插件化控制器 — 通过 cgroup_subsys 注册,分散在各子系统实现
  3. v2 Unified Hierarchy — 单一层级、按需启用控制器、domain/threaded 模式
  4. css_set 共享 — 相同 cgroup 组合的任务共享 css_set,优化 fork 性能
  5. 原子迁移 — can_attach → commit → attach 三阶段保证一致性
  6. rstat — 高效递归统计,支撑 cgroup.stat 和 PSI

在 RK3588 / Android 平台上,cgroup 是进程资源隔离与限制的基础,与调度器(cpu)、内存管理(memory)、块 I/O(io)深度协同,是容器化和系统资源管控的基石。


附录:源文件完整清单

文件 行数 分类
cgroup.c 7,083 核心框架
cpuset.c 4,246 Cpuset 控制器
cgroup-v1.c 1,308 v1 兼容
rdma.c 610 RDMA 控制器
rstat.c 549 递归统计
legacy_freezer.c 487 v1 Freezer
misc.c 424 Misc 控制器
pids.c 387 Pids 控制器
debug.c 381 Debug 控制器
freezer.c 323 v2 Freezer
cgroup-internal.h 299 内部头文件
namespace.c 151 Cgroup namespace

kernel/debug 内核调试机制与原理详解

kernel/debug 内核调试机制与原理详解

源码路径rk3588/kernel-6.1/kernel/debug/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)

该目录实现 Linux 内核 KGDB(Kernel GDB) 调试子系统的核心代码。KGDB 允许通过串口或网络,使用主机端 GDB 远程调试运行中的内核;可选的 KDB 前端提供内核内置命令行调试器。二者共享 debug_core.c 提供的异常处理、断点管理、SMP 协调等基础设施。

说明:架构相关代码(断点指令、寄存器读写、异常向量)位于 arch/arm64/kernel/kgdb.c 等目录;串口 I/O 模块位于 drivers/tty/serial/kgdboc.c 等;本目录是调试框架的通用核心


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
obj-$(CONFIG_KGDB) += debug_core.o gdbstub.o
obj-$(CONFIG_KGDB_KDB) += kdb/
配置项 说明
CONFIG_KGDB 总开关,依赖 HAVE_ARCH_KGDBDEBUG_KERNEL
CONFIG_KGDB_SERIAL_CONSOLE 串口共享 kgdb(kgdboc)
CONFIG_KGDB_KDB 启用 KDB 内置命令行前端
CONFIG_KDB_KEYBOARD PS/2 键盘作为 KDB 输入
CONFIG_KGDB_HONOUR_BLOCKLIST 禁止在不安全符号上设断点

1.2 编译单元

编译单元 源文件 规模 功能
调试核心 debug_core.c ~1,239 行 异常入口、SMP roundup、断点、I/O 注册
GDB Stub gdbstub.c ~1,156 行 GDB 远程串行协议实现
KDB 主循环 kdb/kdb_main.c ~2,937 行 命令解析、注册、主 REPL 循环
KDB I/O kdb/kdb_io.c ~882 行 控制台输出、命令行输入
KDB 断点 kdb/kdb_bp.c ~591 行 KDB 断点/观察点管理
KDB 支持 kdb/kdb_support.c ~556 行 内存读写、符号解析、参数解析
KDB 栈回溯 kdb/kdb_bt.c ~221 行 bt/btp/bta/btc 命令
KDB 键盘 kdb/kdb_keyboard.c ~262 行 PS/2 键盘输入(可选)
KDB/KGDB 桥接 kdb/kdb_debugger.c ~176 行 kdb_stub、模式切换
内部头文件 debug_core.h ~87 行 核心与前端私有接口
KDB 私有头 kdb/kdb_private.h ~245 行 KDB 内部类型与 API
初始命令 kdb/kdb_cmds ~32 行 启动时执行的 defcmd 宏

1.3 相关代码分布(本目录外)

位置 功能
arch/arm64/kernel/kgdb.c ARM64 断点异常、寄存器、单步
include/linux/kgdb.h 公共 API、arch_kgdb_ops
drivers/tty/serial/kgdboc.c 串口 I/O 驱动(kgdboc=)
drivers/misc/kgdbts.c KGDB 内部测试套件
kernel/trace/trace_kdb.c ftrace 与 KDB 集成

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────┐
│ 主机端调试器 │
│ GDB (target remote) / kdb 命令行 │
└──────────────────────────┬──────────────────────────────────┘
│ 串口 / 网络
┌──────────────────────────▼──────────────────────────────────┐
│ gdbstub.c / kdb/kdb_main.c │
│ GDB Remote Protocol │ KDB 命令 REPL │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ debug_core.c (核心) │
│ kgdb_handle_exception │ kgdb_cpu_enter │ 断点 │ SMP roundup │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ arch/arm64/kernel/kgdb.c │
│ 断点指令 │ pt_regs 读写 │ 单步 │ kgdb_brk_fn 异常处理 │
└─────────────────────────────────────────────────────────────┘

2.2 双前端设计

模式 默认 入口 特点
KDB dbg_kdb_mode = 1 kdb_stub() 内核内置 shell,无需主机 GDB
GDB Stub 切换后 gdb_serial_stub() 标准 GDB 远程协议,功能完整

两者可在运行时通过 kgdb 命令或 GDB 3 转义包互相切换。

2.3 关键全局状态

1
2
3
4
5
6
// debug_core.c
struct debuggerinfo_struct kgdb_info[NR_CPUS]; // 每 CPU 调试上下文
atomic_t kgdb_active; // 当前 master CPU
int kgdb_connected; // 主机 GDB 是否已连接
struct kgdb_io *dbg_io_ops; // 当前 I/O 驱动
int dbg_kdb_mode = 1; // 1=KDB, 0=GDB stub

三、核心数据结构

3.1 struct kgdb_state — 异常上下文

1
2
3
4
5
6
7
8
9
10
11
12
// debug_core.h
struct kgdb_state {
int ex_vector; // 异常向量号
int signo; // 信号号(SIGTRAP 等)
int err_code; // 错误码
int cpu; // 触发 CPU
int pass_exception; // 是否将异常传回内核
unsigned long threadid; // 当前线程 ID
long kgdb_usethreadid;
struct pt_regs *linux_regs; // CPU 寄存器
atomic_t *send_ready; // NMI 回调同步
};

3.2 struct debuggerinfo_struct — 每 CPU 状态

1
2
3
4
5
6
7
8
9
struct debuggerinfo_struct {
void *debuggerinfo; // 架构相关调试信息(通常 = pt_regs)
struct task_struct *task; // 触发时的 current
int exception_state; // DCPU_WANT_MASTER 等标志
int ret_state; // 返回状态
int irq_depth; // 进入时硬中断深度
int enter_kgdb; // 递归进入计数
bool rounding_up; // 是否正在 roundup
};

CPU 状态标志:

标志 含义
DCPU_WANT_MASTER 等待成为 master 调试 CPU
DCPU_NEXT_MASTER 切换 master CPU
DCPU_IS_SLAVE 从 CPU 已进入等待
DCPU_WANT_BT 从 CPU 需打印栈回溯

3.3 struct kgdb_bkpt — 软件断点

1
2
3
4
5
6
// include/linux/kgdb.h
struct kgdb_bkpt {
unsigned long bpt_addr; // 断点地址
char saved_instr[BREAK_INSTR_SIZE]; // 原始指令
int state; // BP_SET / BP_REMOVED / BP_UNDEFINED
};

最多 KGDB_MAX_BREAKPOINTS 个(通常 1000),由 GDB 或 KDB 管理。

3.4 struct kgdb_io — I/O 驱动接口

1
2
3
4
5
6
7
8
9
10
struct kgdb_io {
const char *name;
int (*read_char)(void);
void (*write_char)(u8);
void (*flush)(void);
int (*init)(void);
void (*pre_exception)(void);
void (*post_exception)(void);
int is_early; // 是否可用于 early debug
};

通过 kgdb_register_io_module() 注册,典型实现为 kgdboc(串口)。


四、异常进入与 SMP 协调

4.1 主入口

1
2
3
4
5
6
7
// debug_core.c
int kgdb_handle_exception(int evector, int signo, int ecode, struct pt_regs *regs)
{
// 1. 填充 kgdb_state
// 2. 检查重入(kgdb_reenter_check)
// 3. 调用 kgdb_cpu_enter() 进入调试主循环
}

ARM64 上由 kgdb_brk_fn() 在断点异常时调用:

1
2
3
4
5
6
// arch/arm64/kernel/kgdb.c
static int kgdb_brk_fn(struct pt_regs *regs, unsigned long esr)
{
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return 0;
}

4.2 kgdb_cpu_enter 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kgdb_cpu_enter()
├── 获取 master 锁 (dbg_master_lock)
├── kgdb_roundup_cpus() // SMP: 通知其他 CPU 停止
├── 等待所有 CPU 进入 kgdb 等待态
├── dbg_deactivate_sw_breakpoints() // 临时移除 SW 断点
├── 关闭 ftrace (tracing_off)
└── 主循环:
if (dbg_kdb_mode)
kdb_stub(ks) // KDB 命令行
else
gdb_serial_stub(ks) // GDB 协议
→ 根据返回值切换模式或退出
├── dbg_activate_sw_breakpoints() // 恢复断点
├── 释放 slave 锁,等待从 CPU 退出
└── 恢复 tracing,返回正常执行

4.3 SMP Roundup

多核系统中,一个 CPU 触发调试后需冻结其他 CPU,避免并发修改内核状态:

  • Master CPU 获取 dbg_master_lock,设置 kgdb_active
  • 通过 IPI/NMI 调用 kgdb_nmicallback() 让从 CPU 进入 spin 等待
  • 从 CPU 设置 DCPU_IS_SLAVE,在 cpu_loop 中等待
  • 调试结束后释放锁,从 CPU 恢复

可通过内核参数 nokgdbroundup 禁用 roundup(调试用,有风险)。

4.4 递归保护

  • exception_level — 防止调试器内部再次触发异常时死锁
  • kgdb_info[cpu].enter_kgdb — 每 CPU 进入计数
  • kgdb_reenter_check() — 检测双重异常

五、GDB Stub 远程协议

5.1 协议格式

GDB 远程串行协议使用 $<data>#<checksum> 帧格式:

1
2
3
// gdbstub.c
static void get_packet(char *buffer); // 接收并校验
static void put_packet(char *buffer); // 发送并附加校验

5.2 主要命令处理

命令 功能
? 查询停止原因(信号号)
g / G 读/写全部寄存器
p / P 读/写单个寄存器
m / M 读/写内存
c 继续执行
s 单步执行
Z / z 设置/删除断点
H 设置当前线程
T 查询线程是否 alive
q 各类查询(supported、offsets、Xfer 等)
D / k 分离 / 杀死
R 重启
3 转义回 KDB 模式

5.3 线程与多任务

GDB stub 支持按线程调试:

  • Hg<tid> — 设置当前线程
  • Hc<tid> — 设置继续执行的线程
  • T<tid> — 检查线程状态
  • 配合 /proc 或内核 task 列表遍历所有线程

5.4 架构相关处理

kgdb_arch_handle_exception()(ARM64)处理 c/s 包中的地址参数,设置 PC 并启用/禁用硬件单步。


六、KDB 内置调试器

6.1 命令注册机制

1
2
3
4
5
6
7
8
9
10
11
// kdb_main.c
static struct kdb_cmd kdb_cmds[] = {
{ .name = "md", .func = kdb_md, ... }, // 内存显示
{ .name = "mm", .func = kdb_mm, ... }, // 内存修改
{ .name = "go", .func = kdb_go, ... }, // 继续执行
{ .name = "bt", .func = kdb_bt, ... }, // 栈回溯
{ .name = "ps", .func = kdb_ps, ... }, // 进程列表
{ .name = "bp", .func = kdb_bp, ... }, // 断点
{ .name = "kgdb", .func = kdb_kgdb, ... }, // 切换到 GDB
// ...
};

命令通过 kdb_register() 加入 kdb_cmds_head 链表;kdb_parse() 解析用户输入并 dispatch。

6.2 常用 KDB 命令

命令 功能
md / mdr / mdp / mds 显示内存(raw/phys/proc/symbol)
mm 修改内存
rd / rm 读/写 MSR(架构相关)
go 继续执行(可选地址)
bt 当前栈回溯
btp <pid> 指定进程栈回溯
bta 所有进程栈回溯
btc 每 CPU 一个进程栈回溯
ps 进程列表
pid 切换当前进程上下文
bp / bc / ba 断点 set/clear/list
ss 单步
cpu 切换 CPU
dmesg 显示内核 log 缓冲
summary 系统摘要
lsmod 已加载模块
reboot 重启
kill 发送信号给进程
set / env 环境变量(RADIX、PROMPT 等)
help / ? 帮助
kgdb 切换到 GDB stub 模式
defcmd / endefcmd 定义命令宏
sr 触发 SysRq

6.3 权限控制

kdb_cmd_enabled(模块参数 cmd_enable)按位控制命令权限:

权限
0x0002 任意内存读、符号查找
0x0004 任意内存写
0x0008 寄存器读
0x0010 寄存器写
0x0020 被动检查(bt、ps、lsmod)
0x0040 流程控制(断点、单步)
0x0080 信号进程
0x0100 重启

6.4 启动宏(kdb_cmds)

kdb/kdb_cmds 在 KDB 初始化时执行,预定义诊断宏:

1
2
3
defcmd dumpcommon ...   # summary + cpu + ps + dmesg + bt
defcmd dumpall ... # dumpcommon + bta
defcmd dumpcpu ... # dumpcommon + btc

七、断点管理

7.1 软件断点(KGDB 核心)

1
2
3
4
5
6
7
// debug_core.c
kgdb_arch_set_breakpoint(bpt)
→ 保存原始指令到 saved_instr
→ 写入架构断点指令(ARM64: BRK)

kgdb_arch_remove_breakpoint(bpt)
→ 恢复原始指令

进入调试器时 deactivate 所有 SW 断点(避免调试代码自身触发),退出时 activate 恢复。

7.2 KDB 断点(kdb_bp.c)

KDB 独立维护最多 KDB_MAXBPT(16)个断点:

类型 说明
BP_HARDWARE_BREAKPOINT 指令断点
BP_WRITE_WATCHPOINT 数据写观察点
BP_ACCESS_WATCHPOINT 数据读写观察点

支持延迟断点(bp_delay)和单步恢复机制(SSBPT 标志)。

7.3 Blocklist 保护

启用 CONFIG_KGDB_HONOUR_BLOCKLIST 时,禁止在 kprobe blocklist 中的符号(如异常处理路径上的函数)设置断点,防止递归陷阱。


八、I/O 与触发方式

8.1 串口 I/O(kgdboc)

1
2
3
4
# 内核命令行
kgdboc=ttyFIQ0,115200 # RK3588 常用 FIQ 调试串口
# 或
kgdboc=ttyS2,115200

I/O 驱动通过 kgdb_register_io_module() 注册到 dbg_io_ops

8.2 触发进入调试器

方式 说明
SysRq-g echo g > /proc/sysrq-trigger 或 Magic SysRq 键 + g
kgdbwait 内核启动参数,启动后等待 GDB 连接
断点/Oops 内核 panic/oops 时自动进入(若已注册)
KDB 键盘 PS/2 键(需 CONFIG_KDB_KEYBOARD)
1
2
3
4
5
6
// debug_core.c
static void sysrq_handle_dbg(int key)
{
kgdb_breakpoint(); // 触发断点异常
}
register_sysrq_key('g', &sysrq_dbg_op);

8.3 内核参数

参数 功能
kgdbwait 启动时等待 GDB 连接
kgdboc=<tty>,<baud> 指定 kgdb 串口
nokgdbroundup 禁用 SMP CPU roundup
kdb.cmd_enable=<hex> KDB 命令权限掩码
kgdbreboot=<n> panic 时行为

九、KDB 与 GDB 模式切换

9.1 KDB → GDB

在 KDB 提示符输入:

1
kdb> kgdb

kdb_stub() 返回 DBG_PASS_EVENT,主循环切换 dbg_kdb_mode = 0,下次迭代进入 gdb_serial_stub()

9.2 GDB → KDB

GDB 发送转义包 '3',或 KDB I/O 检测到非 GDB 格式输入时切换。

9.3 主循环切换逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// debug_core.c — cpu_master_loop
while (1) {
if (dbg_kdb_mode) {
error = kdb_stub(ks);
} else {
error = gdb_serial_stub(ks);
}
if (error == DBG_PASS_EVENT)
dbg_kdb_mode = !dbg_kdb_mode; // 切换模式
else if (error == DBG_SWITCH_CPU_EVENT)
goto cpu_loop; // 切换 CPU
else
break; // 退出调试
}

十、完整调试会话时序

SysRq-g 触发 → GDB 连接 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[用户 / 按键]
echo g > /proc/sysrq-trigger
→ sysrq_handle_dbg()
→ kgdb_breakpoint()
→ ARM64 BRK 异常
→ kgdb_brk_fn(regs)
→ kgdb_handle_exception(SIGTRAP, regs)

[debug_core — SMP 协调]
kgdb_cpu_enter()
→ master CPU 获取 dbg_master_lock
→ kgdb_roundup_cpus() — IPI 冻结其他 7 核
→ dbg_deactivate_sw_breakpoints()
→ tracing_off()

[KDB 模式 — 默认]
kdb_stub(ks)
→ kdb_common_init_state()
→ 显示 kdb> 提示符,等待输入

[用户输入 kgdb 切换]
kdb> kgdb
→ 返回 DBG_PASS_EVENT
→ dbg_kdb_mode = 0

[GDB Stub 模式]
gdb_serial_stub(ks)
→ put_packet("S05") // 通知 GDB: SIGTRAP
→ get_packet() 循环处理 m/M/g/G/c/s/Z/z ...

[主机 GDB]
(gdb) target remote /dev/ttyUSB0
(gdb) break some_kernel_func
(gdb) continue

[继续执行后再次触发]
→ 再次进入 kgdb_handle_exception
→ GDB 收到停止通知,可 inspect 寄存器/内存

[退出调试]
GDB 发送 'D' (detach) 或 'c' (continue)
→ kgdb_cpu_enter 主循环 break
→ dbg_activate_sw_breakpoints()
→ 释放 slave 锁,从 CPU 恢复
→ 系统正常运行

十一、RK3588 平台配置与使用

11.1 内核配置

1
2
3
4
5
6
7
8
9
zcat /proc/config.gz | grep -E 'KGDB|KDB|DEBUG'

# 典型调试内核配置
# CONFIG_DEBUG_KERNEL=y
# CONFIG_HAVE_ARCH_KGDB=y
# CONFIG_KGDB=y
# CONFIG_KGDB_SERIAL_CONSOLE=y
# CONFIG_KGDB_KDB=y # 可选
# CONFIG_FRAME_POINTER=y # 建议,改善栈回溯

ARM64 RK3588 在 arch/arm64/Kconfig 中选择 HAVE_ARCH_KGDB,具体实现在 arch/arm64/kernel/kgdb.c

11.2 设备树 / 命令行示例

1
2
3
# U-Boot 或 bootargs 追加
console=ttyFIQ0,115200 earlycon=uart8250,mmio32,0xfeb50000 \
kgdboc=ttyFIQ0,115200 kgdbwait
  • ttyFIQ0:Rockchip 平台常用 FIQ 调试串口
  • kgdbwait:内核初始化完成后暂停,等待 GDB 连接

11.3 主机端 GDB 连接

1
2
3
4
5
6
7
8
# 假设串口为 /dev/ttyUSB0
aarch64-linux-gnu-gdb vmlinux
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
(gdb) bt # 栈回溯
(gdb) info registers # 寄存器
(gdb) x/10i $pc # 反汇编
(gdb) continue

11.4 无 GDB 时使用 KDB

1
2
3
4
5
6
7
8
9
# 触发 SysRq-g 进入 KDB
echo g > /proc/sysrq-trigger

# KDB 提示符下
kdb> bt # 栈回溯
kdb> ps # 进程列表
kdb> md 0xffffffc008000000 # 读内存
kdb> dmesg 100 # 最近 100 条 log
kdb> go # 继续

11.5 Android / 嵌入式注意事项

要点 说明
串口访问 需物理 UART 或 USB 转串口,Android 量产机通常无暴露接口
性能影响 kgdbwait 会阻塞启动,仅用于 bring-up 调试
SMP RK3588 8 核,roundup 需等待所有 CPU,超时约 1 秒
与 ftrace 交互 进入 KGDB 时自动 tracing_off(),退出后恢复
Lockdown 启用 Kernel Lockdown 时,GDB 写内存会被拒绝,自动回退 KDB

11.6 与 ftrace/KDB 集成

启用 CONFIG_KGDB_KDB 且 ftrace 支持时,kernel/trace/trace_kdb.c 允许在 KDB 中使用 ftrace 相关命令,便于运行时追踪与调试结合。


十二、总结

kernel/debug 是 Linux 内核源码级调试的基础设施:

  1. debug_core.c — 统一异常入口、SMP 多核协调、断点管理、I/O 抽象
  2. gdbstub.c — 实现 GDB 远程串行协议,支持主机 GDB 全功能调试
  3. kdb/ — 可选内置命令行调试器,适合无 GDB 环境或 early boot 诊断
  4. 双模式切换 — KDB 与 GDB stub 共享核心,可按场景灵活切换
  5. 架构分离 — ARM64 相关代码在 arch 层,本目录保持架构无关

在 RK3588 平台 bring-up、驱动调试、内核 panic 分析场景中,KGDB/KDB 是与串口日志、ftrace、crash dump 互补的重要工具。生产环境通常不启用;开发和深度调试内核时,配合 kgdboc + kgdbwait + 主机 GDB 是标准工作流。


附录:源文件完整清单

文件 行数 分类
kdb/kdb_main.c 2,937 KDB 主循环与命令
debug_core.c 1,239 KGDB 核心
gdbstub.c 1,156 GDB 远程协议
kdb/kdb_io.c 882 KDB I/O
kdb/kdb_bp.c 591 KDB 断点
kdb/kdb_support.c 556 KDB 辅助函数
kdb/kdb_keyboard.c 262 键盘输入
kdb/kdb_bt.c 221 栈回溯
kdb/kdb_debugger.c 176 KDB/KGDB 桥接
debug_core.h 87 内部头文件
kdb/kdb_private.h 245 KDB 私有头文件
kdb/kdb_cmds 32 启动宏定义

kernel/dma DMA 映射机制与原理详解

kernel/dma DMA 映射机制与原理详解

源码路径rk3588/kernel-6.1/kernel/dma/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)

该目录实现 Linux 内核 DMA Mapping API 的通用框架代码。DMA 映射负责在 CPU 虚拟/物理地址与设备可见的 DMA 地址(bus address / IOVA) 之间建立对应关系,处理缓存一致性、IOMMU 翻译、SWIOTLB 回弹及连续内存分配等问题。所有设备驱动通过 dma_alloc_*dma_map_* 等统一 API 访问 DMA 内存。

说明:架构相关缓存维护、IOMMU 挂接位于 arch/arm64/mm/dma-mapping.c;IOMMU/SMMU 驱动位于 drivers/iommu/;CMA 核心实现在 mm/cma.c。本目录提供架构无关的 DMA 框架层


目录


一、源码目录结构

1.1 编译依赖(Makefile / Kconfig)

1
2
3
4
5
6
7
8
9
obj-$(CONFIG_HAS_DMA)           += mapping.o direct.o
obj-$(CONFIG_DMA_OPS) += ops_helpers.o dummy.o
obj-$(CONFIG_DMA_CMA) += contiguous.o
obj-$(CONFIG_DMA_DECLARE_COHERENT) += coherent.o
obj-$(CONFIG_DMA_API_DEBUG) += debug.o
obj-$(CONFIG_SWIOTLB) += swiotlb.o
obj-$(CONFIG_DMA_COHERENT_POOL) += pool.o
obj-$(CONFIG_MMU) += remap.o
obj-$(CONFIG_DMA_MAP_BENCHMARK) += map_benchmark.o
配置项 说明
CONFIG_HAS_DMA 平台支持 DMA(ARM64 默认 y)
CONFIG_DMA_OPS 使用 struct dma_map_ops 抽象
CONFIG_DMA_CMA DMA 连续内存分配器(CMA)
CONFIG_SWIOTLB 软件 I/O TLB 回弹缓冲
CONFIG_DMA_COHERENT_POOL 原子 coherent 内存池
CONFIG_DMA_API_DEBUG DMA-API 使用调试
CONFIG_DMA_PERNUMA_CMA 每 NUMA 节点独立 CMA(ARM64 可选)

1.2 编译单元

编译单元 源文件 规模 功能
API 入口 mapping.c ~831 行 dma_map_*/dma_alloc_* 统一分发
Direct 映射 direct.c ~657 行 无 IOMMU 时的直接物理地址映射
SWIOTLB swiotlb.c ~1,112 行 软件 bounce buffer
DMA 调试 debug.c ~1,604 行 映射泄漏/重复释放检测
CMA 集成 contiguous.c ~443 行 DMA 侧 CMA 初始化与分配接口
Coherent 池 coherent.c ~403 行 设备预留 coherent 内存
Atomic Pool pool.c ~295 行 小块 coherent 原子分配池
Remap 辅助 remap.c ~70 行 vmap/remap 非连续 DMA 内存
Ops 辅助 ops_helpers.c ~93 行 mmap/sgtable 公共实现
Dummy Ops dummy.c ~38 行 无 DMA 能力设备的空 ops
Benchmark map_benchmark.c ~378 行 dma_map 性能测试
Direct 头文件 direct.h ~126 行 direct 映射 inline 函数

1.3 相关代码分布(本目录外)

位置 功能
arch/arm64/mm/dma-mapping.c ARM64 缓存 flush/invalidate、arch_setup_dma_ops
include/linux/dma-mapping.h 驱动使用的公共 DMA API 头文件
include/linux/dma-map-ops.h struct dma_map_ops 定义
drivers/iommu/ ARM SMMU、Rockchip IOMMU 等
mm/cma.c CMA 区域管理与 page 迁移
kernel/dma/direct.h direct 路径 inline 实现

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────┐
│ 设备驱动 │
│ dma_alloc_coherent / dma_map_sg / dma_sync_* │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ mapping.c (API 分发层) │
│ get_dma_ops(dev) → direct 快路径 或 ops->map_* │
└──────┬──────────┬──────────┬──────────┬─────────────────────┘
│ │ │ │
┌──────▼──┐ ┌─────▼────┐ ┌──▼──────┐ ┌─▼──────────────┐
│ direct │ │ SWIOTLB │ │ CMA │ │ IOMMU ops │
│ direct.c│ │ swiotlb.c│ │contiguous│ │ (drivers/iommu)│
└──────┬──┘ └─────┬────┘ └──┬──────┘ └─┬──────────────┘
│ │ │ │
└──────────┴──────────┴──────────┘

┌────────────▼────────────┐
│ 物理内存 / IOVA │
│ 设备 DMA 控制器 │
└─────────────────────────┘

2.2 三种映射路径

路径 条件 行为
Direct 无 IOMMU 或 bypass,DMA 地址在 mask 内 物理地址直接作为 DMA 地址
IOMMU 设备挂接 SMMU/IOMMU 分配 IOVA,建立页表映射
SWIOTLB 物理地址超出 DMA mask 或无 IOMMU 数据复制到 bounce buffer

mapping.cdma_map_direct() 判断是否走 direct 快路径:

1
2
3
4
static inline bool dma_map_direct(struct device *dev, const struct dma_map_ops *ops)
{
return dma_go_direct(dev, *dev->dma_mask, ops);
}

2.3 两类 DMA 内存

类型 API 特点
Coherent(一致性) dma_alloc_coherent CPU 与设备共享同一视图,无需 sync
Streaming(流式) dma_map_page/sg + dma_sync_* 需显式缓存同步,适合网络/块 I/O

三、核心 API 与数据结构

3.1 struct dma_map_ops — 驱动 ops 虚表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// include/linux/dma-map-ops.h
struct dma_map_ops {
void *(*alloc)(...); // coherent 分配
void (*free)(...);
dma_addr_t (*map_page)(...); // 单页 streaming 映射
void (*unmap_page)(...);
int (*map_sg)(...); // scatter-gather 映射
void (*unmap_sg)(...);
void (*sync_single_for_cpu)(...); // 缓存同步
void (*sync_single_for_device)(...);
void (*sync_sg_for_cpu/device)(...);
int (*mmap)(...); // 用户空间 mmap
// ...
};

每个 struct device 通过 dev->dma_ops 指向具体实现。IOMMU 驱动注册自己的 ops;无 IOMMU 时使用 dma_direct_ops

3.2 主要公共 API(mapping.c)

API 功能
dma_alloc_coherent / dma_free_coherent 分配/释放一致性 DMA 内存
dma_alloc_attrs / dma_free_attrs 带属性(WC、non-coherent 等)的分配
dma_map_page / dma_unmap_page 映射/解映射单个 page
dma_map_sg / dma_unmap_sg 映射/解映射 scatterlist
dma_map_sgtable / dma_unmap_sgtable sg_table 版本(带错误码)
dma_map_resource / dma_unmap_resource 映射 MMIO 等资源地址
dma_sync_single_for_cpu/device 单 buffer 缓存同步
dma_sync_sg_for_cpu/device SG 列表缓存同步
dma_set_mask / dma_set_coherent_mask 设置 DMA 地址掩码
dma_mmap_attrs 将 coherent 内存 mmap 到用户空间
dmam_alloc_* / dmam_free_* 托管式分配(驱动 detach 时自动释放)

3.3 DMA 数据方向

1
2
3
4
5
6
enum dma_data_direction {
DMA_BIDIRECTIONAL = 0, // 双向
DMA_TO_DEVICE = 1, // CPU → 设备
DMA_FROM_DEVICE = 2, // 设备 → CPU
DMA_NONE = 3,
};

3.4 DMA 属性(attrs)

属性 含义
DMA_ATTR_WRITE_COMBINE 写合并(WC)映射
DMA_ATTR_NON_CONSISTENT 非一致性映射
DMA_ATTR_SKIP_CPU_SYNC 跳过 CPU 侧 cache sync
DMA_ATTR_FORCE_CONTIGUOUS 强制物理连续
DMA_ATTR_NO_KERNEL_MAPPING 不建立 CPU 线性映射

四、Direct Mapping 直接映射

4.1 概述

direct.c + direct.h 实现 不使用 IOMMU 时的 DMA 操作:DMA 地址 = 物理地址(经 phys_to_dma 转换)。

4.2 Coherent 分配流程

1
2
3
4
5
dma_direct_alloc()
├── is_swiotlb_for_alloc()? → swiotlb_alloc()
├── dma_alloc_contiguous() // 优先 CMA 连续页
├── alloc_pages_node() // 回退普通页分配
└── 检查 dma_coherent_ok()(地址在 mask 范围内)

4.3 Streaming 映射(direct.h inline)

1
2
3
4
5
dma_direct_map_page(dev, page, offset, size, dir, attrs)
├── is_swiotlb_force_bounce()? → swiotlb_map()
├── !dma_capable()? → swiotlb_map() 或 ERROR
└── 非 coherent 设备? → arch_sync_dma_for_device()
return phys_to_dma(phys)

4.4 Zone 选择策略

1
2
3
4
5
// direct.c — 根据 DMA mask 选择 GFP 区域
dma_direct_optimal_gfp_mask(dev, mask, &phys_limit)
→ mask ≤ 24bit: GFP_DMA
→ mask ≤ 32bit: GFP_DMA32
→ 否则: 0(普通 ZONE_NORMAL)

RK3588 ARM64 通常 DMA mask 为 64-bit,默认从 ZONE_NORMAL 分配。


五、SWIOTLB 软件回弹

5.1 使用场景

swiotlb.c 在以下情况提供 bounce buffer

  • 设备 DMA mask 小于物理地址(如 32-bit 设备访问 >4GB 内存)
  • 无 IOMMU 且物理地址不可达
  • 强制 bounce 模式(swiotlb=force

5.2 工作原理

1
2
3
4
5
6
7
8
9
10
dma_map_page(高地址物理页)
→ dma_direct_map_page 检测 !dma_capable
→ swiotlb_map()
├── 在 SWIOTLB 池中分配低地址 slot
├── DMA_TO_DEVICE: 复制数据到 bounce buffer
└── 返回 bounce buffer 的 DMA 地址

dma_unmap_page / sync_for_cpu
→ DMA_FROM_DEVICE: 从 bounce buffer 复制回原始 buffer
→ 释放 SWIOTLB slot

5.3 核心结构

1
2
3
4
5
6
7
8
9
10
struct io_tlb_mem {
phys_addr_t start; // bounce buffer 物理起始
unsigned long nslabs; // slot 数量
// ...
};

struct io_tlb_slot {
phys_addr_t orig_addr; // 原始物理地址
size_t alloc_size;
};

5.4 内核参数

参数 功能
swiotlb=force 强制所有 DMA 使用 bounce
swiotlb=noforce 禁用强制 bounce
swiotlb=<size> 设置 bounce buffer 大小

默认大小 IO_TLB_DEFAULT_SIZE(通常 64MB)。


六、CMA 连续内存分配

6.1 概述

contiguous.c 是 DMA 子系统与 CMA(Contiguous Memory Allocator) 的桥梁。CMA 在 boot 时预留一块物理连续区域,平时供 pagecache 使用,需要时可迁移页面以提供大块连续物理内存。

6.2 为什么需要 CMA

场景 原因
视频编解码 硬件 IP 不支持 SG-DMA,需物理连续 buffer
摄像头/V4L2 frame buffer 通常要求连续
DMA 大 buffer 减少 TLB miss 和 SG 列表复杂度

6.3 初始化

1
2
3
4
// contiguous.c
struct cma *dma_contiguous_default_area;

early_param("cma", early_cma); // cma=64M@0x80000000

Kconfig 默认 ARM64 可设 CMA_SIZE_MBYTES=16 或按内存百分比。

6.4 分配接口

1
2
3
// 通过 dma-map-ops.h
struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp);
void dma_free_contiguous(struct device *dev, struct page *page, size_t size);

direct.c 中 coherent 分配优先调用 dma_alloc_contiguous()

6.5 Pernuma CMA

启用 CONFIG_DMA_PERNUMA_CMA 时,每个 NUMA 节点有独立 CMA 区域,SMMU 可分配本地内存。RK3588 为 UMA 架构,通常使用单一全局 CMA。


七、Coherent 内存与 Pool

7.1 设备 Coherent 内存(coherent.c)

允许平台/驱动声明 预留物理内存 作为设备专用 coherent 池:

1
2
3
4
5
6
7
struct dma_coherent_mem {
void *virt_base; // CPU 虚拟地址
dma_addr_t device_base; // 设备 DMA 地址
unsigned long pfn_base;
int size;
unsigned long *bitmap; // 页分配位图
};

通过 dma_declare_coherent_memory() 注册,dev->dma_mem 指向该结构。

7.2 Atomic Coherent Pool(pool.c)

原子上下文(中断、持有 spinlock)中的小 buffer 分配提供 gen_pool:

1
2
3
static struct gen_pool *atomic_pool_dma;     // GFP_DMA 区域
static struct gen_pool *atomic_pool_dma32; // GFP_DMA32 区域
static struct gen_pool *atomic_pool_kernel; // 普通区域

内核参数 coherent_pool=size 控制池大小。/sys/kernel/debug/dma_pools/ 可查看统计。

7.3 Remap 辅助(remap.c)

非连续物理页通过 vmap 映射为连续虚拟地址:

  • dma_common_pages_remap() — 页数组 remap
  • dma_common_contiguous_remap() — 连续页 remap
  • dma_common_free_remap() — 释放 remap

八、缓存一致性与同步

8.1 ARM64 缓存维护

1
2
3
4
5
6
7
8
9
// arch/arm64/mm/dma-mapping.c
arch_sync_dma_for_device(paddr, size, dir)
→ dcache_clean_poc() // 写回 CPU cache 到 PoC

arch_sync_dma_for_cpu(paddr, size, dir)
→ dcache_inval_poc() // 使 CPU cache 失效(DMA_FROM_DEVICE)

arch_dma_prep_coherent(page, size)
→ dcache_clean_inval_poc() // coherent 分配时 clean+invalidate

8.2 同步 API 调用时机

场景 调用
DMA 发送前 dma_sync_single_for_device(DMA_TO_DEVICE)
DMA 完成后 CPU 读 dma_sync_single_for_cpu(DMA_FROM_DEVICE)
map 时(non-coherent) 内部自动 arch_sync_dma_for_device
unmap 时 内部自动 arch_sync_dma_for_cpu

8.3 Coherent vs Non-coherent

Coherent Non-coherent (Streaming)
CPU cache 硬件或映射保证一致 需软件 sync
分配 API dma_alloc_coherent 普通内存 + dma_map_*
性能 简单但可能慢 高吞吐,需正确 sync
RK3588 大部分外设为 non-coherent 网络/存储驱动常用

九、DMA-API 调试

9.1 DMA_API_DEBUG(debug.c)

启用 CONFIG_DMA_API_DEBUG 后,跟踪每次 dma_map_*dma_alloc_coherent

1
2
3
4
5
6
7
8
struct dma_debug_entry {
struct device *dev;
size_t size;
int type; // single/sg/coherent/resource
enum dma_data_direction direction;
dma_addr_t dev_addr;
// 栈回溯信息
};

检测常见问题:

  • 双重 unmap
  • 释放未 map 的地址
  • map/unmap size 不匹配
  • SG 映射 segment 边界违规

Debugfs 接口:/sys/kernel/debug/dma-api/

9.2 Map Benchmark(map_benchmark.c)

启用 CONFIG_DMA_MAP_BENCHMARK 提供 /sys/kernel/debug/dma_map_benchmark,用于测试 dma_map_page 性能。


十、完整 DMA 映射时序

网络驱动 TX(streaming DMA) 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[驱动]
skb = alloc_skb(...)
dma_addr = dma_map_single(dev, skb->data, len, DMA_TO_DEVICE)

[mapping.c]
ops = get_dma_ops(dev)
if (dma_map_direct(dev, ops))
addr = dma_direct_map_page(dev, page, offset, len, DMA_TO_DEVICE, 0)
else
addr = ops->map_page(dev, page, offset, len, DMA_TO_DEVICE, 0)

[direct.h — direct 路径]
phys = page_to_phys(page) + offset
if (!dma_capable(dev, dma_addr, len))
return swiotlb_map(dev, phys, len, ...) // 高地址 bounce
arch_sync_dma_for_device(phys, len, DMA_TO_DEVICE) // cache clean
return phys_to_dma(dev, phys)

[ARM64]
dcache_clean_poc(vaddr, vaddr + size)

[驱动]
写入硬件 DMA 描述符: dma_addr, len
触发 TX

[TX 完成中断]
dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE)
→ dma_direct_sync_single_for_cpu() // 如需要
→ swiotlb_unmap() 如使用了 bounce

Coherent 分配路径(如 V4L2 帧缓冲):

1
2
3
4
5
dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL)
→ ops->alloc() 或 dma_direct_alloc()
→ dma_alloc_contiguous() // CMA 连续页
→ arch_dma_prep_coherent() // cache clean+invalidate
→ 返回 {cpu_addr, dma_handle}

十一、RK3588 平台应用

11.1 典型配置

1
2
3
4
5
6
7
8
9
zcat /proc/config.gz | grep -E 'CMA|SWIOTLB|DMA|IOMMU'

# RK3588 典型项
# CONFIG_HAS_DMA=y
# CONFIG_DMA_CMA=y
# CONFIG_CMA_SIZE_MBYTES=16 (或更大)
# CONFIG_SWIOTLB=y
# CONFIG_IOMMU_DMA=y
# CONFIG_ARM_SMMU=y

11.2 IOMMU / SMMU

RK3588 集成 ARM SMMU,多数外设(VOP、RGA、VPU、ISP 等)通过 IOMMU 映射:

1
2
3
arch_setup_dma_ops(dev, dma_base, size, iommu_ops, coherent)
→ iommu_setup_dma_ops() // 设置 dev->dma_ops = iommu_dma_ops
→ dev->dma_coherent = coherent
  • 有 IOMMU:dma_map_sg 分配 IOVA 并建立页表
  • Bypass 模式:满足 dma_ops_bypass 条件时走 direct 快路径

11.3 CMA 与多媒体

RK3588 视频/图形驱动大量依赖 CMA:

1
2
3
4
5
# 启动参数调整 CMA 大小(视频应用建议 256M+)
cma=256M

# 查看 CMA 使用
cat /proc/meminfo | grep -i cma

/dev/dma_heap/ 用户空间也可从 CMA 分配(dma-buf 框架)。

11.4 常见问题

问题 原因 排查
DMA 映射失败 内存不足或 IOVA 耗尽 dmesg、/sys/kernel/debug/iommu/
花屏/数据错误 缺少 cache sync 检查 dma_sync_* 调用
高地址设备失败 32-bit DMA mask SWIOTLB bounce 或 CMA 低地址分配
CMA 分配失败 CMA 区域太小 增大 cma= 参数

11.5 驱动开发要点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 设置 DMA mask
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));

// 2. Coherent 分配(帧缓冲等)
void *cpu = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);

// 3. Streaming 映射(网络/块 I/O)
dma_addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
// ... 设备 DMA ...
dma_unmap_single(dev, dma_addr, len, DMA_TO_DEVICE);

// 4. SG 映射(scatter-gather)
nents = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
// ...
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);

十二、总结

kernel/dma 是 Linux 设备 DMA 访问的统一框架:

  1. mapping.c — 公共 API 入口,根据 dev->dma_ops 和 direct 条件分发
  2. direct.c — 无 IOMMU 时的直接物理地址映射,含 CMA 优先分配
  3. swiotlb.c — 软件 bounce buffer,解决 DMA 地址受限问题
  4. contiguous.c — CMA 集成,为多媒体等提供大块连续物理内存
  5. coherent.c / pool.c — 设备预留内存和原子 coherent 池
  6. debug.c — DMA-API 使用错误检测

在 RK3588 平台上,DMA 子系统与 ARM SMMU、CMA、dma-buf 紧密配合,是 VOP/RGA/VPU/ISP、Ethernet、PCIe 等所有外设驱动的基础。


附录:源文件完整清单

文件 行数 分类
debug.c 1,604 DMA-API 调试
swiotlb.c 1,112 软件 I/O TLB
mapping.c 831 API 入口
direct.c 657 Direct 映射
contiguous.c 443 CMA 集成
coherent.c 403 Coherent 内存
map_benchmark.c 378 性能测试
pool.c 295 Atomic pool
direct.h 126 Direct inline
debug.h 130 调试头文件
ops_helpers.c 93 Ops 辅助
remap.c 70 Remap 辅助
dummy.c 38 Dummy ops

kernel/entry 用户态/内核态入口机制与原理详解

kernel/entry 用户态/内核态入口机制与原理详解

源码路径rk3588/kernel-6.1/kernel/entry/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)

该目录实现 Linux 内核 Generic Entry(通用入口/出口) 框架,统一管理从用户态进入内核(系统调用、中断、异常)以及返回用户态时的公共逻辑:RCU/上下文跟踪、lockdep、tracing、ptrace、seccomp、信号投递、调度等。

RK3588 说明:ARM64 未启用 CONFIG_GENERIC_ENTRY,而是在 arch/arm64/kernel/entry-common.csyscall.c 中实现了语义等价的架构专用入口路径。本目录代码定义了通用框架的设计规范;理解本目录即理解 ARM64 入口逻辑的”蓝图”。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
# 禁止 sanitizer 污染 noinstr 代码
KASAN_SANITIZE := n
UBSAN_SANITIZE := n
KCOV_INSTRUMENT := n

obj-$(CONFIG_GENERIC_ENTRY) += common.o syscall_user_dispatch.o
obj-$(CONFIG_KVM_XFER_TO_GUEST_WORK) += kvm.o
配置项 说明
CONFIG_GENERIC_ENTRY 启用通用入口框架(x86/s390/loongarch 等)
CONFIG_KVM_XFER_TO_GUEST_WORK KVM 进入 guest 前的 pending work 处理

启用 GENERIC_ENTRY 的架构:x86、 s390、 loongarch 等(arch/*/Kconfigselect GENERIC_ENTRY)。

ARM64/RK3588:不 select GENERIC_ENTRY,使用 arch/arm64/kernel/entry-common.c

1.2 源文件

文件 规模 功能
common.c ~490 行 通用入口/出口核心:syscall、irq、NMI
syscall_user_dispatch.c ~109 行 Syscall User Dispatch(SUD)机制
kvm.c ~49 行 KVM 进入 guest 前 pending work
common.h ~7 行 内部头文件
include/linux/entry-common.h ~468 行 公共 API 与架构 hook 定义

二、整体架构

2.1 入口/出口分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                  用户态 (EL0)

┌───────────────┼───────────────┐
│ │ │
系统调用 SVC 中断/异常 页错误等
│ │ │
▼ ▼ ▼
enter_from_user irqentry_enter enter_from_user
│ │ │
▼ ▼ ▼
syscall 处理 IRQ handler fault 处理
│ │ │
▼ ▼ ▼
exit_to_user irqentry_exit exit_to_user
│ │ │
└───────────────┴───────────────┘

用户态 (EL0)

2.2 每次入口/出口的统一职责

阶段 职责
进入内核 lockdep 标记 IRQ 关闭、RCU/context tracking 切换、tracing、kmsan
内核处理 实际 syscall/IRQ/fault 逻辑
退出内核 处理 pending work(信号、调度、uprobe、livepatch)、RCU 切换回用户态、lockdep/tracing 恢复

2.3 设计原则

  • noinstr 代码段:入口/出口关键路径标记 noinstr,避免 ftrace/KASAN 插桩导致递归
  • instrumentation_begin/end:在安全点之后才允许插桩
  • 架构 hookarch_enter_from_user_modearch_exit_to_user_mode 等 weak/inline 函数供架构扩展

三、核心入口/出口函数

3.1 用户态进入(common.c)

1
2
3
4
5
6
7
void noinstr enter_from_user_mode(struct pt_regs *regs)
{
arch_enter_from_user_mode(regs); // 架构 hook
lockdep_hardirqs_off();
user_exit_irqoff(); // RCU/context tracking: USER → KERNEL
trace_hardirqs_off_finish(); // ftrace: 标记 IRQ off
}

3.2 用户态退出

1
2
3
4
5
6
7
8
void noinstr exit_to_user_mode(void)
{
trace_hardirqs_on_prepare();
lockdep_hardirqs_on_prepare();
user_enter_irqoff(); // RCU/context tracking: KERNEL → USER
arch_exit_to_user_mode(); // 架构 hook(推测执行缓解等)
lockdep_hardirqs_on();
}

3.3 函数对照表

函数 场景 说明
enter_from_user_mode() 通用用户态进入 建立 kernel 上下文状态
syscall_enter_from_user_mode() 系统调用进入 enter + 开 IRQ + syscall work
syscall_enter_from_user_mode_prepare() 分步进入(仅 prepare) 架构需中间插入逻辑时使用
syscall_enter_from_user_mode_work() 分步进入(仅 work) ptrace/seccomp/tracepoint
exit_to_user_mode() 通用用户态退出 最终状态切换
syscall_exit_to_user_mode() 系统调用退出 exit work + exit_to_user_mode
syscall_exit_to_user_mode_work() 分步退出 不含最终 exit_to_user_mode
irqentry_enter_from_user_mode() 中断从用户态进入 同 enter_from_user_mode
irqentry_exit_to_user_mode() 中断返回用户态 exit work + exit_to_user_mode
irqentry_enter() 通用中断进入 自动判断 user/kernel/idle
irqentry_exit() 通用中断退出 自动判断返回目标
irqentry_nmi_enter/exit() NMI 进入/退出 NMI 专用 lockdep/RCU

四、系统调用入口路径

4.1 完整流程

1
2
3
4
5
6
7
8
9
10
11
12
架构 syscall 入口 (如 x86 syscall/sysenter)
→ syscall_enter_from_user_mode(regs, nr)
├── __enter_from_user_mode(regs) // RCU/lockdep/tracing
├── local_irq_enable()
└── __syscall_enter_from_user_work()
└── syscall_trace_enter(regs, nr, work)
├── [1] syscall_user_dispatch // SUD 过滤
├── [2] ptrace_report_syscall_entry
├── [3] __secure_computing // seccomp
├── [4] trace_sys_enter // tracepoint
└── [5] audit_syscall_entry // audit
→ 架构调用实际 syscall 处理函数

4.2 syscall_work 标志

1
2
3
// include/linux/entry-common.h
#define SYSCALL_WORK_ENTER (SECCOMP | TRACEPOINT | TRACE | EMU | AUDIT | USER_DISPATCH)
#define SYSCALL_WORK_EXIT (TRACEPOINT | TRACE | AUDIT | USER_DISPATCH | EXIT_TRAP)
标志 功能
SYSCALL_WORK_SECCOMP seccomp BPF 过滤
SYSCALL_WORK_SYSCALL_TRACE ptrace 追踪
SYSCALL_WORK_SYSCALL_EMU ptrace syscall 模拟
SYSCALL_WORK_SYSCALL_TRACEPOINT ftrace sys_enter tracepoint
SYSCALL_WORK_SYSCALL_AUDIT audit 审计
SYSCALL_WORK_SYSCALL_USER_DISPATCH Syscall User Dispatch
SYSCALL_WORK_SYSCALL_EXIT_TRAP 单步退出 trap

4.3 跳过系统调用

syscall_trace_enter() 返回 -1 表示跳过 syscall 执行:

  • ptrace 模拟(SYSCALL_EMU)
  • seccomp 拒绝
  • SUD 拦截

五、返回用户态路径

5.1 exit_to_user_mode_loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static unsigned long exit_to_user_mode_loop(struct pt_regs *regs, unsigned long ti_work)
{
while (ti_work & EXIT_TO_USER_MODE_WORK) {
local_irq_enable_exit_to_user(ti_work);

if (ti_work & _TIF_NEED_RESCHED)
schedule(); // 调度

if (ti_work & _TIF_UPROBE)
uprobe_notify_resume(regs); // uprobe

if (ti_work & _TIF_PATCH_PENDING)
klp_update_patch_state(current); // livepatch

if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
arch_do_signal_or_restart(regs); // 信号投递

if (ti_work & _TIF_NOTIFY_RESUME)
resume_user_mode_work(regs); // rseq 等

arch_exit_to_user_mode_work(regs, ti_work); // 架构 TIF work

local_irq_disable_exit_to_user();
tick_nohz_user_enter_prepare();
ti_work = read_thread_flags(); // 重新检查
}
return ti_work;
}

5.2 EXIT_TO_USER_MODE_WORK 标志

1
2
3
4
#define EXIT_TO_USER_MODE_WORK  \
(_TIF_SIGPENDING | _TIF_NOTIFY_RESUME | _TIF_UPROBE | \
_TIF_NEED_RESCHED | _TIF_PATCH_PENDING | _TIF_NOTIFY_SIGNAL | \
ARCH_EXIT_TO_USER_MODE_WORK)

5.3 系统调用退出流程

1
2
3
4
5
6
7
8
9
10
11
syscall 处理完成
→ syscall_exit_to_user_mode(regs)
├── syscall_exit_to_user_mode_prepare(regs)
│ ├── rseq_syscall()
│ └── syscall_exit_work() // audit/trace/ptrace exit
├── exit_to_user_mode_prepare(regs)
│ ├── exit_to_user_mode_loop() // 处理所有 TIF pending work
│ ├── arch_exit_to_user_mode_prepare()
│ └── addr_limit_user_check() // 安全检查
└── __exit_to_user_mode() // RCU/lockdep/tracing 恢复
→ 架构 restore context 并 ERET 返回用户态

六、中断/异常入口路径

6.1 irqentry_enter

统一的中断入口状态管理,自动判断来源:

1
2
3
4
5
6
7
8
9
10
irqentry_state_t irqentry_enter(struct pt_regs *regs)
{
if (user_mode(regs))
irqentry_enter_from_user_mode(regs); // 来自用户态
else if (is_idle_task(current))
ct_irq_enter(); // idle 任务特殊 RCU 处理
else
rcu_irq_enter_check_tick(); // 内核态 RCU tick 检查
// lockdep + tracing
}

6.2 irqentry_exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void irqentry_exit(struct pt_regs *regs, irqentry_state_t state)
{
if (user_mode(regs))
irqentry_exit_to_user_mode(regs); // → 用户态:完整 exit work
else if (!regs_irqs_disabled(regs)) {
if (state.exit_rcu)
ct_irq_exit(); // idle RCU 恢复
irqentry_exit_cond_resched(); // 内核态可抢占调度
trace_hardirqs_on();
} else {
if (state.exit_rcu)
ct_irq_exit();
}
}

6.3 NMI 入口/出口

1
2
3
4
5
6
7
8
9
10
11
irqentry_nmi_enter(regs)
→ __nmi_enter()
→ lockdep_hardirq_enter()
→ ct_nmi_enter()
→ ftrace_nmi_enter()

irqentry_nmi_exit(regs, state)
→ ftrace_nmi_exit()
→ ct_nmi_exit()
→ lockdep_hardirq_exit()
→ __nmi_exit()

NMI 有独立的 lockdep/RCU 路径,因为 NMI 不可被普通中断打断。


七、Syscall User Dispatch

7.1 概述

syscall_user_dispatch.c 实现 Syscall User Dispatch(SUD),允许用户空间指定”允许直接发起 syscall 的代码区域”,区域外的 syscall 被拦截并发送 SIGSYS

用途:Wine/Proton 等兼容层,让原生 Linux syscall 与转译层 syscall 共存。

7.2 工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool syscall_user_dispatch(struct pt_regs *regs)
{
// 1. 检查 PC 是否在允许的 [offset, offset+len) 区域内
if (PC 在允许区域内)
return false; // 正常执行 syscall

// 2. 如有 selector,读取用户态字节决定 ALLOW/BLOCK
if (selector == ALLOW)
return false;

// 3. 拦截:回滚 syscall 并发送 SIGSYS
syscall_rollback(current, regs);
trigger_sigsys(regs); // si_code = SYS_USER_DISPATCH
return true;
}

7.3 用户空间配置

通过 prctl 配置:

1
2
3
4
// prctl(PR_SYS_DISPATCH_*, ...)
set_syscall_user_dispatch(mode, offset, len, selector)
→ 设置 current->syscall_dispatch { offset, len, selector }
→ set_syscall_work(SYSCALL_USER_DISPATCH)

八、KVM 进入 Guest 模式

8.1 概述

kvm.cKVM vCPU 进入 guest 模式前 处理 host pending work,类似 exit_to_user_mode_loop 但针对虚拟化场景。

8.2 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int xfer_to_guest_mode_handle_work(struct kvm_vcpu *vcpu)
{
ti_work = read_thread_flags();
if (!(ti_work & XFER_TO_GUEST_MODE_WORK))
return 0;

// 循环处理 pending work
do {
if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
kvm_handle_signal_exit(vcpu); // 信号 → 退出 guest

if (ti_work & _TIF_NEED_RESCHED)
schedule();

if (ti_work & _TIF_NOTIFY_RESUME)
resume_user_mode_work(NULL);

ret = arch_xfer_to_guest_mode_handle_work(vcpu, ti_work);
if (ret) return ret;

ti_work = read_thread_flags();
} while (ti_work & XFER_TO_GUEST_MODE_WORK || need_resched());
}

8.3 相关 API(entry-kvm.h)

函数 功能
xfer_to_guest_mode_handle_work() 处理所有 pending work
xfer_to_guest_mode_work_pending() 快速检查是否有 pending work
xfer_to_guest_mode_prepare() IRQ 关闭时的 NOHZ 准备

九、noinstr 与编译约束

9.1 为什么需要 noinstr

入口/出口代码在 中断关闭、RCU 特殊状态 下运行。若 ftrace/KASAN 插桩:

  • ftrace 回调可能触发 page fault → 递归异常
  • KASAN 检测可能分配内存 → 死锁

9.2 Makefile 编译约束

1
2
3
4
KASAN_SANITIZE := n          # 整个目录禁用 KASAN
UBSAN_SANITIZE := n # 禁用 UBSAN
KCOV_INSTRUMENT := n # 禁用 KCOV
CFLAGS_REMOVE_common.o = -fstack-protector # 禁用栈保护

9.3 instrumentation 边界

1
2
3
4
5
6
7
8
9
10
11
noinstr void syscall_enter_from_user_mode(...)
{
__enter_from_user_mode(regs); // noinstr 区域

instrumentation_begin(); // ← 安全边界
local_irq_enable();
ret = __syscall_enter_from_user_work(regs, syscall);
instrumentation_end(); // ← 安全边界

return ret;
}

十、RK3588/ARM64 对应实现

10.1 架构差异

方面 Generic Entry (kernel/entry) ARM64 (RK3588)
配置 CONFIG_GENERIC_ENTRY=y 未启用
入口文件 kernel/entry/common.c arch/arm64/kernel/entry-common.c
Syscall 处理 架构调用 generic 函数 arch/arm64/kernel/syscall.c
返回用户态 TIF work exit_to_user_mode_loop() do_notify_resume()
SUD syscall_user_dispatch.c 不支持(无 GENERIC_ENTRY)

10.2 ARM64 系统调用路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
EL0 SVC 指令
→ entry.S: el0_svc / el0_svc_compat
→ entry-common.c: el0_svc()
├── enter_from_user_mode() // 架构版:lockdep/RCU/tracing/MTE
├── local_daif_restore(DAIF_PROCCTX) // 开 IRQ
└── do_el0_svc(regs) // syscall.c
├── fp_user_discard() // 清除 SVE/SME 状态
└── el0_svc_common()
├── syscall_trace_enter() // ptrace/seccomp/trace
├── invoke_syscall() // 调用 sys_* 函数
└── syscall_trace_exit()
→ exit_to_user_mode(regs) // entry-common.c
├── prepare_exit_to_user_mode()
│ └── do_notify_resume() // 信号/调度/uprobe 等
└── __exit_to_user_mode() // RCU/lockdep/tracing 恢复
→ entry.S: ret_to_user // 恢复寄存器并 ERET

10.3 ARM64 中断路径

1
2
3
4
5
6
7
8
EL0/EL1 IRQ
→ entry.S: el0t_64_irq_handler / el1h_64_irq_handler
→ entry-common.c:
enter_from_user_mode / enter_from_kernel_mode
do_interrupt_handler()
arm64_preempt_schedule_irq() // 内核态抢占
exit_to_user_mode / exit_to_kernel_mode
→ entry.S: 返回

10.4 ARM64 特有处理

特性 处理位置
MTE (Memory Tagging) mte_check_tfsr_entry/exit, mte_disable_tco_entry
SVE/SME fp_user_discard() 在 syscall 入口
BP hardening arm64_apply_bp_hardening() 在 instruction abort
DAIF 中断掩码 local_daif_mask/restore 替代 generic local_irq_*
Pseudo NMI CONFIG_ARM64_PSEUDO_NMI 区分 IRQ/FIQ

十一、完整系统调用时序

用户态 write() 系统调用 为例(Generic Entry 路径):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[用户态]
app 执行 syscall 指令 (write)
→ CPU 切换到 EL1,保存 pt_regs

[架构入口 - entry.S]
保存寄存器到 pt_regs
调用 C 入口函数

[common.c - 进入]
syscall_enter_from_user_mode(regs, __NR_write)
→ user_exit_irqoff() // RCU: CONTEXT_USER → CONTEXT_KERNEL
→ local_irq_enable()
→ seccomp 检查通过
→ trace_sys_enter(write) // 可选 tracepoint

[内核 syscall 处理]
ksys_write(fd, buf, count)
→ vfs_write()
→ 设备驱动 copy_from_user + 硬件写入

[common.c - 退出]
syscall_exit_to_user_mode(regs)
→ audit_syscall_exit()
→ trace_sys_exit()
→ exit_to_user_mode_loop()
→ (如有信号) arch_do_signal_or_restart()
→ (如需调度) schedule()
→ exit_to_user_mode()
→ user_enter_irqoff() // RCU: CONTEXT_KERNEL → CONTEXT_USER
→ arch_exit_to_user_mode()

[架构出口 - entry.S]
恢复寄存器
ERET 返回 EL0

[用户态]
write() 返回写入字节数

十二、总结

kernel/entry 是 Linux 内核 用户态/内核态边界管理 的通用框架:

  1. 统一入口enter_from_user_mode / irqentry_enter 建立 kernel 上下文(RCU、lockdep、tracing)
  2. Syscall 拦截链 — SUD → ptrace → seccomp → tracepoint → audit 顺序处理
  3. 统一出口exit_to_user_mode_loop 循环处理所有 pending work(信号、调度、uprobe、livepatch)
  4. noinstr 安全 — 关键路径禁止插桩,避免递归异常
  5. 架构 hook — 通过 weak/inline 函数让各架构扩展
  6. KVM 集成 — guest 模式进入前的 work 处理
  7. SUD — 用户态 syscall 区域过滤,服务兼容层

RK3588(ARM64)虽未编译本目录代码,但在 arch/arm64/kernel/entry-common.c 中实现了相同语义的入口/出口逻辑,是理解 ARM64 异常/系统调用/中断处理的关键参照。


附录:源文件清单

文件 行数 分类
common.c ~490 通用入口/出口核心
syscall_user_dispatch.c ~109 Syscall User Dispatch
kvm.c ~49 KVM guest 模式 work
common.h ~7 内部头文件
include/linux/entry-common.h ~468 公共 API 定义
include/linux/entry-kvm.h ~99 KVM 入口 API

ARM64 对应文件(RK3588 实际使用)

文件 功能
arch/arm64/kernel/entry.S 异常向量、上下文保存/恢复
arch/arm64/kernel/entry-common.c C 语言入口/出口逻辑
arch/arm64/kernel/syscall.c 系统调用分发与 trace
arch/arm64/kernel/signal.c do_notify_resume 信号/work 处理

kernel/events 性能事件(Perf Events)机制与原理详解

kernel/events 性能事件(Perf Events)机制与原理详解

源码路径rk3588/kernel-6.1/kernel/events/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)

该目录实现 Linux 内核 Perf Events(性能事件) 子系统的核心框架,为 perfftrace、BPF、KVM PMU 等提供统一的性能计数、采样、追踪基础设施。用户态通过 perf_event_open(2) 创建事件,内核将采样数据写入 mmap 环形缓冲区,供 perf record 等工具消费。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
obj-y := core.o ring_buffer.o callchain.o

obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
obj-$(CONFIG_HW_BREAKPOINT_KUNIT_TEST) += hw_breakpoint_test.o
obj-$(CONFIG_UPROBES) += uprobes.o
配置项 说明
CONFIG_PERF_EVENTS 启用 perf 子系统(kernel/Makefileobj-$(CONFIG_PERF_EVENTS) += events/
CONFIG_HAVE_HW_BREAKPOINT 硬件断点支持(ARM64 在 PERF_EVENTS 下 select)
CONFIG_UPROBES 用户态探针(trace Kconfig 中 select)
CONFIG_UPROBE_EVENTS 将 uprobes 注册为 perf PMU
CONFIG_HW_BREAKPOINT_KUNIT_TEST 硬件断点 KUnit 单元测试

1.2 源文件

文件 行数 功能
core.c ~13825 perf 核心:syscall、PMU 注册、上下文调度、采样、mmap、BPF 集成
ring_buffer.c ~972 mmap 环形缓冲区读写、AUX 区域、内存屏障同步
callchain.c ~253 采样调用栈收集与缓冲区管理
hw_breakpoint.c ~1051 硬件断点 slot 约束、perf_event 集成
uprobes.c ~2360 用户态断点探针、XOL 单步执行
hw_breakpoint_test.c ~333 KUnit 硬件断点测试(非生产路径)
internal.h ~247 环形缓冲区内部 API、递归上下文保护

相关头文件

文件 功能
include/linux/perf_event.h 公共 API:struct pmustruct perf_event、内核/用户接口
include/linux/hw_breakpoint.h 硬件断点 API
include/linux/uprobes.h uprobes 公共接口

架构/驱动层(RK3588 实际硬件 PMU)

文件 功能
drivers/perf/arm_pmu.c ARM 通用 PMU 驱动框架
drivers/perf/arm_pmu_platform.c 平台 PMU 注册(含 Cortex-A76/A55)
arch/arm64/kernel/perf_event.c ARM64 PMU 架构实现
arch/arm64/kvm/pmu-emul.c KVM guest PMU 模拟

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────┐
│ 用户态:perf / trace-cmd / BPF / gdb │
│ perf_event_open(2) + mmap + poll/read │
└───────────────────────────┬─────────────────────────────────┘

┌───────────────────────────▼─────────────────────────────────┐
│ kernel/events/core.c │
│ 事件分配 · 上下文调度 · 采样输出 · 权限控制 · sysfs │
└───────────────────────────┬─────────────────────────────────┘

┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ software │ │ tracepoint │ │ armv8_pmuv3 │
│ PMU │ │ / kprobe │ │ (CPU PMU) │
│ │ │ / uprobe │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└──────────────────┴──────────────────┘

┌───────────────────────────▼─────────────────────────────────┐
│ kernel/events/ring_buffer.c │
│ perf_buffer 环形缓冲区 → mmap 映射到用户态 │
└─────────────────────────────────────────────────────────────┘

2.2 事件类型(type 字段)

type PMU 名称 说明
PERF_TYPE_HARDWARE armv8_pmuv3 CPU 硬件计数器(cycles、cache miss 等)
PERF_TYPE_SOFTWARE software 软件事件(page fault、context switch 等)
PERF_TYPE_TRACEPOINT tracepoint 内核 tracepoint
动态分配 kprobe / uprobe 内核/用户态动态探针
自定义 各 SoC uncore PMU DDR、CCI 等(RK3588 通用 ARM PMU 为主)

2.3 与其他子系统的关系

子系统 关系
kernel/trace/ tracepoint PMU 依赖 perf 框架;ftrace 与 perf 共享 tracepoint
kernel/bpf/ BPF 程序可 attach 到 perf event 作为 overflow handler
kernel/cgroup/ CONFIG_CGROUP_PERF 支持按 cgroup 过滤事件
kernel/entry/ 采样时获取 pt_regs;uprobe 通过 TIF_UPROBE 在返回用户态时处理

三、核心数据结构

3.1 struct pmu — 性能监控单元抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct pmu {
struct list_head entry;
const char *name;
int type; // PERF_TYPE_* 或动态 ID
int capabilities; // PERF_PMU_CAP_*

// 生命周期回调
int (*event_init)(struct perf_event *event);
int (*add)(struct perf_event *event, int flags);
void (*del)(struct perf_event *event, int flags);
void (*start)(struct perf_event *event, int flags);
void (*stop)(struct perf_event *event, int flags);
void (*read)(struct perf_event *event); // 读取计数值

struct perf_cpu_context __percpu *pmu_cpu_context;
// ...
};

每个 PMU(software、tracepoint、armv8_pmuv3 等)通过 perf_pmu_register() 注册到全局 PMU 链表。

3.2 struct perf_event — 单个性能事件

1
2
3
4
5
6
7
8
9
10
11
struct perf_event {
struct pmu *pmu;
struct perf_event_attr attr; // 用户态传入的属性
struct perf_event_context *ctx; // 所属上下文
struct perf_event *group_leader;
struct list_head event_entry;

struct hw_perf_event hw; // 硬件/软件具体状态
struct perf_buffer *rb; // 输出环形缓冲区
// overflow_handler, prog (BPF), ...
};

struct perf_event_attr 定义采样类型(sample_type)、采样周期(sample_period / sample_freq)、过滤条件(exclude_kernelexclude_user)等。

3.3 struct perf_event_context — 事件上下文

1
2
3
4
5
6
7
8
9
10
struct perf_event_context {
struct pmu *pmu;
struct task_struct *task; // NULL 表示 per-CPU 上下文
struct list_head event_list;
struct mutex mutex;
raw_spinlock_t lock;
int nr_events;
int nr_active;
// ...
};
  • task context:绑定到特定进程/线程的事件集合
  • cpu context:绑定到特定 CPU 的事件集合(task == NULL

3.4 struct perf_cpu_context — 每 CPU 上下文

1
2
3
4
5
6
struct perf_cpu_context {
struct perf_event_context ctx; // CPU 级上下文
struct perf_event_context *task_ctx; // 当前运行任务的上下文
struct pmu *pmu;
// active_ctx_list, hrtimer (mux rotation), ...
};

每个 PMU 在每个 CPU 上有一个 perf_cpu_context,管理该 PMU 在该 CPU 上的所有事件调度。

3.5 struct perf_buffer — 环形缓冲区(internal.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct perf_buffer {
refcount_t refcount;
int nr_pages;
int overwrite;
local_t head; // 内核写指针
local_t lost; // 丢失记录数
long watermark; // 唤醒水位线
unsigned int nest; // 嵌套写计数
struct perf_event_mmap_page *user_page; // 用户可见 meta page
void *data_pages[]; // 数据页数组

// AUX 区域(Intel PT、ARM SPE 等)
long aux_head;
int aux_nr_pages;
void **aux_pages;
};

四、PMU 抽象与注册

4.1 初始化顺序(perf_event_init)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __init perf_event_init(void)
{
idr_init(&pmu_idr);
perf_event_init_all_cpus();
init_srcu_struct(&pmus_srcu);

perf_pmu_register(&perf_swevent, "software", PERF_TYPE_SOFTWARE);
perf_pmu_register(&perf_cpu_clock, NULL, -1);
perf_pmu_register(&perf_task_clock, NULL, -1);
perf_tp_register(); // tracepoint / kprobe / uprobe PMU

perf_event_init_cpu(smp_processor_id());
init_hw_breakpoint();
perf_event_cache = KMEM_CACHE(perf_event, SLAB_PANIC);
}

4.2 perf_pmu_register 流程

1
2
3
4
5
6
perf_pmu_register(pmu, name, type)
→ 分配 pmu_disable_count (percpu)
→ idr_alloc 分配 type ID(非 SOFTWARE 类型)
→ 分配/关联 perf_cpu_context (percpu)
→ 加入全局 pmus 链表
→ 注册 sysfs 设备(device_initcall 阶段)

4.3 已注册 PMU 一览

PMU type 注册位置
software PERF_TYPE_SOFTWARE core.c
cpu_clock / task_clock -1(无 type) core.c
tracepoint PERF_TYPE_TRACEPOINT core.c
kprobe 动态 core.c (CONFIG_KPROBE_EVENTS)
uprobe 动态 core.c (CONFIG_UPROBE_EVENTS)
armv8_pmuv3 PERF_TYPE_HARDWARE arch/arm64 + drivers/perf

五、事件生命周期

5.1 perf_event_open 系统调用

1
2
3
SYSCALL_DEFINE5(perf_event_open,
struct perf_event_attr __user *, attr_uptr,
pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)

主要步骤:

1
2
3
4
5
6
7
8
1. perf_copy_attr()           — 拷贝并校验 attr
2. security_perf_event_open() — LSM 安全检查
3. perf_allow_kernel() — paranoid 权限检查
4. find_lively_task_by_vpid() — 解析目标 task(pid != -1)
5. perf_event_alloc() — 分配 perf_event,匹配 PMU
6. perf_event_set_output() — 关联输出 ring buffer
7. perf_install_in_context() — 安装到 context 并 enable
8. anon_inode 创建 fd — 返回 event fd

5.2 perf_event_alloc 流程

1
2
3
4
5
6
perf_event_alloc(attr, cpu, task, group_leader, ...)
→ 遍历 pmus 链表,调用 pmu->event_init(event)
→ 匹配第一个返回 0 的 PMU
→ 分配 perf_event_context(task 或 cpu)
→ 初始化 hw_perf_event 状态
→ inherit 处理(子进程继承)

5.3 安装到上下文

1
2
3
4
5
6
7
8
perf_install_in_context(ctx, event, cpu)
→ 若在目标 CPU 上:直接 __perf_install_in_context()
→ 否则:smp_call_function_single / task_function_call 远程安装

__perf_install_in_context():
→ list_add_event() + perf_group_attach()
→ ctx_sched_in() — 调度事件到硬件
→ event->state = PERF_EVENT_STATE_ACTIVE

5.4 事件状态机

1
2
3
4
PERF_EVENT_STATE_INACTIVE  →  add/start  →  PERF_EVENT_STATE_ACTIVE
PERF_EVENT_STATE_ACTIVE → stop/del → PERF_EVENT_STATE_INACTIVE
PERF_EVENT_STATE_ERROR — 初始化失败
PERF_EVENT_STATE_OFF — 被显式 disable

六、上下文调度机制

6.1 调度优先级

硬件 PMU 计数器数量有限,多个事件竞争时需按优先级调度:

1
2
3
4
5
优先级(高 → 低):
1. CPU pinned (EVENT_CPU | EVENT_PINNED)
2. task pinned (EVENT_PINNED)
3. CPU flexible (EVENT_CPU | EVENT_FLEXIBLE)
4. task flexible (EVENT_FLEXIBLE)

6.2 ctx_resched 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void ctx_resched(struct perf_cpu_context *cpuctx,
struct perf_event_context *task_ctx,
enum event_type_t event_type)
{
perf_pmu_disable(cpuctx->ctx.pmu);

// 1. 按优先级 sched_out 低优先级事件
task_ctx_sched_out(cpuctx, task_ctx, event_type);
cpu_ctx_sched_out(cpuctx, ctx_event_type);

// 2. 按优先级 sched_in 高优先级事件
perf_event_sched_in(cpuctx, task_ctx);

perf_pmu_enable(cpuctx->ctx.pmu);
}

6.3 任务切换时的 perf 处理

1
2
3
4
context_switch(old, new)
→ perf_event_context_sched_out(old) // 卸载 old task 的 perf events
→ perf_event_context_sched_in(new) // 加载 new task 的 perf events
→ perf_cgroup_switch() // cgroup perf 时间戳更新

6.4 Mux 轮转(hrtimer)

当 flexible 事件数超过硬件 counter 数量时,通过 hrtimer 周期性轮转计数器,使各事件共享硬件资源:

1
2
3
perf_mux_hrtimer_handler()
→ perf_rotate_context(cpuctx)
→ ctx_resched() — 切换 active 事件组

七、环形缓冲区(ring_buffer.c)

7.1 设计目标

  • 内核 NMI/IRQ 上下文写入,用户态 mmap 读取
  • 支持 overwrite 模式(环形覆盖)和 lossless 模式(缓冲区满则丢弃)
  • 支持 AUX 区域(ARM SPE、Intel PT 等辅助追踪数据)
  • 正确的内存屏障保证无锁 SPSC(单生产者单消费者)语义

7.2 生产者-消费者同步

1
2
3
4
5
6
7
8
kernel (producer)              userspace (consumer)
───────────────── ─────────────────────
if (LOAD ->data_tail) { LOAD ->data_head
(A) control dep smp_rmb() (C)
STORE $data LOAD $data
smp_wmb() (B) smp_mb() (D)
STORE ->data_head STORE ->data_tail
}
  • A ↔ D:control dependency / full barrier
  • B ↔ C:WMB / RMB 配对

7.3 嵌套写入(nest 计数)

NMI 可能嵌套在 IRQ 采样中,rb->nest 确保只有最外层写入完成后才发布 data_head

1
2
3
perf_output_get_handle()  → rb->nest++
// ... 写入 sample record ...
perf_output_put_handle() → 仅 nest==1 时更新 user_page->data_head

7.4 关键 API

函数 功能
rb_alloc() 分配 perf_buffer 及 data pages
perf_output_begin/end() 开始/结束一次 sample 写入
perf_event_wakeup() 水位线到达时 irq_work 唤醒 poll
rb_alloc_aux() 分配 AUX 追踪区域
perf_mmap_to_page() mmap fault 处理,映射 data page

八、采样与输出(core.c)

8.1 溢出处理链

1
2
3
4
5
6
7
8
9
10
PMU 中断 / hrtimer / tracepoint 触发
→ __perf_event_overflow(event, data, regs)
→ 频率节流检查
→ 调用 event->overflow_handler()
→ 默认:perf_event_output()
→ BPF attach:bpf_overflow_handler() → bpf_prog_run()
→ perf_prepare_sample() — 填充 sample 字段
→ output_begin() — 获取 ring buffer 空间
→ perf_output_sample() — 写入 PERF_RECORD_SAMPLE
→ perf_output_end() — 发布 head

8.2 sample_type 字段

标志 内容
PERF_SAMPLE_IP 指令指针
PERF_SAMPLE_TID pid/tid
PERF_SAMPLE_TIME 时间戳
PERF_SAMPLE_CALLCHAIN 调用栈
PERF_SAMPLE_REGS 寄存器快照
PERF_SAMPLE_STACK_USER 用户栈 dump
PERF_SAMPLE_DATA_SRC 内存访问来源(cache level 等)

8.3 权限与安全

sysctl 默认值 说明
kernel.perf_event_paranoid 2 ≥2 仅允许用户态事件;0 允许内核态;<0 无限制
kernel.perf_event_mlock ~516 KB 每用户 perf mmap 锁定内存上限
kernel.perf_event_max_sample_rate 100000 最大采样频率 (Hz)
kernel.perf_cpu_time_max_percent 25 perf 采样占用 CPU 时间上限 (%)

九、调用栈回溯(callchain.c)

9.1 功能

sample_type 包含 PERF_SAMPLE_CALLCHAIN 时,在采样点收集内核/用户态调用栈。

9.2 架构 hook

1
2
3
4
__weak void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
struct pt_regs *regs);
__weak void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
struct pt_regs *regs);

ARM64 在 arch/arm64/kernel/perf_callchain.c 中实现栈展开(frame pointer / ORC)。

9.3 缓冲区管理

1
2
3
4
5
6
perf_callchain(event, regs)
→ get_recursion_context() — NMI/IRQ/softirq 递归保护
→ get_callchain_entry() — 获取 per-CPU 预分配 buffer
→ perf_callchain_kernel() — 内核栈
→ perf_callchain_user() — 用户栈
→ put_callchain_entry()
  • 缓冲区按 CPU 预分配(NMI 安全,不能用 percpu API)
  • sysctl perf_event_max_stack 控制最大栈深度(默认 127)

十、硬件断点(hw_breakpoint.c)

10.1 概述

利用 CPU Debug 寄存器(ARM64 DBGBCR/DBGBVR)实现数据/指令断点,集成到 perf_event 框架。

10.2 Slot 约束管理

硬件断点寄存器数量有限(ARM64 通常每 CPU 若干组),需全局约束:

1
2
3
4
struct bp_cpuinfo {
unsigned int cpu_pinned; // 该 CPU 上 pinned 断点数
struct bp_slots_histogram tsk_pinned; // 任务级 pinned 直方图
};
函数 功能
reserve_bp_slot() 注册前检查并预留 slot
release_bp_slot() 释放 slot
register_user_hw_breakpoint() 用户态 API:注册任务级断点
register_wide_hw_breakpoint() 全 CPU 范围断点

10.3 断点类型

类型 说明
HW_BREAKPOINT_R 读访问断点
HW_BREAKPOINT_W 写访问断点
HW_BREAKPOINT_RW 读写断点
HW_BREAKPOINT_X 指令执行断点

10.4 与 perf 集成

1
2
3
4
5
6
7
// hw_breakpoint.c
static struct pmu perf_breakpoint = {
.event_init = hw_breakpoint_event_init,
.add = hw_breakpoint_add,
.del = hw_breakpoint_del,
// ...
};

用户态通过 perf_event_open 设置 type=PERF_TYPE_BREAKPOINT 创建硬件断点事件。


十一、用户态探针(uprobes.c)

11.1 概述

Uprobes 在用户态可执行文件的指定偏移插入软件断点,命中时执行 probe handler(如 perf 采样、kprobe 式追踪)。

11.2 核心数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct uprobe {
struct rb_node rb_node; // 按 (inode, offset) 索引
struct inode *inode;
loff_t offset;
struct arch_uprobe arch; // 原始/修改指令
struct uprobe_consumer *consumers;
struct list_head pending_list;
};

struct uprobe_task {
struct uprobe *active_uprobe;
enum uprobe_task_state state;
// XOL (execute out of line) 区域
};

11.3 工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注册阶段:
uprobe_register(inode, offset, consumer)
→ 插入 uprobes_tree (rbtree)
→ uprobe_apply() — 对已映射的 VMA 插入 SW breakpoint

命中阶段:
用户态执行到断点 → CPU debug exception
→ uprobe_pre_sstep_notifier() — 设置 TIF_UPROBE
→ 返回用户态路径:uprobe_notify_resume()
→ handle_swbp()
→ 分配 XOL slot
→ 复制原始指令到 XOL 区域
→ 单步执行原始指令
→ uprobe_post_sstep_notifier() — 单步完成
→ handle_singlestep() — 恢复断点,调用 consumer handler

进程 fork/mmap:
uprobe_mmap() — 新映射时自动插入断点
uprobe_dup_mmap() — fork 时复制 uprobe 状态

11.4 XOL(Execute Out of Line)

由于断点指令覆盖了原始指令,需在独立的匿名可执行页中保存并执行原始指令:

1
2
3
原始 VMA:  [SW BREAKPOINT] [next insn...]
XOL area: [original insn] [jump back]
↑ 单步在此执行

11.5 与 perf 集成

1
2
3
4
// core.c (CONFIG_UPROBE_EVENTS)
perf_pmu_register(&perf_uprobe, "uprobe", -1);

// 用户态:perf probe -x /path/to/binary:offset

十二、用户态接口

12.1 系统调用

调用 功能
perf_event_open(2) 创建/打开 perf event
perf_event_read(2) 读取计数值(部分架构)
perf_event_read_value(2) 读取 enabled/running/count
perf_event_query_bpf(2) 查询/attach BPF 程序

12.2 mmap 映射

1
2
fd = perf_event_open(&attr, pid, cpu, -1, 0);
mmap(addr, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

映射布局:

pgoff 内容
0 perf_event_mmap_page(meta page:data_head/data_tail/time/…)
1..N 数据页(sample records)
aux_offset.. AUX 区域(可选,ARM SPE 等)

12.3 常用 perf 命令对应

命令 内核路径
perf stat -e cycles PERF_TYPE_HARDWARE → armv8_pmuv3 PMU
perf record -e syscalls:sys_enter_openat PERF_TYPE_TRACEPOINT
perf probe -x ./a.out:main uprobe PMU
perf record -e cpu-cycles -g callchain.c 栈回溯
perf record -e 'mem:0x1000' hw_breakpoint PMU

十三、RK3588/ARM64 平台说明

13.1 Kconfig 支持

1
2
3
4
5
6
7
# arch/arm64/Kconfig
select HAVE_PERF_EVENTS
select HAVE_HW_BREAKPOINT if PERF_EVENTS
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
config ARCH_SUPPORTS_UPROBES
def_bool y

RK3588 默认启用 CONFIG_PERF_EVENTSCONFIG_HAVE_HW_BREAKPOINTCONFIG_UPROBES

13.2 CPU PMU(Cortex-A76 + A55)

特性 说明
PMU 版本 ARMv8 PMUv3(armv8_pmuv3
计数器数 每 CPU 通常 6 个通用计数器 + 1 个 cycle counter
big.LITTLE PERF_PMU_CAP_HETEROGENEOUS_CPUS — A76/A55 事件编码可能不同
驱动路径 drivers/perf/arm_pmu.c + arch/arm64/kernel/perf_event.c
常见事件 cyclesinstructionscache-refillsbranch-misses

13.3 big.LITTLE 注意事项

RK3588 的 4×A76 + 4×A55 属于异构 CPU:

  • 同一 attr.config 在不同核心上可能映射到不同硬件事件
  • perf stat 跨核心统计时需注意 event 兼容性
  • 可通过 -C 绑定特定 CPU 或 --no-aggr 分核统计

13.4 KVM PMU

arch/arm64/kvm/pmu-emul.c 为 guest 提供 PMU 模拟:

  • guest 访问 PMUSERENR/PMCR 等寄存器时 trap 到 host
  • host 侧创建真实 perf_event 计数 guest 活动
  • kvm_arm_pmu_available static key 控制是否启用

13.5 硬件断点(ARM64)

  • 使用 Debug 架构的 Breakpoint Value/Control 寄存器
  • 每 CPU 有限数量的 breakpoint/watchpoint slot
  • hw_breakpoint.c 统一管理 slot 分配,防止超限
  • gdb watch/break 命令底层使用此机制

13.6 uprobes(ARM64)

  • arch/arm64/include/asm/uprobes.h 定义 struct arch_uprobe
  • 断点指令:BRK #0x004d(UPROBE_SWBP_INSN)
  • XOL 单步:arch_uprobe_skip_sstep() 处理 PC 对齐

十四、完整 perf record 时序

perf record -e cycles -c 10000 ./app 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[用户态 perf]
perf_event_open(PERF_TYPE_HARDWARE, config=cycles, sample_period=10000)
→ core.c: SYSCALL_DEFINE5(perf_event_open)
→ 匹配 armv8_pmuv3 PMU
→ perf_event_alloc() + perf_install_in_context()
→ ctx_sched_in() → pmu->add() → 编程 PMU 计数器

mmap(fd) → ring_buffer.c: perf_mmap()
→ 分配 perf_buffer + data pages
→ 映射 meta page (data_head/data_tail) + data pages

[app 运行]
CPU 执行 app 指令,PMU cycle counter 递减
counter 归零 → PMU 溢出中断
→ arch/arm64/kernel/perf_event.c: arm_pmu interrupt handler
→ __perf_event_overflow()
→ perf_prepare_sample(IP, TID, TIME, ...)
→ perf_callchain() (若 -g 启用)
→ perf_output_begin/end() → 写入 ring buffer
→ perf_event_wakeup() → irq_work → poll 唤醒

[用户态 perf]
poll(fd) 返回 POLLIN
读取 ring buffer: data_tail 追赶 data_head
解析 PERF_RECORD_SAMPLE records
写入 perf.data

close(fd) → perf_event_release()
→ perf_remove_from_context()
→ ctx_sched_out() → pmu->del()
→ rb_free() 释放 ring buffer

十五、总结

kernel/events 是 Linux 性能分析与追踪 的核心基础设施:

  1. PMU 抽象struct pmu 统一 software、tracepoint、hardware 等各类事件源
  2. 上下文调度 — pinned/flexible 优先级 + mux 轮转,在有限硬件计数器上 multiplex 多事件
  3. 环形缓冲区 — 无锁 SPSC mmap 缓冲区,NMI 安全的嵌套写入
  4. 采样链 — overflow → prepare_sample → callchain → output → wakeup
  5. 硬件断点 — Debug 寄存器 slot 约束管理,集成 perf_event 框架
  6. Uprobes — 用户态软件断点 + XOL 单步,支持 perf probe 动态追踪
  7. 安全控制 — paranoid 级别、mlock 限制、LSM 集成

RK3588 作为 ARM64 big.LITTLE 平台,通过 drivers/perf/arm_pmu.carch/arm64/kernel/perf_event.c 提供 CPU PMU 支持;kernel/events 中的 core、ring_buffer、callchain、hw_breakpoint、uprobes 等模块在 RK3588 上完整可用,是 perf stat/record/top、eBPF 性能工具、gdb 硬件断点等功能的内核基础。


附录:源文件清单

文件 行数 分类
core.c ~13825 perf 核心框架
ring_buffer.c ~972 mmap 环形缓冲区
callchain.c ~253 调用栈回溯
hw_breakpoint.c ~1051 硬件断点
uprobes.c ~2360 用户态探针
hw_breakpoint_test.c ~333 KUnit 测试
internal.h ~247 内部头文件

RK3588 相关架构/驱动文件

文件 功能
arch/arm64/kernel/perf_event.c ARM64 PMU 驱动
arch/arm64/kernel/perf_callchain.c ARM64 栈展开
arch/arm64/kvm/pmu-emul.c KVM PMU 模拟
drivers/perf/arm_pmu.c ARM PMU 通用框架
drivers/perf/arm_pmu_platform.c 平台 PMU 注册
include/linux/perf_event.h 公共 API(~1766 行)