首页/目录/全部文章

全部文章

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

笔记列表

kernel/futex 快速用户态互斥(Futex)机制与原理详解

kernel/futex 快速用户态互斥(Futex)机制与原理详解

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

该目录实现 Linux 内核 Futex(Fast Userspace Mutex,快速用户态互斥) 机制,是 pthread 互斥锁、条件变量、信号量等用户态同步原语的底层支撑。核心思想:无竞争时在用户态通过原子操作完成加锁/解锁,仅在需要阻塞/唤醒时才进入内核


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
obj-y += core.o syscalls.o pi.o requeue.o waitwake.o
配置项 说明
CONFIG_FUTEX 启用 futex 子系统(默认 y)
CONFIG_FUTEX_PI 优先级继承 futex(默认 y,依赖 RT_MUTEXES)
CONFIG_FAIL_FUTEX 故障注入测试(debug)
CONFIG_PREEMPT_RT RT 内核下 hash bucket 锁变为 sleeping spinlock

1.2 源文件

文件 行数 功能
core.c ~1159 哈希表初始化、futex key 计算、用户态原子操作封装、robust 清理
waitwake.c ~708 futex_wait / futex_wake / futex_wake_op / futex_waitv 多路等待
requeue.c ~897 futex_requeue / requeue_pi / wait_requeue_pi
pi.c ~1233 优先级继承锁:lock_pi / unlock_pi / pi_state 管理
syscalls.c ~379 系统调用入口:futex / futex_waitv / robust_list
futex.h ~294 内部头文件:数据结构、函数声明

二、整体架构

2.1 设计哲学

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────────────────┐
│ 用户态(glibc/pthread/bionic) │
│ 原子 CAS 操作 futex 变量(userspace fast path) │
│ 仅在需要阻塞/唤醒时调用 futex(2) 进入内核 │
└───────────────────────────┬─────────────────────────────┘
│ syscall
┌───────────────────────────▼─────────────────────────────┐
│ kernel/futex/ │
│ 哈希桶管理等待队列 → 阻塞/唤醒任务 → PI/robust 扩展 │
└─────────────────────────────────────────────────────────┘

Fast path(用户态)lock() 尝试 CAS(0→1),成功则直接返回,零 syscall 开销。

Slow path(内核态):CAS 失败(已有持有者或 WAITERS 位),调用 FUTEX_WAIT 阻塞;解锁方调用 FUTEX_WAKE 唤醒。

2.2 模块职责划分

模块 职责
core.c 基础设施:hash 表、key 计算、cmpxchg、robust 退出清理
waitwake.c 核心 wait/wake 语义、bitset 过滤、wake_op 原子操作
requeue.c 将一个 futex 上的 waiter 批量转移到另一个 futex(条件变量基础)
pi.c 优先级继承,防止优先级反转(PI mutex)
syscalls.c 系统调用分发、robust_list、多路 waitv

2.3 与用户态库的映射

用户态 API 内核 futex 操作
pthread_mutex_lock() 用户态 CAS + FUTEX_WAIT / FUTEX_LOCK_PI
pthread_mutex_unlock() 用户态 store + FUTEX_WAKE / FUTEX_UNLOCK_PI
pthread_cond_wait() FUTEX_WAIT + FUTEX_REQUEUE
pthread_cond_signal() FUTEX_WAKE
pthread_cond_broadcast() FUTEX_WAKE (nr=INT_MAX) + FUTEX_REQUEUE
sem_wait() / sem_post() FUTEX_WAIT / FUTEX_WAKE

三、核心数据结构

3.1 union futex_key — 哈希键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
union futex_key {
struct { // PROCESS_SHARED(文件映射)
u64 i_seq; // inode 序列号
unsigned long pgoff; // 页索引
unsigned int offset; // 页内偏移
} shared;
struct { // PROCESS_PRIVATE
struct mm_struct *mm; // 地址空间
unsigned long address; // 虚拟地址(页对齐)
unsigned int offset; // 页内偏移
} private;
struct { // 哈希比较用
u64 ptr;
unsigned long word;
unsigned int offset;
} both;
};
  • Private futex:key = (mm, address, offset),无需 pin 页面,速度快
  • Shared futex:key = (inode->i_sequence, page->index, offset),跨进程共享

3.2 struct futex_hash_bucket — 哈希桶

1
2
3
4
5
struct futex_hash_bucket {
atomic_t waiters; // 等待者计数(SMP 优化用)
spinlock_t lock;
struct plist_head chain; // 优先级排序的等待队列
} ____cacheline_aligned_in_smp;

全局哈希表 futex_queues[],大小为 256 × num_possible_cpus() 的 2 的幂。

3.3 struct futex_q — 等待队列条目

1
2
3
4
5
6
7
8
9
10
struct futex_q {
struct plist_node list; // 按优先级排序
struct task_struct *task;
spinlock_t *lock_ptr; // 所属 hash bucket 的锁
union futex_key key;
struct futex_pi_state *pi_state; // PI futex 专用
struct rt_mutex_waiter *rt_waiter;
u32 bitset; // bitset 唤醒过滤
atomic_t requeue_state; // requeue_pi 状态机
};

3.4 struct futex_pi_state — 优先级继承状态

1
2
3
4
5
6
7
struct futex_pi_state {
struct list_head list; // 挂在 owner->pi_state_list
struct rt_mutex_base pi_mutex; // 底层 RT mutex
struct task_struct *owner;
refcount_t refcount;
union futex_key key;
};

四、Futex Key 与哈希

4.1 get_futex_key()

1
2
3
4
5
6
7
8
9
10
11
get_futex_key(uaddr, fshared, key, rw)

├── Private (!fshared):
│ key = (current->mm, page_align(uaddr), offset)
│ 无需 pin 页面,直接返回

└── Shared (fshared):
get_user_pages_fast(uaddr)
→ 获取 page → compound_head → mapping
→ key = (inode->i_sequence, page->index, offset)
→ 处理 COW / swap / truncate 等边界情况

4.2 futex_hash()

1
2
3
4
5
struct futex_hash_bucket *futex_hash(union futex_key *key)
{
u32 hash = jhash2((u32 *)key, ..., key->both.offset);
return &futex_queues[hash & (futex_hashsize - 1)];
}

4.3 哈希表初始化

1
2
3
4
5
6
7
8
9
10
11
static int __init futex_init(void)
{
futex_hashsize = roundup_pow_of_two(256 * num_possible_cpus());
futex_queues = alloc_large_system_hash(...);
for (i = 0; i < futex_hashsize; i++) {
atomic_set(&futex_queues[i].waiters, 0);
plist_head_init(&futex_queues[i].chain);
spin_lock_init(&futex_queues[i].lock);
}
}
core_initcall(futex_init);

五、基本 Wait/Wake 流程

5.1 FUTEX_WAIT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
用户态:
val = *futex;
if (cond(val))
futex(FUTEX_WAIT, futex, val);

内核 futex_wait():
futex_wait_setup(uaddr, val, flags, &q, &hb)
→ get_futex_key() 计算 key
→ futex_q_lock(q) 获取 hash bucket 锁
→ futex_get_value_locked() 再次读取 *uaddr
→ if (*uaddr != val) return -EWOULDBLOCK // 值已变,无需等待

futex_queue(q, hb) 入队,释放 bucket 锁
futex_wait_queue(hb, q, to) schedule() 阻塞

被唤醒后:
futex_unqueue(q) 检查是否成功出队
→ 0: 正常唤醒
→ -ETIMEDOUT: 超时
→ -ERESTARTSYS: 信号中断

5.2 FUTEX_WAKE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
用户态:
*futex = newval;
futex(FUTEX_WAKE, futex, nr_wake);

内核 futex_wake():
get_futex_key(uaddr) → futex_hash(&key)

// 优化:无 waiter 时跳过加锁
if (!futex_hb_waiters_pending(hb))
return 0;

spin_lock(&hb->lock)
plist_for_each_entry_safe(this, next, &hb->chain, list) {
if (futex_match(&this->key, &key)) {
if (this->bitset & bitset) // bitset 过滤
futex_wake_mark(&wake_q, this);
if (++ret >= nr_wake) break;
}
}
spin_unlock(&hb->lock)
wake_up_q(&wake_q) 批量唤醒

5.3 FUTEX_WAKE_OP

在一个 syscall 中同时对两个 futex 执行 wake + 原子操作 + 条件 wake:

1
2
3
4
futex_wake_op(uaddr1, uaddr2, nr_wake, nr_wake2, op)
→ 对 uaddr1 执行 futex_wake(nr_wake)
→ 对 uaddr2 执行 arch_futex_atomic_op_inuser(op) // 原子加减/位操作
→ 若 atomic_op 条件满足,对 uaddr2 执行 futex_wake(nr_wake2)

用途:用户态读写锁、复杂同步原语的高效实现。


六、内存序与竞态防护

6.1 核心问题

Waiter 和 Waker 并发时,必须保证:waiter 要么看到 futex 值变化,要么被 waker 唤醒,不能两者都错过。

6.2 SMP 解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CPU 0 (waiter)                    CPU 1 (waker)
val = *futex;
futex_wait(futex, val);
waiters++ (a)
smp_mb() (A) ←─── 配对 ───→ smp_mb() (B)
lock(hb); *futex = newval;
uval = *futex; futex_wake(futex);
if (uval == val)
queue(); schedule(); if (waiters)
lock(hb);
wake();
unlock(hb);
else
waiters-- (b)
  • (A) ↔ (B)futex_hb_waiters_inc()smp_mb__after_atomic()futex_hb_waiters_pending()smp_mb() 配对
  • Waker 看到 waiters > 0 才加锁,避免无 waiter 时的锁开销
  • Waiter 在加锁前递增 waiters,保证 waker 不会错过

6.3 futex_q 唤醒判定

1
2
// futex_q 被认为已唤醒当且仅当:
plist_node_empty(&q->list) || q->lock_ptr == NULL

唤醒顺序:先清空 list 节点,再 store_release lock_ptr = NULL。


七、Requeue 机制

7.1 概述

FUTEX_REQUEUE 将一个 futex(uaddr1)上的 waiter 批量转移到另一个 futex(uaddr2),是 pthread 条件变量 的核心实现。

7.2 流程

1
2
3
4
5
6
futex_requeue(uaddr1, uaddr2, nr_wake, nr_requeue, cmpval, requeue_pi)
→ double_lock_hb(hb1, hb2) 按地址顺序加两把 bucket 锁
→ futex_wake(uaddr1, nr_wake) 先唤醒 uaddr1 上部分 waiter
→ 对 uaddr1 上剩余 waiter:
requeue_futex(q, hb1, hb2, key2) 转移到 hb2
→ double_unlock_hb(hb1, hb2)

7.3 requeue_futex()

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline void requeue_futex(struct futex_q *q,
struct futex_hash_bucket *hb1, struct futex_hash_bucket *hb2,
union futex_key *key2)
{
if (&hb1->chain != &hb2->chain) {
plist_del(&q->list, &hb1->chain);
futex_hb_waiters_dec(hb1);
futex_hb_waiters_inc(hb2);
plist_add(&q->list, &hb2->chain);
q->lock_ptr = &hb2->lock;
}
q->key = *key2;
}

7.4 FUTEX_CMP_REQUEUE

在 requeue 前检查 *uaddr1 == cmpval,仅当条件满足时才执行 requeue。用于条件变量的 “while loop” 语义。

7.5 Requeue-PI

FUTEX_CMP_REQUEUE_PI / FUTEX_WAIT_REQUEUE_PI:requeue 到 PI futex 时,需建立优先级继承链。涉及复杂的状态机(Q_REQUEUE_PI_* 枚举),处理 RT 内核下的并发 wakeup 与 requeue 交错。


八、Priority Inheritance(PI)

8.1 问题背景

优先级反转:高优先级任务等待低优先级任务持有的锁,而中优先级任务抢占低优先级任务,导致高优先级任务被间接阻塞。

8.2 PI Futex 原理

1
2
3
4
5
6
7
8
9
10
11
12
futex 值编码:
bit 31-1: owner TID
bit 0: FUTEX_WAITERS 标志

FUTEX_LOCK_PI:
尝试 CAS(0 → current_tid)
失败 → 解析 owner TID → 建立 PI 链 → 阻塞在 rt_mutex 上
owner 优先级被临时提升到 waiters 中最高优先级

FUTEX_UNLOCK_PI:
查找 top waiter → 将 futex 值设为 waiter 的 TID
→ 唤醒 waiter 并通过 PI 恢复 owner 优先级

8.3 关键函数(pi.c)

函数 功能
futex_lock_pi() PI 加锁:atomic 尝试 → rt_mutex 阻塞
futex_unlock_pi() PI 解锁:传递锁给 top waiter
futex_lock_pi_atomic() 原子路径:解析 owner、attach pi_state
attach_to_pi_owner() 建立 PI 链,处理 owner 退出竞态
fixup_pi_owner() 修正 pi_state owner 并获取锁
wake_futex_pi() PI 唤醒 top waiter

8.4 pi_state 生命周期

1
2
3
4
alloc_pi_state()          ← 从 current->pi_state_cache 分配
→ attach_to_pi_state() ← 关联到 futex key
→ rt_mutex 阻塞/唤醒
→ put_pi_state() ← refcount 归零,回收到 cache 或 kfree

Owner 退出时:exit_pi_state_list() 遍历 task->pi_state_list,清理 PI 链并设置 FUTEX_OWNER_DIED


九、Robust Futex

9.1 问题背景

持有 robust mutex 的线程异常退出(crash/exit)时,锁不会被释放,导致其他 waiter 永久阻塞。

9.2 机制

用户态维护 per-thread robust list(通过 set_robust_list 注册):

1
2
3
4
5
struct robust_list_head {
struct robust_list list;
long futex_offset;
struct robust_list *list_op_pending;
};

线程退出时内核遍历 robust list:

1
2
3
4
5
6
7
futex_exit_release(task)
→ futex_cleanup_begin() 设置 FUTEX_STATE_EXITING
→ exit_robust_list() 遍历 robust_list
→ handle_futex_death() 设置 FUTEX_OWNER_DIED 位
→ futex_wake() 唤醒 waiter
→ exit_pi_state_list() 清理 PI futex
→ futex_cleanup_end() 设置 FUTEX_STATE_DEAD

9.3 FUTEX_OWNER_DIED

Waiter 被唤醒后检查 FUTEX_OWNER_DIED 位:

  • 若 owner 已死,waiter 可尝试 CAS(OWNER_DIED → current_tid) 接管锁
  • 否则正常竞争

十、Futex2 扩展(futex_waitv)

10.1 概述

Linux 6.x 引入的 futex2 接口,支持在单个 syscall 中等待多个 futex。

10.2 futex_waitv 系统调用

1
2
3
4
5
6
SYSCALL_DEFINE5(futex_waitv,
struct futex_waitv __user *, waiters, // 等待数组
unsigned int, nr_futexes, // 数组长度(≤128)
unsigned int, flags,
struct __kernel_timespec __user *, timeout,
clockid_t, clockid)
1
2
3
4
5
6
struct futex_waitv {
__u64 val; // 期望值
__u64 uaddr; // futex 地址
__u32 flags; // FUTEX_32 | FUTEX_PRIVATE_FLAG
__u32 __reserved;
};

10.3 实现(waitwake.c)

1
2
3
4
5
futex_wait_multiple(vs, count, to)
→ futex_wait_multiple_setup() 逐个 futex 检查值并入队
→ futex_sleep_multiple() 统一 schedule
→ 任一 futex 被 wake 或值变化 → 返回对应 index
→ unqueue_multiple() 清理其余 waiter

用途:eventfd + futex 等多路复用等待,减少 syscall 次数。


十一、系统调用接口

11.1 futex(2) 操作码

操作 说明
FUTEX_WAIT 0 等待 futex 值等于 val
FUTEX_WAKE 1 唤醒最多 val 个 waiter
FUTEX_REQUEUE 3 转移 waiter 到 uaddr2
FUTEX_CMP_REQUEUE 4 条件 requeue
FUTEX_WAKE_OP 5 wake + 原子操作 + 条件 wake
FUTEX_LOCK_PI 6 PI 加锁
FUTEX_UNLOCK_PI 7 PI 解锁
FUTEX_TRYLOCK_PI 8 PI 尝试加锁
FUTEX_WAIT_BITSET 9 带 bitset 过滤的 wait
FUTEX_WAKE_BITSET 10 带 bitset 过滤的 wake
FUTEX_WAIT_REQUEUE_PI 11 wait + requeue 到 PI futex
FUTEX_CMP_REQUEUE_PI 12 条件 requeue 到 PI futex
FUTEX_LOCK_PI2 13 PI 加锁(CLOCK_REALTIME 超时)

标志位

标志 说明
FUTEX_PRIVATE_FLAG 128 进程私有 futex(默认)
FUTEX_CLOCK_REALTIME 256 使用 CLOCK_REALTIME 超时

11.2 其他系统调用

调用 功能
set_robust_list(2) 注册当前线程的 robust futex 列表
get_robust_list(2) 获取指定线程的 robust 列表
futex_waitv(2) 多 futex 等待(futex2)

11.3 do_futex 分发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
long do_futex(uaddr, op, val, timeout, uaddr2, val2, val3)
{
cmd = op & FUTEX_CMD_MASK;
if (!(op & FUTEX_PRIVATE_FLAG)) flags |= FLAGS_SHARED;

switch (cmd) {
case FUTEX_WAIT: return futex_wait(...);
case FUTEX_WAKE: return futex_wake(...);
case FUTEX_REQUEUE: return futex_requeue(...);
case FUTEX_LOCK_PI: return futex_lock_pi(...);
case FUTEX_UNLOCK_PI: return futex_unlock_pi(...);
// ...
}
}

十二、RK3588/ARM64 平台说明

12.1 架构支持

ARM64 完整支持 futex 全部特性:

1
2
3
4
# arch/arm64/Kconfig
select HAVE_FUTEX
select ARCH_HAS_FUTEX_ATOMIC
select FUTEX_PI

12.2 用户态原子操作(arch/arm64/include/asm/futex.h)

ARM64 使用 LL/SC(Load-Linked / Store-Conditional) 指令实现 futex 原子操作:

1
2
3
4
5
6
7
8
9
10
11
// futex_atomic_cmpxchg_inatomic — CAS 实现
asm volatile(
"1: ldxr %w1, %2\n" // Load-Exclusive
" sub %w3, %w1, %w5\n" // compare with oldval
" cbnz %w3, 4f\n" // mismatch → exit
"2: stlxr %w3, %w6, %2\n" // Store-Exclusive
" cbz %w3, 3f\n" // success
" ... retry loop ...\n"
"3: dmb ish\n" // Data Memory Barrier
"4:\n"
...);
操作 ARM64 实现
FUTEX_OP_SET mov + STLRX
FUTEX_OP_ADD add + STLRX
FUTEX_OP_OR orr + STLRX
FUTEX_OP_ANDN and + STLRX
FUTEX_OP_XOR eor + STLRX
cmpxchg ldxr + stlxr + dmb ish
  • 最大重试次数:FUTEX_MAX_LOOPS = 128
  • 使用 uaccess_enable_privileged() 允许在内核态访问用户页
  • dmb ish(Inner Shareable)保证 ARM64 弱内存序下的正确性

12.3 big.LITTLE 注意点

  • Futex key 基于虚拟地址/mm,与运行在哪个核心无关
  • PI futex 的优先级继承跨 A76/A55 核心正常工作(基于 RT mutex)
  • futex_hashsize = 256 × 8 = 2048(RK3588 8 核)

12.4 Android/Bionic 相关

RK3588 常用于 Android 平台,Bionic libc 大量使用 futex:

  • pthread_mutexFUTEX_WAIT/WAKEFUTEX_LOCK_PI/UNLOCK_PI
  • pthread_condFUTEX_WAIT + FUTEX_REQUEUE
  • semFUTEX_WAIT/WAKE
  • Java synchronized / Object.wait/notify → 底层 futex

十三、完整 pthread mutex 时序

两个线程竞争 pthread_mutex 为例(非 PI、非 robust):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Thread A — 加锁成功]
lock(&mutex):
CAS(0 → 1) 成功 → 返回(纯用户态,零 syscall)

[Thread B — 加锁阻塞]
lock(&mutex):
CAS(0 → 1) 失败(值为 1)
val = 1; 设置 WAITERS 位: CAS(1 → 0x80000001) // bit31=TID|WAITERS
futex(FUTEX_WAIT, &mutex, 2) // val=2 含 WAITERS 位
→ 内核: futex_wait_setup() → *mutex==2 ✓
→ futex_queue() → schedule() 阻塞

[Thread A — 解锁]
unlock(&mutex):
读取 waiter 存在 → store(0)
futex(FUTEX_WAKE, &mutex, 1)
→ 内核: futex_wake() → 找到 Thread B
→ futex_wake_mark() → wake_up_q()

[Thread B — 被唤醒]
futex_wait 返回 → CAS(0 → 1) 成功 → 持有锁

条件变量 broadcast 时序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Thread B — cond_wait]
lock(&cond_mutex)
unlock(&user_mutex)
futex(FUTEX_WAIT, &cond, 0) // 等待条件
→ 内核阻塞在 cond futex 上

[Thread A — cond_broadcast]
lock(&cond_mutex)
修改条件
unlock(&user_mutex)
futex(FUTEX_CMP_REQUEUE, &cond, &mutex, 1, INT_MAX, &cond_val)
→ wake cond 上 1 个 waiter
→ 将其余 waiter requeue 到 mutex futex
unlock(&cond_mutex)

[Thread B — 被 requeue]
在 mutex futex 上被唤醒 → 竞争 mutex → 重新 lock(&cond_mutex)

十四、总结

kernel/futex 是 Linux 用户态同步的基石:

  1. Fast path 在用户态 — 无竞争时零 syscall,性能接近纯用户态锁
  2. 哈希桶等待队列 — 按 futex key 哈希,plist 优先级排序,支持高效 wake
  3. 内存序保证 — SMP 下 waiters 计数 + memory barrier 防止 lost wakeup
  4. Requeue — 条件变量的核心原语,批量转移 waiter
  5. Priority Inheritance — 通过 RT mutex 解决优先级反转
  6. Robust Futex — 线程异常退出时自动清理持有的锁
  7. Futex2futex_waitv 支持多 futex 单 syscall 等待

RK3588(ARM64)通过 LL/SC 指令(ldxr/stlxr/dmb ish)实现高效的用户态原子操作,完整支持 PI futex 和 robust futex,是 Android/Linux 桌面系统中 pthread 同步的底层支撑。


附录:源文件清单

文件 行数 分类
core.c ~1159 基础设施、key、robust 清理
waitwake.c ~708 wait/wake/wake_op/waitv
requeue.c ~897 requeue / requeue_pi
pi.c ~1233 优先级继承
syscalls.c ~379 系统调用入口
futex.h ~294 内部头文件

相关头文件

文件 功能
include/linux/futex.h 内核公共 API
include/uapi/linux/futex.h 用户态 API、操作码定义
arch/arm64/include/asm/futex.h ARM64 原子操作实现

kernel/gcov 内核代码覆盖率(GCOV)机制与原理详解

kernel/gcov 内核代码覆盖率(GCOV)机制与原理详解

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

该目录实现 Linux 内核 GCOV 代码覆盖率分析 基础设施。通过编译时插桩(-fprofile-arcs -ftest-coverage),在运行时收集每个基本块/分支的执行次数,并通过 debugfs 以标准 .gcda 格式导出到用户态,供 gcovlcovgenhtml 等工具生成覆盖率报告。

与 KCOV 的区别kernel/gcov编译期插桩的代码行/分支覆盖率(开发/测试用);KCOV运行时 syscall/代码路径覆盖(syzkaller 模糊测试用),二者独立,不可混淆。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
ccflags-y := -DSRCTREE='"$(srctree)"' -DOBJTREE='"$(objtree)"'

obj-y := base.o fs.o
obj-$(CONFIG_CC_IS_GCC) += gcc_base.o gcc_4_7.o
obj-$(CONFIG_CC_IS_CLANG) += clang.o
配置项 说明
CONFIG_GCOV_KERNEL 启用 gcov 子系统(默认 n,依赖 DEBUG_FS
CONFIG_GCOV_PROFILE_ALL 对整个内核启用插桩(依赖 ARCH_HAS_GCOV_PROFILE_ALL
CONFIG_CC_IS_GCC 使用 GCC 后端(gcc_base.o + gcc_4_7.o
CONFIG_CC_IS_CLANG 使用 Clang/LLVM 后端(clang.o
CONFIG_FAIL_FUTEX — (无关,gcov 无 fault injection)

编译标志(顶层 Makefile):

1
2
3
CFLAGS_GCOV := -fprofile-arcs -ftest-coverage
# GCC 额外禁用 loop invariant motion,避免计数器失真
CFLAGS_GCOV += -fno-tree-loop-im

1.2 源文件

文件 行数 功能
base.c ~138 公共基础:事件启用、数值序列化、模块卸载通知
fs.c ~882 debugfs 文件系统:节点树、gcda 读写、reset
gcc_base.c ~86 GCC 入口:__gcov_init 等编译器回调
gcc_4_7.c ~438 GCC ≥4.7 数据结构与 gcda 转换
clang.c ~393 Clang/LLVM 覆盖率回调与 gcda 转换
gcov.h ~85 内部 API 与 opaque gcov_info 接口
Kconfig 配置项说明

二、整体架构

2.1 数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
编译阶段                          运行阶段                         分析阶段
───────── ───────── ─────────
-fprofile-arcs 代码执行时 用户态工具
-ftest-coverage 编译器插桩计数器递增 │
│ │ │
▼ ▼ ▼
生成 .gcno(静态) __gcov_init / llvm_gcov_init 读取 debugfs
生成计数器段(.data) 注册 gcov_info │
│ │ ▼
│ gcov_event(GCOV_ADD) cp *.gcda
│ │ │
│ debugfs 节点树 lcov / genhtml
│ /sys/kernel/debug/gcov/ │
└────────────────────────────────┴──────────────────── 覆盖率 HTML 报告

2.2 模块职责

模块 职责
gcc_base.c / clang.c 接收编译器生成的 profiling 元数据,注册到内核链表
gcc_4_7.c / clang.c 实现 gcov_info_* 接口:dup/add/reset/convert_to_gcda
base.c 全局锁、事件重放、模块 notifier、store_gcov_u32/u64
fs.c debugfs 目录树维护、用户态读写 .gcda、reset 控制

2.3 debugfs 目录结构

1
2
3
4
5
6
7
8
9
/sys/kernel/debug/gcov/
├── reset # 写入任意值 → 清零所有计数器
└── <objtree相对路径>/ # 镜像内核源码/对象树路径
├── kernel/
│ └── sched/
│ └── core.gcda # 覆盖率数据(可读)
│ └── core.gcno → ... # 符号链接到 .gcno 静态文件
└── drivers/
└── ...

三、核心数据结构

3.1 struct gcov_info(GCC 版,gcc_4_7.c)

1
2
3
4
5
6
7
8
9
10
11
12
struct gcov_info {
unsigned int version; // GCC version magic
struct gcov_info *next; // 全局链表
unsigned int stamp; // 唯一时间戳
#if (__GNUC__ >= 12)
unsigned int checksum; // 编译单元 checksum
#endif
const char *filename; // 对应 .gcda 文件名
void (*merge[GCOV_COUNTERS])(...); // 各 counter 类型 merge 函数
unsigned int n_functions;
struct gcov_fn_info **functions;
};

3.2 struct gcov_fn_info — 每函数元数据

1
2
3
4
5
6
7
8
9
10
11
12
struct gcov_fn_info {
const struct gcov_info *key; // comdat key
unsigned int ident;
unsigned int lineno_checksum;
unsigned int cfg_checksum;
struct gcov_ctr_info ctrs[]; // 柔性数组:各类型 counter
};

struct gcov_ctr_info {
unsigned int num; // counter 数量
gcov_type *values; // 运行时递增的计数值
};

3.3 struct gcov_node(fs.c)— debugfs 节点

1
2
3
4
5
6
7
8
9
10
11
12
struct gcov_node {
struct list_head list; // 父节点 children 链表
struct list_head children;
struct list_head all; // 全局节点链表
struct gcov_node *parent;
struct gcov_info **loaded_info; // 已加载对象的 profiling 数据
int num_loaded;
struct gcov_info *unloaded_info; // 模块卸载后的持久化副本
struct dentry *dentry;
struct dentry **links; // .gcno 符号链接
char name[];
};

3.4 GCOV 文件格式常量(gcov.h)

常量 说明
GCOV_DATA_MAGIC 0x67636461 (“gcda”) .gcda 文件魔数
GCOV_TAG_FUNCTION 0x01000000 函数记录标签
GCOV_TAG_COUNTER_BASE 0x01a10000 计数器记录基标签

四、编译时插桩机制

4.1 启用方式

Kconfig

1
2
3
CONFIG_GCOV_KERNEL=y
CONFIG_GCOV_PROFILE_ALL=y # 可选:全内核插桩
CONFIG_DEBUG_FS=y # 必须

按文件/目录选择性插桩scripts/Makefile.lib):

1
2
3
4
5
6
7
8
9
# 单个文件
GCOV_PROFILE_sched/core.o := y

# 整个目录
GCOV_PROFILE := y

# 排除(即使 PROFILE_ALL 开启)
GCOV_PROFILE := n
GCOV_PROFILE_futex.o := n

优先级:GCOV_PROFILE_<obj.o> > GCOV_PROFILE > CONFIG_GCOV_PROFILE_ALL

4.2 编译器行为

GCC/Clang 对插桩文件:

  1. 在每个基本块/弧(arc)插入计数器递增代码
  2. 生成 .gcno 文件(编译时静态 CFG 信息)
  3. .data 段放置 gcov_info 结构及计数器数组
  4. 生成 constructor 调用 __gcov_init()(GCC)或 llvm_gcov_init()(Clang)

4.3 注意事项

  • 插桩内核 体积显著增大、运行变慢,仅用于开发/CI
  • 未链接进内核的插桩文件会导致 链接错误,需排除
  • 模块 final link 会过滤 CFLAGS_GCOVMakefile.modfinal
  • gcov 子系统自身 不应被插桩(否则递归/失真)

五、运行时注册流程

5.1 GCC 路径(gcc_base.c)

1
2
3
4
5
6
7
8
9
10
11
void __gcov_init(struct gcov_info *info)
{
mutex_lock(&gcov_lock);
if (gcov_version == 0)
gcov_version = gcov_info_version(info); // 记录 GCC magic

gcov_info_link(info); // 加入全局链表
if (gcov_events_enabled)
gcov_event(GCOV_ADD, info); // 创建 debugfs 节点
mutex_unlock(&gcov_lock);
}

由 GCC constructor 段在每个插桩 .o 加载时自动调用。

5.2 Clang 路径(clang.c)

1
2
3
4
5
6
7
8
9
10
11
12
void llvm_gcov_init(writeout, flush)
{
info = kzalloc(...);
list_add_tail(&info->head, &clang_gcov_list);
current_info = info;
writeout(); // 回调编译器 writeout,依次调用:
// llvm_gcda_start_file()
// llvm_gcda_emit_function() + llvm_gcda_emit_arcs()
// llvm_gcda_end_file()
current_info = NULL;
gcov_event(GCOV_ADD, info);
}

Clang 采用 回调驱动、无状态 设计,在模块 init 时一次性 dump 元数据。

5.3 事件启用与重放(base.c)

1
2
3
4
5
6
7
void gcov_enable_events(void)
{
gcov_events_enabled = 1;
// 重放已注册但未创建 debugfs 节点的 info
while ((info = gcov_info_next(info)))
gcov_event(GCOV_ADD, info);
}

fs.cgcov_fs_init() 在 debugfs 就绪后调用,解决 早期 constructor 早于 debugfs 初始化 的时序问题。

5.4 全局锁

1
2
3
DEFINE_MUTEX(gcov_lock);    // 保护 gcov_info 链表(注册/遍历)
// fs.c 中:
DEFINE_MUTEX(node_lock); // 保护 debugfs 节点树

六、debugfs 导出(fs.c)

6.1 gcov_event 回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void gcov_event(enum gcov_action action, struct gcov_info *info)
{
switch (action) {
case GCOV_ADD:
node = get_node_by_name(gcov_info_filename(info));
if (node)
add_info(node, info); // 已有节点,追加 data set
else
add_node(info); // 新建目录树 + .gcda 文件
break;
case GCOV_REMOVE:
remove_info(node, info); // 模块卸载
break;
}
}

6.2 节点树构建(add_node)

filename 路径逐级创建目录节点:

1
2
3
4
filename = "home/cp/.../kernel/sched/core.gcda"
→ gcov/kernel/sched/ (目录节点)
→ gcov/kernel/sched/core.gcda (数据文件)
→ 符号链接: core.gcno → $(objtree)/kernel/sched/core.gcno

6.3 读取覆盖率数据

1
2
3
4
5
6
// open /sys/kernel/debug/gcov/.../core.gcda
gcov_seq_open()
→ get_accumulated_info(node) // dup + 合并多个 loaded_info
→ gcov_iter_new(info)
→ convert_to_gcda(buffer, info) // 转为标准 .gcda 二进制
→ seq_read() 分页输出

读操作返回 快照副本,不影响运行时计数器。

6.4 写入 / Reset

操作 路径 效果
单文件 reset .gcda 文件 清零该文件所有 counter
全局 reset gcov/reset 清零所有 counter,移除纯 unloaded 节点

6.5 符号链接

1
2
3
4
const struct gcov_link gcov_link[] = {
{ OBJ_TREE, "gcno" }, // 链接到 $(objtree) 下的 .gcno
{ 0, NULL },
};

使用户态 lcov 工具能在同一目录找到 .gcno + .gcda 配对文件。


七、GCDA 格式转换

7.1 convert_to_gcda(GCC 版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
size_t convert_to_gcda(char *buffer, struct gcov_info *info)
{
// File header
store_gcov_u32(GCOV_DATA_MAGIC); // "gcda"
store_gcov_u32(info->version);
store_gcov_u32(info->stamp);
#if GCC >= 12
store_gcov_u32(0); // checksum placeholder
#endif

for each function:
store_gcov_u32(GCOV_TAG_FUNCTION);
store_gcov_u32(length);
store_gcov_u32(ident, lineno_checksum, cfg_checksum);

for each active counter type:
store_gcov_u32(GCOV_TAG_FOR_COUNTER(type));
store_gcov_u32(num * 2);
for each counter value:
store_gcov_u64(value); // 64-bit 分两个 u32 存储
}

7.2 数值编码(base.c)

  • 所有数值以 本机字节序 32 位 unsigned 存储
  • 64 位数值:低 32 位在前,高 32 位在后
  • store_gcov_u32/u64(buffer, off, val)buffer==NULL 时仅计算大小(dry-run)

7.3 数据操作 API

函数 功能
gcov_info_reset() 所有 counter 清零
gcov_info_add(dst, src) 累加两份 profiling 数据
gcov_info_dup() 深拷贝(供 debugfs 读取快照)
gcov_info_is_compatible() 检查 stamp/checksum 是否可合并
gcov_info_free() 释放 dup 副本

八、GCC 与 Clang 双后端

8.1 设计模式

gcov.hstruct gcov_info 声明为 opaque 类型,GCC 和 Clang 各自维护不兼容的内部结构,通过统一接口对外:

1
2
3
4
const char *gcov_info_filename(struct gcov_info *info);
struct gcov_info *gcov_info_next(struct gcov_info *info);
size_t convert_to_gcda(char *buffer, struct gcov_info *info);
// ...

8.2 GCC 后端特点

方面 说明
注册时机 每个 .o 的 constructor 调用 __gcov_init
数据结构 静态分配在 .data,与 gcc/gcov-io.h 兼容
版本适配 GCOV_COUNTERS 随 GCC 版本变化(8~10 个 counter 类型)
GCC 12+ 新增 checksum 字段,GCOV_UNIT_SIZE 改为 4 字节

8.3 Clang 后端特点

方面 说明
注册时机 每模块一次 llvm_gcov_init,回调 writeout
数据结构 动态分配,list_head 链表
兼容性检查 基于 checksum + 各函数 cfg_checksum
参考 LLVM GCOVProfiling.cpp

8.4 未使用的 GCC 回调

1
2
3
void __gcov_flush(void)           { /* Unused */ }
void __gcov_merge_add(...) { /* Unused */ }
void __gcov_exit(void) { /* Unused */ }

内核不做 profiling 数据合并(无 fork/exit 场景),这些回调为空实现但需 EXPORT 以满足链接。


九、模块与持久化

9.1 模块加载/卸载

1
2
3
4
5
6
7
8
9
模块 insmod(插桩编译)
→ 各 .o constructor → __gcov_init
→ gcov_event(GCOV_ADD) → debugfs 节点

模块 rmmod
→ base.c: gcov_module_notifier(MODULE_STATE_GOING)
→ 遍历 gcov_info 链表,移除属于该 module 的 info
→ gcov_event(GCOV_REMOVE)
→ remove_info() → 可选 save_info() 持久化

9.2 gcov_persist

1
2
3
4
static int gcov_persist = 1;   // 默认开启

// 内核参数:gcov_persist=0 关闭
__setup("gcov_persist=", gcov_persist_setup);
  • gcov_persist=1:模块卸载时将 counter 数据 dup 保存node->unloaded_info
  • 重新加载兼容版本模块时,新数据 替换 旧副本
  • 不兼容版本:丢弃旧数据并警告

9.3 多版本冲突

同一 .gcda 节点可能关联多个 loaded_info(如不同版本模块同时加载):

  • 读取时 get_accumulated_info() 合并所有 loaded + unloaded 数据
  • stamp/checksum 兼容 的数据才允许合并

十、用户态使用方法

10.1 典型工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 配置并编译插桩内核
make menuconfig # CONFIG_GCOV_KERNEL=y, CONFIG_GCOV_PROFILE_ALL=y
make -j$(nproc)

# 2. 启动内核,挂载 debugfs
mount -t debugfs none /sys/kernel/debug

# 3. 运行测试用例(触发内核代码路径)

# 4. 收集覆盖率数据
mkdir -p /tmp/gcov-data
cp -r /sys/kernel/debug/gcov/* /tmp/gcov-data/

# 5. 生成报告(需要编译时生成的 .gcno 文件)
lcov --capture --directory /tmp/gcov-data --output-file coverage.info
genhtml coverage.info --output-directory /tmp/coverage-html

10.2 Reset 计数器

1
2
3
4
5
# 清零所有覆盖率计数
echo 1 > /sys/kernel/debug/gcov/reset

# 清零单个文件
echo 1 > /sys/kernel/debug/gcov/path/to/file.gcda

10.3 文件配对

文件 来源 内容
.gcno 编译时生成($(objtree) 静态 CFG、行号映射
.gcda debugfs 运行时导出 动态执行计数

两者需 版本/stamp 匹配,否则 lcov 报错。


十一、RK3588/ARM64 平台说明

11.1 架构支持

1
2
# arch/arm64/Kconfig
select ARCH_HAS_GCOV_PROFILE_ALL

RK3588 支持 CONFIG_GCOV_PROFILE_ALL 全内核插桩。

11.2 编译器选择

工具链 后端文件 说明
GCC(常见 RK3588 内核构建) gcc_base.o + gcc_4_7.o 通过 __gcov_init 注册
Clang/LLVM clang.o 通过 llvm_gcov_init 注册

Rockchip SDK 通常使用 GCC 交叉编译器(如 aarch64-linux-gnu-gcc),走 GCC 后端。

11.3 生产内核注意

  • RK3588 产品内核 默认不启用 CONFIG_GCOV_KERNELdefault n
  • 启用后内核镜像和模块体积增大,启动和运行性能下降
  • 仅建议在 CI 覆盖率测试驱动开发调试 构建中使用
  • CONFIG_KCOV(模糊测试)独立,可同时或分别启用

11.4 big.LITTLE 无关性

GCOV 计数器是 per-compilation-unit 内存变量,与运行在 A76 还是 A55 核心无关,无需额外跨核同步。

11.5 常见排除项

以下文件/目录通常需排除插桩:

类型 原因
kernel/gcov/ 自身 避免自引用
引导/early init 代码 链接或时序问题
汇编文件(.S 无法插桩
未链入 vmlinux 的 lib 文件 链接错误

十二、总结

kernel/gcov 是 Linux 内核 代码覆盖率分析 的完整基础设施:

  1. 编译插桩-fprofile-arcs -ftest-coverage 在基本块插入计数器
  2. 双编译器后端 — GCC(constructor 注册)和 Clang(callback 注册)统一接口
  3. debugfs 导出 — 标准 .gcda 格式,目录树镜像源码路径
  4. 模块感知 — 加载/卸载自动增删节点,支持持久化(gcov_persist
  5. 快照读取 — dup + convert_to_gcda,不影响运行时计数
  6. Reset 支持 — 单文件或全局清零计数器
  7. 符号链接 — 自动链接 .gcno 便于 lcov 配对

RK3588 平台完整支持 GCOV 全内核插桩,典型用途是 内核/driver CI 覆盖率测试开发阶段代码路径验证。生产环境应关闭此选项。


附录:源文件清单

文件 行数 分类
base.c ~138 公共基础与模块 notifier
fs.c ~882 debugfs 文件系统
gcc_base.c ~86 GCC 编译器回调
gcc_4_7.c ~438 GCC 数据结构/gcda 转换
clang.c ~393 Clang/LLVM 后端
gcov.h ~85 内部 API

相关构建配置

文件 功能
kernel/gcov/Kconfig GCOV_KERNEL / GCOV_PROFILE_ALL
scripts/Makefile.lib GCOV_PROFILE per-file 控制
Makefile CFLAGS_GCOV 定义
arch/arm64/Kconfig ARCH_HAS_GCOV_PROFILE_ALL

与 KCOV 对比

特性 GCOV(本目录) KCOV
机制 编译期 -fprofile-arcs 运行时 -fsanitize-coverage
粒度 行/分支/弧 PC/比较操作
导出 debugfs .gcda /sys/kernel/debug/kcov
用途 代码覆盖率报告 模糊测试(syzkaller)
配置 CONFIG_GCOV_KERNEL CONFIG_KCOV

kernel/irq 通用中断子系统机制与原理详解

kernel/irq 通用中断子系统机制与原理详解

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

该目录实现 Linux 内核 Generic IRQ(通用中断) 子系统的核心框架,统一管理从硬件中断信号到驱动 handler 的完整路径:IRQ 描述符、flow handler、irq_chip 抽象、irq_domain 映射、MSI、亲和性、线程化中断等。

架构分工kernel/irq/ 提供 与架构无关 的中断管理逻辑;ARM64 入口汇编、set_handle_irq() 及 GIC 驱动位于 arch/arm64/drivers/irqchip/


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
obj-y := irqdesc.o handle.o manage.o spurious.o resend.o chip.o dummychip.o devres.o
obj-$(CONFIG_IRQ_TIMINGS) += timings.o
obj-$(CONFIG_GENERIC_IRQ_CHIP) += generic-chip.o
obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o
obj-$(CONFIG_IRQ_SIM) += irq_sim.o
obj-$(CONFIG_PROC_FS) += proc.o
obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o
obj-$(CONFIG_GENERIC_IRQ_MIGRATION) += cpuhotplug.o
obj-$(CONFIG_PM_SLEEP) += pm.o
obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o
obj-$(CONFIG_GENERIC_IRQ_IPI) += ipi.o
obj-$(CONFIG_SMP) += affinity.o
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o

1.2 源文件

文件 行数 功能
irqdesc.c ~996 IRQ 描述符分配/释放、sparse irq、sysfs
manage.c ~2934 request/free_irq、线程化中断、enable/disable、亲和性
irqdomain.c ~2006 irq_domain 创建、hw→linux IRQ 映射、DeviceTree
chip.c ~1607 flow handler、mask/unmask、startup/shutdown
handle.c ~242 事件分发、线程唤醒、arch_irq 入口
msi.c ~1042 MSI/MSI-X 描述符与 domain 管理
timings.c ~958 中断时序统计(延迟分析)
generic-chip.c ~668 可配置 generic irq_chip 库
proc.c ~538 /proc/interrupts 实现
spurious.c ~478 虚假/丢失中断检测与恢复
matrix.c ~513 IRQ 矩阵分配器(x2APIC 等)
ipi.c ~345 跨 CPU IPI 中断管理
pm.c ~260 休眠/唤醒中断状态保存
irq_sim.c ~250 模拟中断(测试用)
cpuhotplug.c ~233 CPU 热插拔时 IRQ 迁移
migration.c ~119 pending affinity 迁移
resend.c ~184 软件重发 pending 中断
affinity.c ~128 多队列 IRQ 亲和性 mask 生成
autoprobe.c ~184 IRQ 自动探测
devres.c ~284 devres 托管 request_irq
dummychip.c ~64 空 irq_chip 占位
debugfs.c ~261 debugfs 调试接口
internals.h ~516 内部 API、状态位定义
settings.h ~188 irq_settings 宏

二、整体架构

2.1 分层模型

1
2
3
4
硬件中断线                内核 IRQ 框架                    驱动
───────── ───────────── ────
Device ──→ IRQ Chip ──→ irq_domain ──→ irq_desc ──→ irqaction.handler
(GIC/GPIO) (mask/eoi) (hw→virq) (flow handler) (driver ISR)

2.2 关键层次

层次 组件 职责
架构入口 handle_arch_irq / generic_handle_arch_irq 汇编 trap → C 入口,irq_enter/exit
Flow Handler handle_fasteoi_irq 中断流控制:mask/ack/unmask/eoi
Event Handler handle_irq_event 调用 irqaction 链
Driver Handler request_irq() 注册的函数 设备特定 ISR
IRQ Chip struct irq_chip 硬件操作:mask/unmask/eoi/set_type
IRQ Domain struct irq_domain 硬件 IRQ 号 → Linux virq 映射

2.3 RK3588 典型硬件栈

1
2
3
4
5
6
外设 (UART/Ethernet/PCIe)
→ GICv3 SPI/PPI/SGI (drivers/irqchip/irq-gic-v3.c)
→ GIC ITS (MSI, irq-gic-v3-its.c) — PCIe MSIs
→ irq_domain (gic_irq_domain)
→ kernel/irq/ 通用框架
→ 驱动 request_irq() handler

三、核心数据结构

3.1 struct irq_desc — 中断描述符

每个 Linux IRQ 号对应一个 irq_desc

1
2
3
4
5
6
7
8
9
10
11
12
13
struct irq_desc {
struct irq_common_data irq_common_data; // affinity、msi_desc 等
struct irq_data irq_data; // chip、hwirq、状态
unsigned int __percpu *kstat_irqs; // 每 CPU 中断计数
irq_flow_handler_t handle_irq; // flow handler 函数
struct irqaction *action; // handler 链表头
unsigned int depth; // disable 嵌套深度
raw_spinlock_t lock;
struct cpumask *affinity; // SMP 亲和性
struct mutex request_mutex; // request/free 序列化
const char *name;
// ...
};

3.2 struct irq_data — chip 层数据

1
2
3
4
5
6
7
8
9
struct irq_data {
u32 mask; // chip 内 bit mask
unsigned int irq; // Linux virq 号
unsigned long hwirq; // 硬件 IRQ 号
struct irq_chip *chip; // 关联的 irq_chip
struct irq_domain *domain; // 所属 domain
void *chip_data; // chip 私有数据
// state: disabled/masked/inprogress/...
};

3.3 struct irq_chip — 硬件中断控制器

1
2
3
4
5
6
7
8
9
10
11
12
struct irq_chip {
const char *name;
unsigned int irq_ack; // 是否需 ack
void (*irq_mask)(struct irq_data *);
void (*irq_unmask)(struct irq_data *);
void (*irq_eoi)(struct irq_data *); // End Of Interrupt
int (*irq_set_type)(struct irq_data *, unsigned int);
int (*irq_set_affinity)(struct irq_data *, const struct cpumask *, bool);
int (*irq_startup)(struct irq_data *);
void (*irq_shutdown)(struct irq_data *);
unsigned long flags; // IRQCHIP_* 标志
};

3.4 struct irqaction — 驱动 handler

1
2
3
4
5
6
7
8
9
10
11
12
13
struct irqaction {
irq_handler_t handler; // 硬中断 handler
irq_handler_t thread_fn; // 线程化 handler(可选)
struct irqaction *next; // 共享 IRQ 链表
irq_handler_t *secondary; // 辅助 handler
void *dev_id; // 设备标识(free_irq 用)
void *percpu_dev_id;
const char *name;
struct task_struct *thread; // IRQ 线程
unsigned int flags; // IRQF_* 标志
unsigned long thread_flags; // IRQTF_* 内部标志
// ...
};

3.5 struct irq_domain — 映射域

1
2
3
4
5
6
7
8
9
10
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
irq_hw_number_t hwirq_max; // 最大 hwirq
unsigned int revmap_size;
unsigned int *revmap; // hwirq → virq 反向映射
struct irq_domain *parent; // 层级 domain
// ...
};

四、中断处理流程

4.1 总体路径

1
2
3
4
5
6
7
8
9
10
11
12
13
CPU 收到 IRQ 异常
→ arch/arm64: el1h_64_irq_handler (entry.S)
→ generic_handle_arch_irq(regs) [handle.c]
→ irq_enter()
→ handle_arch_irq(regs) [GIC driver]
→ gic_handle_irq()
→ generic_handle_domain_irq(domain, hwirq)
→ irq_find_mapping() → virq
→ generic_handle_irq_desc(desc)
→ desc->handle_irq(desc) [flow handler]
→ handle_irq_event(desc)
→ action->handler(irq, dev_id)
→ irq_exit()

4.2 handle_irq_event(handle.c)

1
2
3
4
5
6
7
8
9
10
11
12
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock); // 释放锁调用 handler

ret = handle_irq_event_percpu(desc); // 遍历 action 链

raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}

4.3 handler 返回值

返回值 含义
IRQ_NONE 未处理(可能是共享 IRQ 或非本设备)
IRQ_HANDLED 已处理
IRQ_WAKE_THREAD 唤醒 thread_fn 线程继续处理

4.4 线程化中断唤醒

1
2
3
4
5
6
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
desc->threads_oneshot |= action->thread_mask;
atomic_inc(&desc->threads_active);
wake_up_process(action->thread); // 唤醒 IRQ 内核线程
}

五、Flow Handler 类型

Flow handler 定义中断 流控制语义(何时 mask/ack/unmask/eoi):

Handler 适用场景 行为
handle_simple_irq 无 chip 控制 直接 handle_irq_event
handle_level_irq 电平触发 mask → handler → unmask
handle_edge_irq 边沿触发 ack → handler → unmask(可能 pending)
handle_fasteoi_irq GIC 等现代控制器 handler → chip->irq_eoi()
handle_fasteoi_nmi NMI 线 NMI-safe 版 fasteoi
handle_edge_eoi_irq Cell 等 边沿 + EOI
handle_bad_irq 未配置/错误 计数 + ack_bad_irq

5.1 handle_fasteoi_irq(RK3588 GIC 最常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void handle_fasteoi_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);

if (!desc->action || irqd_irq_disabled(&desc->irq_data)) {
desc->istate |= IRQS_PENDING;
mask_irq(desc);
goto out;
}

kstat_incr_irqs_this_cpu(desc);
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc); // ONESHOT: 线程完成前保持 masked

handle_irq_event(desc); // 调用 driver handler

cond_unmask_eoi_irq(desc, chip); // chip->irq_eoi() + 条件 unmask
out:
raw_spin_unlock(&desc->lock);
}

5.2 handle_edge_irq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void handle_edge_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
desc->istate &= stale flags;
desc->istate |= IRQS_PENDING; // 边沿可能 pending
mask_ack_irq(desc); // mask + ack

if (desc->action && !irqd_irq_disabled())
handle_irq_event(desc);

// 若 pending 且 unmasked → 重新处理
cond_unmask_irq(desc);
raw_spin_unlock(&desc->lock);
}

六、IRQ 注册与管理(manage.c)

6.1 request_threaded_irq

1
2
3
4
5
6
7
8
9
int request_threaded_irq(unsigned int irq,
irq_handler_t handler, // 硬中断 handler(可为 NULL)
irq_handler_t thread_fn, // 线程 handler
unsigned long flags,
const char *name, void *dev_id)
{
// 分配 irqaction
// __setup_irq(irq, desc, action)
}

6.2 __setup_irq 核心步骤

1
2
3
4
5
6
7
8
9
10
__setup_irq(irq, desc, new)
→ try_module_get(desc->owner)
→ 检查 trigger type、nested thread
→ irq_setup_forced_threading() // CONFIG_IRQ_FORCED_THREADING
→ setup_irq_thread() // 创建 IRQ 内核线程
→ 链入 desc->action 链表(支持共享 IRQ)
→ irq_settings 配置 ONESHOT 等
→ irq_startup() / activate // 启动硬件中断
→ register_handler_proc() // /proc/irq/N/
→ wake_up_and_wait_for_irq_thread_ready()

6.3 IRQF 标志

标志 说明
IRQF_SHARED 多驱动共享同一 IRQ
IRQF_TRIGGER_RISING/FALLING/HIGH/LOW 触发类型
IRQF_ONESHOT 线程 handler 完成前保持 masked
IRQF_NO_THREAD 禁止强制线程化
IRQF_NO_SUSPEND 休眠时不 disable
IRQF_EARLY_RESUME 早恢复
IRQF_COND_SUSPEND 条件 suspend

6.4 enable/disable

1
2
3
void disable_irq(unsigned int irq)    // depth++,depth==1 时 mask
void enable_irq(unsigned int irq) // depth--,depth==0 时 unmask + resend
void synchronize_irq(unsigned int irq) // 等待 handler 完成

depth 支持嵌套 disable/enable,引用计数语义。

6.5 free_irq

1
2
3
4
const void *free_irq(unsigned int irq, void *dev_id)
→ __free_irq(desc, dev_id) // 从 action 链移除
→ 若无 action 剩余 → irq_shutdown()
kfree(action) + 停止 IRQ 线程

七、IRQ Domain 映射(irqdomain.c)

7.1 概念

IRQ Domain 解决硬件 IRQ 号与 Linux 虚拟 IRQ(virq)的映射:

1
2
3
hwirq (GIC INTID 32~1019)  ←→  virq (Linux IRQ 动态分配)
↑ irq_domain
↑ irq_find_mapping(domain, hwirq)

7.2 主要 API

API 功能
__irq_domain_add() 创建 irq_domain
irq_create_mapping() hwirq → 分配 virq 并关联
irq_find_mapping() hwirq → 查找已有 virq
irq_domain_create_simple() 简单线性映射 domain
irq_domain_add_legacy() 固定偏移 legacy 映射
irq_alloc_descs() 批量分配 virq
irq_domain_alloc_irqs() 层级 domain 分配

7.3 DeviceTree 集成

1
2
3
4
// 驱动 probe 中
domain = irq_domain_add_simple(node, max_irq, irq_base, &chip, chip_data);
virq = irq_create_of_mapping(irq_spec); // 解析 interrupt-parent + interrupts
request_irq(virq, handler, flags, name, dev);

7.4 层级 Domain

1
2
3
Root Domain (GIC)
└── PCI MSI Domain (ITS)
└── Device MSI

CONFIG_IRQ_DOMAIN_HIERARCHY 支持多级 domain 嵌套。


八、IRQ Chip 操作(chip.c)

8.1 生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
irq_startup(desc)          // 首次 enable
→ chip->irq_startup() 或 irq_enable()
→ unmask_irq()

irq_shutdown(desc) // 最后 disable
→ mask_irq()
→ chip->irq_shutdown()

irq_enable(desc) // depth 归零时
→ chip->irq_enable/unmask

irq_disable(desc) // depth 递增到 1 时
→ chip->irq_disable/mask

8.2 mask/unmask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void mask_irq(struct irq_desc *desc)
{
if (!irqd_irq_masked(&desc->irq_data)) {
desc->irq_data.chip->irq_mask(&desc->irq_data);
irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
}
}

void unmask_irq(struct irq_desc *desc)
{
if (irqd_irq_masked(&desc->irq_data)) {
desc->irq_data.chip->irq_unmask(&desc->irq_data);
irqd_clear(&desc->irq_data, IRQD_IRQ_MASKED);
}
}

8.3 SPARSE IRQ 描述符管理(irqdesc.c)

1
2
3
4
5
6
7
#ifdef CONFIG_SPARSE_IRQ
// 动态分配 irq_desc,radix tree 索引
struct irq_desc *irq_alloc_desc(irq, node, affinity, owner, ...)
// allocated_irqs bitmap 跟踪
#else
struct irq_desc irq_desc[NR_IRQS]; // 静态数组
#endif

ARM64/RK3588 启用 CONFIG_SPARSE_IRQ,IRQ 号动态分配,节省内存。


九、MSI 中断(msi.c)

9.1 概述

MSI(Message Signaled Interrupts) 让 PCIe 等设备通过写内存地址触发中断,替代传统 INTx 线。

9.2 核心结构

1
2
3
4
5
6
7
8
9
10
11
12
13
struct msi_desc {
struct device *dev;
unsigned int msi_index;
unsigned int nvec_used;
struct irq_affinity_desc *affinity;
// PCI 特定字段
};

struct msi_device_data {
struct xa_array __store; // msi_desc 存储
struct mutex mutex;
struct irq_domain *domain;
};

9.3 RK3588 PCIe MSI 路径

1
2
3
4
5
PCIe Device
→ ITS (Interrupt Translation Service, GICv3)
→ irq-gic-v3-its.c
→ msi_domain (kernel/irq/msi.c)
→ virq 分配 + request_irq()

十、Generic IRQ Chip(generic-chip.c)

MMIO 中断控制器(GPIO 中断、SoC 内部 INTC)提供可配置 irq_chip 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct irq_chip_generic {
raw_spinlock_t lock;
void __iomem *reg_base;
struct irq_chip_type chip_types[]; // 多种 trigger 类型
u32 mask_cache;
u32 wake_enabled;
};

struct irq_chip_type {
struct irq_chip chip;
struct irq_chip_regs regs; // mask/enable/ack/disable 寄存器偏移
u32 type; // IRQ_TYPE_* 组合
u32 handler; // handle_level/edge_irq
u32 *mask_cache;
};

提供标准回调:irq_gc_mask_set_bitirq_gc_unmask_enable_regirq_gc_eoi 等,减少驱动重复代码。


十一、亲和性与迁移

11.1 IRQ Affinity

1
2
3
4
int irq_set_affinity(unsigned int irq, const struct cpumask *cpumask)
irq_do_set_affinity(data, mask, force)
→ chip->irq_set_affinity() // 硬件设置
→ 或 pending 到 pending_mask // 中断上下文中延迟迁移

11.2 Pending Migration(migration.c)

中断上下文中无法安全修改 affinity 时,标记 IRQD_SETAFFINITY_PENDING

1
2
3
4
5
6
7
void irq_move_masked_irq(struct irq_data *idata)
{
if (!irqd_is_setaffinity_pending(data))
return;
irqd_clr_move_pending(data);
chip->irq_set_affinity(data, desc->pending_mask, false);
}

在下次 interrupt 的 mask 阶段执行实际迁移。

11.3 CPU Hotplug(cpuhotplug.c)

CPU offline 前:

  • 将 IRQ affinity 从 offline CPU 迁移到其他 CPU
  • 清理 pending_mask

11.4 多队列 Affinity(affinity.c)

1
2
struct irq_affinity_desc *
irq_create_affinity_masks(unsigned int nvecs, struct irq_affinity *affd)

为 multiqueue 设备(网卡多队列等)生成 per-vector 亲和性 mask,在可用 CPU 间均匀分布。


十二、其他模块

12.1 虚假中断检测(spurious.c)

  • 跟踪 irqs_unhandled 计数
  • 超过阈值(100000/300s)自动 disable 该 IRQ
  • irqfixup 启动参数启用 poll 恢复机制
  • 防止中断风暴拖垮系统

12.2 IPI(ipi.c)

1
2
3
int irq_reserve_ipi(struct irq_domain *domain, const struct cpumask *dest)
int ipi_send_single(unsigned int virq, unsigned int cpu)
int ipi_send_mask(unsigned int virq, const struct cpumask *dest)

ARM64 使用 GIC SGI(Software Generated Interrupt)实现核间 IPI。

12.3 /proc/interrupts(proc.c)

1
2
3
4
5
         CPU0       CPU4       CPU5
27: 0 0 0 GICv3 27 Level arch_timer
32: 123456 0 0 GICv3 32 Level rk_uart
IPI0: 567890 456789 345678 Rescheduling interrupts
IPI1: 12345 12340 12350 Function call interrupts

12.4 电源管理(pm.c)

  • suspend:保存 IRQ 状态,disable 非 wake IRQ
  • resume:restore 状态,resend pending
  • IRQF_NO_SUSPEND / IRQF_FORCE_RESUME 控制

12.5 devres(devres.c)

1
devm_request_irq(dev, irq, handler, flags, name, dev_id)

设备移除时自动 free_irq,防止资源泄漏。

12.6 IRQ Timings(timings.c)

记录中断到达时间间隔,用于 中断延迟分析 和 debug。

12.7 Matrix Allocator(matrix.c)

用于 x2APIC 等场景的 IRQ 向量矩阵分配,避免冲突。


十三、RK3588/ARM64 平台说明

13.1 Kconfig 支持

1
2
3
4
5
6
7
8
9
10
# arch/arm64/Kconfig(RK3588 启用项)
select GENERIC_IRQ_IPI # IPI 支持
select GENERIC_IRQ_PROBE
select GENERIC_IRQ_SHOW # /proc/interrupts
select GENERIC_IRQ_SHOW_LEVEL
select HARDIRQS_SW_RESEND
select IRQ_DOMAIN # irq_domain 框架
select IRQ_FORCED_THREADING # 可选强制线程化
select SPARSE_IRQ # 动态 IRQ 号
select GENERIC_IRQ_MIGRATION # CPU hotplug 迁移

13.2 GICv3 中断控制器

组件 文件 说明
GICv3 Distributor/Redistributor drivers/irqchip/irq-gic-v3.c 主中断控制器
GICv3 ITS drivers/irqchip/irq-gic-v3-its.c PCIe MSI 翻译
GIC 公共代码 drivers/irqchip/irq-gic-common.c 共享逻辑
GICv3 PM drivers/irqchip/irq-gic-pm.c 休眠支持

RK3588 使用 ARM GICv3

  • SGI 0-15:软件生成中断(IPI)
  • PPI 16-31:每核私有(arch_timer、性能监控)
  • SPI 32+:共享外设中断(UART、EMMC、Ethernet 等)

13.3 中断入口

1
2
3
4
5
6
7
8
// arch/arm64/kernel/irq.c
asmlinkage void __exception_irq_entry handle_arch_irq(struct pt_regs *regs)
{
generic_handle_arch_irq(regs);
}

// init 阶段
set_handle_irq(handle_arch_irq);

13.4 big.LITTLE 亲和性

  • GICv3 affinity routing 可将 SPI 路由到特定 CPU 或 CPU 组
  • RK3588 8 核:中断默认 spread 到所有 CPU,驱动可通过 irq_set_affinity_hint() 优化
  • 高 IOPS 设备(NVMe、千兆网卡)通常绑定 A76 大核

13.5 强制线程化

CONFIG_IRQ_FORCED_THREADING + 启动参数 threadirqs

  • 所有非 per-CPU、非 ONESHOT 的 hard handler 自动线程化
  • 减少 hardirq 关中断时间,改善 RT 延迟
  • 调试用途,生产环境一般不用

十四、完整中断处理时序

RK3588 UART 接收中断 为例:

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
[硬件]
UART RX FIFO 达到阈值 → 触发 SPI 中断线 → GICv3

[ARM64 入口]
el1h_64_irq_handler (entry.S)
→ 保存 pt_regs
→ generic_handle_arch_irq(regs)
→ irq_enter() // RCU/lockdep/tracing

[ GIC 驱动 ]
gic_handle_irq()
→ 读取 IAR 寄存器 → hwirq = 125
→ generic_handle_domain_irq(gic_domain, 125)
→ virq = irq_find_mapping(domain, 125) // 如 virq=32

[ Flow Handler ]
generic_handle_irq_desc(desc)
→ handle_fasteoi_irq(desc) // GIC 使用 fasteoi
→ kstat_incr_irqs_this_cpu()
→ handle_irq_event(desc)
→ desc->lock 释放
→ serial8250_interrupt(32, dev_id)
→ 读取 FIFO → push 到 tty flip buffer
→ return IRQ_HANDLED
→ desc->lock 重新获取
→ gic_eoi_irq() // 写 EOIR 寄存器
→ irq_exit() // 可能触发 preempt/schedule

[ 用户态 ]
tty flip buffer → line discipline → 用户 read() 返回数据

十五、总结

kernel/irq 是 Linux 中断管理的核心框架:

  1. irq_desc — 每个 IRQ 的中央描述符,关联 chip、handler、affinity
  2. Flow Handler — 抽象 edge/level/fasteoi 等中断流控制语义
  3. irq_chip — 硬件控制器操作抽象(mask/unmask/eoi/set_affinity)
  4. irq_domain — hwirq ↔ virq 映射,支持 DeviceTree 和层级 domain
  5. request_irq — 驱动注册 handler,支持共享、线程化、ONESHOT
  6. MSI — PCIe 消息中断管理,与 GIC ITS 配合
  7. Affinity/Migration — SMP 中断路由、CPU hotplug 迁移
  8. Spurious 检测 — 自动 disable 异常中断源

RK3588 通过 GICv3 + Generic IRQ 框架管理全部硬件中断,是 UART、网络、存储、PCIe 等驱动 request_irq() 的底层支撑。


附录:源文件清单

文件 行数 分类
manage.c ~2934 IRQ 注册/enable/affinity
irqdomain.c ~2006 IRQ Domain 映射
chip.c ~1607 Flow handler / chip 操作
irqdesc.c ~996 描述符管理
timings.c ~958 中断时序
msi.c ~1042 MSI 中断
generic-chip.c ~668 Generic irq_chip
proc.c ~538 /proc/interrupts
spurious.c ~478 虚假中断
matrix.c ~513 矩阵分配器
handle.c ~242 事件分发
ipi.c ~345 IPI
其他 ~1500 pm/migration/affinity/debug 等

RK3588 相关驱动/架构文件

文件 功能
drivers/irqchip/irq-gic-v3.c GICv3 主驱动
drivers/irqchip/irq-gic-v3-its.c GIC ITS (MSI)
arch/arm64/kernel/irq.c ARM64 IRQ 入口
arch/arm64/kernel/entry-common.c 异常入口公共逻辑
include/linux/irqdesc.h irq_desc 定义
include/linux/irqdomain.h irq_domain API

kernel/kcsan 内核并发消毒器(KCSAN)机制与原理详解

kernel/kcsan 内核并发消毒器(KCSAN)机制与原理详解

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

该目录实现 KCSAN(Kernel Concurrency Sanitizer,内核并发消毒器) — Linux 内核的动态 数据竞争(Data Race) 检测框架。通过编译器插桩(基于 ThreadSanitizer 接口)在运行时以 watchpoint 采样 方式检测未同步的并发内存访问,并支持 ASSERT_EXCLUSIVE_* 等约束断言。

定位:KCSAN 是 调试/测试工具,默认不启用;与 KASAN 互斥,不可用于生产内核。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
KCSAN_SANITIZE := n          # KCSAN 自身不被插桩
KCOV_INSTRUMENT := n
UBSAN_SANITIZE := n

CFLAGS_core.o := -fno-conserve-stack -fno-stack-protector
CFLAGS_REMOVE_core.o = $(CC_FLAGS_FTRACE) # 禁用 ftrace 插桩

obj-y := core.o debugfs.o report.o
obj-$(CONFIG_KCSAN_SELFTEST) += selftest.o
obj-$(CONFIG_KCSAN_KUNIT_TEST) += kcsan_test.o
配置项 说明
CONFIG_KCSAN 启用 KCSAN(依赖 HAVE_ARCH_KCSAN + HAVE_KCSAN_COMPILER + DEBUG_KERNEL,且 !KASAN
CONFIG_KCSAN_NUM_WATCHPOINTS watchpoint 槽数量(默认 64)
CONFIG_KCSAN_EARLY_ENABLE 启动早期即全局启用
CONFIG_KCSAN_STRICT 严格模式(对齐 LKMM 规则)
CONFIG_KCSAN_WEAK_MEMORY 弱内存模型/缺失 barrier 检测
CONFIG_KCSAN_SELFTEST 启动自检
CONFIG_KCSAN_VERBOSE 报告含锁/IRQ 跟踪信息

1.2 源文件

文件 行数 功能
core.c ~1371 核心运行时:watchpoint、check_access、TSAN 钩子
report.c ~715 竞争报告生成、速率限制、栈回溯
debugfs.c ~275 debugfs 统计/开关/过滤/微基准
encoding.h ~102 watchpoint 地址/大小/读写编码
kcsan.h ~142 内部 API、计数器定义
permissive.h ~94 宽松模式忽略规则
selftest.c ~272 启动自检
kcsan_test.c ~1618 KUnit 集成测试

相关头文件

文件 功能
include/linux/kcsan.h 公共 API、struct kcsan_ctx
include/linux/kcsan-checks.h ASSERT_EXCLUSIVE_*
lib/Kconfig.kcsan 全部 Kconfig 选项

二、整体架构

2.1 设计思想

KCSAN 采用 采样 watchpoint 而非追踪每一访问(对比用户态 TSAN 的 shadow memory):

1
2
3
4
5
每次内存访问(编译器插桩)
→ check_access() 快速路径
├── 查找是否有冲突 watchpoint → 报告竞争
└── 采样:概率性设置 watchpoint + 延迟
→ 其他线程访问同一地址 → 检测到竞争

优势:内核开销可控,可在完整内核上运行;代价:可能漏检(采样未覆盖的访问)。

2.2 数据流

1
2
3
4
5
6
7
8
9
10
编译阶段                          运行阶段
───────── ─────────
-fsanitize=thread 每次 load/store
→ __tsan_readN/writeN() → check_access()
→ find_watchpoint (fast)
→ should_watch → setup_watchpoint (slow)
→ insert_watchpoint
→ udelay (扩大竞争窗口)
→ 值变化检测
→ consume_watchpoint / report

2.3 与相关工具对比

工具 机制 检测目标
KCSAN TSAN 插桩 + watchpoint 采样 数据竞争、缺失 barrier
KASAN 影子内存 越界、use-after-free
KCSAN 与 KASAN 互斥depends on !KASAN
KCOV 覆盖率 代码路径(模糊测试)
LOCKDEP 锁依赖图 死锁、锁顺序

三、Watchpoint 采样算法

3.1 采样控制

1
2
3
4
5
6
7
8
9
10
11
12
13
// 每 CPU 跳过计数器:大部分访问直接返回
static DEFINE_PER_CPU(long, kcsan_skip);

static __always_inline bool should_watch(struct kcsan_ctx *ctx, ...)
{
if (is_atomic(ctx, ptr, size, type)) // 原子区域不采样
return false;

if (this_cpu_dec_return(kcsan_skip) >= 0) // 跳过 N 次访问
return false;

return true; // 本次访问设置 watchpoint
}
参数 默认值 说明
KCSAN_SKIP_WATCH 4000 每 CPU 跳过多少次访问后才采样一次
KCSAN_UDELAY_TASK 80 μs 任务上下文 watchpoint 延迟
KCSAN_UDELAY_INTERRUPT 20 μs 中断上下文 watchpoint 延迟
KCSAN_DELAY_RANDOMIZE y 随机化延迟
KCSAN_SKIP_WATCH_RANDOMIZE y 随机化 skip 计数

3.2 check_access 双路径

1
2
3
4
5
6
7
8
9
10
11
12
static __always_inline void check_access(const volatile void *ptr,
size_t size, int type, unsigned long ip)
{
// 快速路径:检查是否命中已有 watchpoint
watchpoint = find_watchpoint(addr, size, expect_write, &encoded);
if (watchpoint != NULL)
kcsan_found_watchpoint(...); // 慢路径:消费 watchpoint,记录竞争方
else if (should_watch(ctx, ptr, size, type))
kcsan_setup_watchpoint(...); // 慢路径:插入 watchpoint + 延迟
else
// 检查 reorder_access(弱内存)和 scoped_access
}

3.3 setup_watchpoint 流程

1
2
3
4
5
6
7
8
9
10
11
kcsan_setup_watchpoint(ptr, size, type, ip)
1. check_encodable() — 地址/大小是否可编码
2. insert_watchpoint() — CAS 插入 atomic_long 槽
3. old = read_instrumented_memory(ptr) — 记录当前值
4. delay_access(type) — udelay 扩大竞争窗口
5. new = read_instrumented_memory(ptr) — 重读检查值变化
6. consume_watchpoint() — 是否被其他线程命中?
→ 是:kcsan_report_known_origin() — 双方栈回溯报告
→ 否但值变化:kcsan_report_unknown_origin() — 未知来源报告
7. remove_watchpoint()
8. set_reorder_access() — 弱内存模型重排序检测

3.4 相邻槽检查

1
2
#define KCSAN_CHECK_ADJACENT 1
#define NUM_SLOTS (1 + 2*KCSAN_CHECK_ADJACENT) // = 3

跨页边界的大访问可能检查相邻 watchpoint 槽,减少漏检;极大访问仍可能错过。


四、Watchpoint 编码(encoding.h)

每个 watchpoint 编码为单个 atomic_long,无锁 CAS 更新:

1
2
3
4
5
6
7
// 编码格式(64-bit):
// [63] = is_write
// [62:ADDR_BITS] = size
// [ADDR_BITS-1:0] = addr 低位(masked)

encode_watchpoint(addr, size, is_write)
watchpoint_slot(addr) = (addr / PAGE_SIZE) % CONFIG_KCSAN_NUM_WATCHPOINTS
常量 说明
INVALID_WATCHPOINT 0 空槽
CONSUMED_WATCHPOINT 1 已被竞争方消费
MAX_ENCODABLE_SIZE PAGE_SIZE * 3 最大可编码访问大小
SLOT_RANGE PAGE_SIZE 槽映射粒度

地址按 (addr / PAGE_SIZE) % NUM_WATCHPOINTS 哈希到槽,不同地址可能碰撞(report.c 会过滤编码假阳性)。


五、核心运行时(core.c)

5.1 全局状态

1
2
3
4
5
bool kcsan_enabled;                                    // 全局开关
static atomic_long_t watchpoints[NUM_WATCHPOINTS + 2]; // watchpoint 数组
atomic_long_t kcsan_counters[KCSAN_COUNTER_COUNT]; // 统计计数器
static DEFINE_PER_CPU(struct kcsan_ctx, kcsan_cpu_ctx); // 中断上下文
// task 使用 task_struct.kcsan_ctx

5.2 统计计数器

计数器 说明
used_watchpoints 当前使用中的 watchpoint
setup_watchpoints 累计设置次数
data_races 检测到的数据竞争
assert_failures ASSERT 失败
no_capacity watchpoint 槽耗尽
report_races 竞争报告竞争(一方已消费)
races_unknown_origin 未知来源竞争
unencodable_accesses 无法编码的访问
encoding_false_positives 编码碰撞假阳性

5.3 初始化

1
2
3
4
5
6
7
8
9
10
void __init kcsan_init(void)
{
for_each_possible_cpu(cpu)
per_cpu(kcsan_rand_state, cpu) = get_cycles();

if (kcsan_early_enable)
WRITE_ONCE(kcsan_enabled, true);

// 非 strict 模式警告
}

5.4 自身保护

KCSAN 核心代码 禁止被插桩KCSAN_SANITIZE := n),且禁用 ftrace/KCOV,避免递归和性能灾难。


六、编译器插桩接口

6.1 TSAN 钩子

KCSAN 复用编译器为 ThreadSanitizer 生成的插桩回调:

1
2
3
4
5
6
7
8
9
10
// 编译选项(内核构建)
CFLAGS_KCSAN := -fsanitize=thread -mllvm -tsan-distinguish-volatile=1 // Clang
// 或 GCC: --param tsan-distinguish-volatile=1

// 编译器在每个 load/store 插入:
__tsan_read1/2/4/8/16(void *ptr)
__tsan_write1/2/4/8/16(void *ptr)
__tsan_read_writeN(void *ptr) // 复合操作 ++, += 等
__tsan_volatile_read/writeN() // volatile 访问
__tsan_read/write_range() // 范围访问

6.2 实现示例

1
2
3
4
5
6
7
8
9
void __tsan_write4(void *ptr)
{
check_access(ptr, 4, KCSAN_ACCESS_WRITE, _RET_IP_);
}

void __tsan_read4(void *ptr)
{
check_access(ptr, 4, 0, _RET_IP_); // 0 = read
}

6.3 不插桩的情况

  • 内联汇编(asm)访问
  • 标记为 __no_kcsan 的函数
  • KCSAN 自身代码(Makefile 排除)
  • 原子区域内的访问(READ_ONCE/WRITE_ONCE 在 atomic 区域等)

七、上下文与原子区域

7.1 struct kcsan_ctx

1
2
3
4
5
6
7
8
9
10
struct kcsan_ctx {
int disable_count; // kcsan_disable_current() 嵌套计数
int disable_scoped;
int atomic_next; // 后续 N 个操作视为 atomic
int atomic_nest_count; // nestable atomic 区域深度
bool in_flat_atomic; // flat atomic 区域(如 seqlock 读侧)
unsigned long access_mask; // 位级 ASSERT_EXCLUSIVE_BITS
struct list_head scoped_accesses;
struct kcsan_scoped_access reorder_access; // 弱内存重排序
};
  • 任务上下文current->kcsan_ctx
  • 中断上下文per_cpu(kcsan_cpu_ctx)

7.2 原子区域 API

API 功能
kcsan_disable_current() / enable_current() 临时禁用当前上下文 KCSAN
kcsan_nestable_atomic_begin/end() 嵌套原子区域(如 spin_lock 内)
kcsan_flat_atomic_begin/end() 非嵌套 flat 原子区域(如 seqlock 读)
kcsan_atomic_next(n) 标记后续 n 个操作为 atomic
kcsan_set_access_mask(mask) 位级访问掩码

7.3 is_atomic 判定

以下访问 不设置 watchpoint(视为已同步):

  1. KCSAN_ACCESS_ATOMIC 标记的访问
  2. atomic_nest_count > 0(nestable atomic 区域内)
  3. in_flat_atomic == true(flat atomic 区域内)
  4. atomic_next > 0(显式标记的后续 atomic 操作)
  5. CONFIG_KCSAN_ASSUME_PLAIN_WRITES_ATOMIC:对齐 ≤ word 的 plain write

八、弱内存模型检测

8.1 重排序访问(CONFIG_KCSAN_WEAK_MEMORY)

模拟编译器/CPU 对内存访问的重排序,检测缺失 memory barrier:

1
2
3
4
5
6
普通 write 访问
→ set_reorder_access() — 记录为"可能被重排序的访问"
→ 后续 barrier (__kcsan_mb/wmb/rmb/release) 清除 reorder
→ 若无 barrier,在函数退出或 scoped check 时
以 KCSAN_ACCESS_SCOPED 类型重新 check_access
→ 检测与其他线程的冲突

8.2 Barrier 钩子

1
2
3
4
void __kcsan_mb(void)      { reorder_access->size = 0; }  // 全屏障
void __kcsan_wmb(void) { 写屏障 }
void __kcsan_rmb(void) { 读屏障 }
void __kcsan_release(void) { release 语义 }

8.3 函数边界

1
2
__tsan_func_entry()  — 函数入口,初始化 reorder 作用域
__tsan_func_exit() — 函数退出,invalidate reorder_access

九、报告生成(report.c)

9.1 报告类型

类型 触发条件
known origin watchpoint 被消费,双方栈回溯完整
unknown origin 延迟期间值变化但无 watchpoint 命中(如设备 DMA)
assert: race ASSERT_EXCLUSIVE_* 检测到冲突

9.2 报告格式示例

1
2
3
4
5
6
7
8
9
10
11
12
BUG: KCSAN: data-race in foo_read / bar_write

write to 0xffffffffc0123456 of 4 bytes by task 1234 on cpu 2:
foo_write+0x42/0x100 [module]
...

read to 0xffffffffc0123456 of 4 bytes by task 5678 on cpu 5:
bar_read+0x18/0x80 [module]
...

Reported by Kernel Concurrency Sanitizer on:
CPU: 2 PID: 1234 Comm: test Tainted: G ...

9.3 速率限制

1
2
// 同一对栈帧在 KCSAN_REPORT_ONCE_IN_MS (默认 3000ms) 内只报告一次
static bool rate_limit_report(unsigned long frame1, unsigned long frame2)

9.4 双线程协调

1
2
3
4
5
竞争方 A(命中 watchpoint):
try_consume_watchpoint() → kcsan_report_set_info() — 写入 other_info

竞争方 B(watchpoint 设置者):
consume_watchpoint 失败 → kcsan_report_known_origin() — 读取 other_info 打印完整报告

使用 report_lock 串行化报告,other_infos[] 数组大小与 watchpoint 数一致。


十、Debugfs 接口(debugfs.c)

路径:/sys/kernel/debug/kcsan/

文件 功能
enabled 读写:全局启用/禁用 KCSAN
stats 各计数器统计
report_filterlist 函数黑名单/白名单(按符号地址过滤报告)
microbench 写入迭代次数 → 微基准测试 fast-path

10.1 模块参数

1
2
3
4
5
6
kcsan.early_enable=1
kcsan.udelay_task=80
kcsan.udelay_interrupt=20
kcsan.skip_watch=4000
kcsan.interrupt_watcher=0
kcsan.weak_memory=1

十一、显式检查 API

定义于 include/linux/kcsan-checks.h

11.1 数据竞争检查

1
2
3
4
5
6
// 显式检查一次访问(不依赖编译器插桩)
kcsan_check_read(ptr, size);
kcsan_check_write(ptr, size);

// 原子访问检查
kcsan_check_atomic_read/write/read_write(ptr, size);

11.2 排他性断言

1
2
3
4
ASSERT_EXCLUSIVE_WRITER(var);          // 断言无并发 write
ASSERT_EXCLUSIVE_ACCESS(var); // 断言无并发 read/write
ASSERT_EXCLUSIVE_WRITER_SCOPED(var); // 作用域内断言
ASSERT_EXCLUSIVE_BITS(var, mask); // 位级排他断言

失败时报告 assert: race,计入 assert_failures 计数器。

11.3 Scoped Access

1
2
3
4
struct kcsan_scoped_access sa;
kcsan_begin_scoped_access(ptr, size, type, &sa);
// ... critical section ...
kcsan_end_scoped_access(&sa);

十二、RK3588/ARM64 平台说明

12.1 架构支持

1
2
# arch/arm64/Kconfig
select HAVE_ARCH_KCSAN if EXPERT
  • 需在 EXPERT 模式下才可选 KCSAN
  • 编译器需支持 -fsanitize=thread(GCC ≥7 或 Clang ≥9)
  • 与 KASAN 互斥,调试时二选一

12.2 启用方法

1
2
3
4
5
6
make menuconfig:
Kernel hacking → KCSAN: dynamic data race detector → Y
(需要 DEBUG_KERNEL=y, 不能同时启用 KASAN)

# 内核命令行(可选)
kcsan.early_enable=1

12.3 ARM64 注意事项

方面 说明
原子操作 ARM64 LL/SC 保证对齐 native atomic;plain write 在 strict 模式下仍可能被报告
big.LITTLE watchpoint 全局共享,与运行在 A76/A55 无关
性能 采样模式开销远低于用户态 TSAN;仍显著增加内核体积和运行时间
outline atomics Makefile 使用 -mno-outline-atomics 避免 core.c 中额外原子调用
生产环境 不应启用 — 仅 CI/开发内核使用

12.4 宽松模式(非 strict 默认)

RK3588 开发内核若未设 CONFIG_KCSAN_STRICT=y,默认启用多项宽松规则:

  • KCSAN_REPORT_VALUE_CHANGE_ONLY — 仅报告观察到值变化的竞争
  • KCSAN_ASSUME_PLAIN_WRITES_ATOMIC — 假设对齐 word write 原子
  • KCSAN_IGNORE_ATOMICS — 不插桩 marked atomic
  • KCSAN_PERMISSIVE — 忽略单 bit 变化等常见模式

启动时会打印:non-strict mode configured - use CONFIG_KCSAN_STRICT=y to see all data races


十三、完整检测时序

两个线程无锁并发写同一变量 为例:

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
[编译期]
int shared; // 未用 lock/atomic 保护
shared = 1; → 编译器插入 __tsan_write4(&shared)

[Thread A — CPU 2]
__tsan_write4(&shared)
→ check_access(&shared, 4, WRITE, ip_A)
→ find_watchpoint: 无
→ should_watch: true (skip 计数归零)
→ kcsan_setup_watchpoint()
→ insert_watchpoint(slot=shared的hash)
→ old = 1
→ udelay(80μs) ← 竞争窗口
→ new = 1 (尚未被 B 修改)
→ consume_watchpoint: 未被命中
→ remove_watchpoint

[Thread B — CPU 5](在 A 的 udelay 期间)
__tsan_write4(&shared)
→ check_access(&shared, 4, WRITE, ip_B)
→ find_watchpoint: 命中 A 的 watchpoint!
→ kcsan_found_watchpoint()
→ try_consume_watchpoint() = true
→ kcsan_report_set_info() ← B 记录栈信息

[Thread A — udelay 结束]
→ consume_watchpoint() = false (已被 B 消费)
→ kcsan_report_known_origin()
→ 打印完整报告:A(write) vs B(write) + 双方栈回溯
→ data_races counter++

十四、总结

kernel/kcsan 是 Linux 内核 动态数据竞争检测 的完整实现:

  1. 采样 watchpoint — 非 shadow memory,开销可控,适合完整内核
  2. TSAN 插桩 — 复用编译器 -fsanitize=thread 在每个 load/store 插入回调
  3. 无锁 CAS watchpoint — 每个槽一个 atomic_long,编码地址/大小/读写
  4. 延迟放大 — udelay 扩大竞争观测窗口
  5. 值变化检测 — 区分真实竞争与 false sharing
  6. 原子区域 — nestable/flat atomic 标记已同步访问
  7. 弱内存模型 — reorder_access + barrier 钩子检测缺失 memory barrier
  8. ASSERT APIASSERT_EXCLUSIVE_* 约束检查
  9. Debugfs — 运行时开关、统计、过滤

RK3588 在 EXPERT 配置下可启用 KCSAN 用于驱动/内核并发 bug 调试,但应与 KASAN 分开构建,且不用于产品内核。


附录:源文件清单

文件 行数 分类
core.c ~1371 核心运行时
report.c ~715 报告生成
debugfs.c ~275 debugfs 接口
kcsan_test.c ~1618 KUnit 测试
selftest.c ~272 启动自检
encoding.h ~102 watchpoint 编码
kcsan.h ~142 内部头文件
permissive.h ~94 宽松规则

配置与文档

文件 功能
lib/Kconfig.kcsan 全部 Kconfig 选项
include/linux/kcsan-checks.h 公共检查宏
Documentation/dev-tools/kcsan.rst 官方文档

kernel/livepatch 内核热补丁(Livepatch)机制与原理详解

kernel/livepatch 内核热补丁(Livepatch)机制与原理详解

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

该目录实现 Kernel Live Patching(内核热补丁) 核心框架。在不重启系统、不卸载内核模块的前提下,通过 ftrace 动态跳转 将已运行内核中被 patch 的函数调用重定向到补丁模块中的新实现,并配合 一致性模型(Consistency Model) 保证所有任务安全迁移到新/旧代码。

定位:生产环境安全修复内核 bug 的基础设施;补丁以 livepatch 模块 形式加载,由管理员通过 sysfs 或模块 init 启用。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
obj-$(CONFIG_LIVEPATCH) += livepatch.o

livepatch-objs := core.o patch.o shadow.o state.o transition.o
配置项 说明
CONFIG_LIVEPATCH 启用内核热补丁核心
CONFIG_HAVE_LIVEPATCH 架构声明支持 livepatch(ARM64 未 select
CONFIG_DYNAMIC_FTRACE_WITH_REGSCONFIG_DYNAMIC_FTRACE_WITH_ARGS ftrace 需支持寄存器/参数保存
CONFIG_KALLSYMS_ALL 完整符号表(含未导出符号)
CONFIG_HAVE_RELIABLE_STACKTRACE 可靠栈回溯(一致性模型加速路径)
!CONFIG_TRIM_UNUSED_KSYMS 不可裁剪未使用 ksym

Kconfig 依赖链:

1
2
3
4
5
6
7
LIVEPATCH
├── DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS
├── MODULES
├── SYSFS
├── KALLSYMS_ALL
├── HAVE_LIVEPATCH ← 架构能力
└── !TRIM_UNUSED_KSYMS

1.2 源文件

文件 行数 功能
core.c 1291 补丁生命周期、符号查找、KLP 重定位、sysfs、模块钩子
transition.c 666 一致性模型:任务 patch_state、栈检查、过渡完成
patch.c 289 ftrace 注册/注销、klp_ftrace_handler 跳转
shadow.c 299 Shadow 变量:RCU 哈希表 <obj, id> → 数据
state.c 119 klp_state 版本兼容、累积补丁检查
core.h 59 内部 API、回调封装、patch 遍历宏
patch.h 35 patch/unpatch 对象声明
transition.h 16 过渡函数声明
state.h 9 state 兼容检查声明

相关头文件

文件 功能
include/linux/livepatch.h 公共 API、klp_patch/klp_object/klp_func 定义
kernel/entry/common.c 返回用户态时 klp_update_patch_state()
kernel/sched/idle.c idle 循环中 klp_update_patch_state()
Documentation/livepatch/ 官方文档与示例

二、整体架构

2.1 设计思想

Livepatch 不修改内核 text 段,而是在函数入口通过 ftrace mcount 钩子 拦截调用,将指令指针重定向到补丁模块中的 new_func。多个补丁叠加时,同一函数维护 func_stack 栈,栈顶为当前生效补丁。

1
2
3
4
5
6
7
补丁模块加载
→ klp_enable_patch()
→ 符号解析 + KLP 重定位
→ klp_patch_object() → register_ftrace_function()
→ klp_init_transition() + klp_start_transition()
→ 所有任务迁移 patch_state
→ klp_complete_transition()

2.2 模块关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────┐
│ Livepatch 补丁模块 │
│ struct klp_patch { objs[], states[], replace } │
│ └── klp_object (vmlinux 或 module) │
│ └── klp_func { old_name → new_func } │
└──────────────────────────┬──────────────────────────────────┘
│ klp_enable_patch()

┌─────────────────────────────────────────────────────────────┐
│ kernel/livepatch/ 核心 │
│ core.c ── 生命周期、符号、sysfs、模块钩子 │
│ patch.c ── ftrace 注册 + klp_ftrace_handler 跳转 │
│ transition.c ── 一致性模型、任务迁移 │
│ shadow.c ── 补丁私有数据(Shadow 变量) │
│ state.c ── 累积补丁状态版本兼容 │
└──────────────────────────┬──────────────────────────────────┘
│ ftrace

┌─────────────────────────────────────────────────────────────┐
│ arch/arm64/kernel/ftrace.c │
│ DYNAMIC_FTRACE_WITH_REGS → ftrace_regs + IP 修改 │
└─────────────────────────────────────────────────────────────┘

2.3 与相关子系统对比

机制 方式 是否需要重启
Livepatch ftrace 跳转 + 一致性模型
kpatch / kgraft 早期独立实现,已并入 livepatch
模块 reload 卸载/重载 .ko 通常需停止使用者
kexec 新内核启动 近似重启

三、核心数据结构

定义于 include/linux/livepatch.h

3.1 klp_func — 函数补丁项

1
2
3
4
5
6
7
8
9
10
11
struct klp_func {
const char *old_name; /* 被 patch 的函数名 */
void *new_func; /* 补丁实现地址 */
unsigned long old_sympos; /* 同名符号第 N 个(0=须唯一) */
void *old_func; /* 运行时解析的原函数地址 */
unsigned long old_size, new_size;
bool nop; /* replace 模式下的占位 NOP */
bool patched; /* 已加入 klp_ops */
bool transition; /* 过渡期中 */
struct list_head stack_node; /* klp_ops.func_stack 节点 */
};

patching 状态机

patched transition 含义
0 0 未 patch
0 1 未 patch,过渡初始态
1 1 已 patch,部分任务可见
1 0 已 patch,全部任务可见

unpatch 时按相反顺序迁移。

3.2 klp_object — 内核对象(vmlinux 或模块)

1
2
3
4
5
6
7
8
struct klp_object {
const char *name; /* NULL = vmlinux */
struct klp_func *funcs;
struct klp_callbacks callbacks; /* pre/post patch/unpatch */
struct module *mod; /* 运行时关联模块 */
bool dynamic; /* replace 模式动态 NOP 对象 */
bool patched;
};

3.3 klp_patch — 补丁顶层

1
2
3
4
5
6
7
8
struct klp_patch {
struct module *mod; /* livepatch 模块本身 */
struct klp_object *objs;
struct klp_state *states; /* 系统状态版本(累积补丁用) */
bool replace; /* 替换所有已有补丁 */
bool enabled;
bool forced; /* 强制过渡(可能破坏一致性) */
};

3.4 任务 patch 状态

每个 task_struct 有:

1
2
int patch_state;   /* KLP_UNDEFINED / KLP_UNPATCHED / KLP_PATCHED */
/* TIF_PATCH_PENDING 线程标志:需在安全点更新 patch_state */

全局过渡变量(transition.c):

1
2
struct klp_patch *klp_transition_patch;
static int klp_target_state; /* 目标状态 */

四、ftrace 跳转机制(patch.c)

4.1 klp_ops 与 func_stack

每个被 patch 的 原函数地址 对应一个 klp_ops

1
2
3
4
5
struct klp_ops {
struct list_head node;
struct ftrace_ops fops;
struct list_head func_stack; /* klp_func.stack_node,栈顶=当前生效 */
};

全局链表 klp_ops 管理所有已注册 ops。

4.2 klp_patch_func() 流程

1
2
3
4
5
6
7
8
9
1. klp_find_ops(old_func) — 是否已有 ops
2. 若无:
a. ftrace_location(old_func) 获取 ftrace 插桩点
b. 分配 klp_ops,设置 fops:
func = klp_ftrace_handler
flags = DYNAMIC | IPMODIFY | PERMANENT [| SAVE_REGS]
c. ftrace_set_filter_ip() + register_ftrace_function()
3. list_add_rcu(&func->stack_node, &ops->func_stack) /* 栈顶 */
4. func->patched = true

4.3 klp_ftrace_handler() — 核心跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void notrace klp_ftrace_handler(unsigned long ip,
unsigned long parent_ip,
struct ftrace_ops *fops,
struct ftrace_regs *fregs)
{
func = list_first_or_null_rcu(&ops->func_stack, ...);

if (unlikely(func->transition)) {
patch_state = current->patch_state;
if (patch_state == KLP_UNPATCHED) {
/* 过渡中仍用旧版:取 stack 中下一层 */
func = list_entry_rcu(func->stack_node.next, ...);
if (&func->stack_node == &ops->func_stack)
return; /* 无旧版,执行原函数 */
}
}

if (func->nop)
return; /* NOP:不修改 IP,执行原函数 */

ftrace_instruction_pointer_set(fregs, (unsigned long)func->new_func);
}

要点

  • 非过渡期:栈顶 new_func 直接生效
  • 过渡期:根据 current->patch_state 选择新/旧实现
  • NOP 函数new_func == old_func,handler 直接返回
  • 使用 ftrace_test_recursion_trylock 保证与 klp_synchronize_transition() 的 RCU 语义兼容

4.4 klp_unpatch_func()

  • func_stack 仅一项:注销 ftrace、删除 ops
  • 否则:仅从 stack 移除该 func(RCU 延迟释放)

五、补丁启用/禁用流程(core.c)

5.1 klp_enable_patch() — 入口

补丁模块在 module_init() 中调用:

1
2
3
4
5
6
7
8
9
int klp_enable_patch(struct klp_patch *patch)
{
/* 1. 校验:livepatch 模块标记、objs 非空 */
/* 2. 警告:无可靠栈时过渡可能永不完成 */
/* 3. klp_is_patch_compatible() — 累积补丁版本检查 */
/* 4. try_module_get() */
/* 5. klp_init_patch_early() + klp_init_patch() — sysfs、符号 */
/* 6. __klp_enable_patch() */
}

5.2 __klp_enable_patch()

1
2
3
4
5
6
7
8
9
10
11
klp_init_transition(patch, KLP_PATCHED)
→ 所有 task->patch_state = KLP_UNPATCHED(初始态)
→ 所有 func->transition = true
smp_wmb()
对每个已加载 object:
klp_pre_patch_callback()
klp_patch_object() ← ftrace 注册
klp_start_transition()
→ 设置 TIF_PATCH_PENDING
patch->enabled = true
klp_try_complete_transition()

5.3 __klp_disable_patch()

1
2
3
4
5
6
7
klp_init_transition(patch, KLP_UNPATCHED)
→ 初始态 = KLP_PATCHED
klp_pre_unpatch_callback()
klp_start_transition()
patch->enabled = false
klp_try_complete_transition()
→ 完成后 klp_unpatch_objects()

5.4 replace 模式(累积补丁)

patch->replace = true 时:

  1. klp_add_nops() — 为旧补丁所有函数创建 NOP 占位
  2. 启用新补丁后,klp_complete_transition() 中:
    • klp_unpatch_replaced_patches() — 注销旧补丁 ftrace
    • klp_discard_nops() — 移除动态 NOP

NOP 保证过渡期间 ftrace handler 始终有合法 stack 条目。

5.5 全局锁 klp_mutex

粗粒度互斥锁,保护所有 klp 数据结构。例外(无锁访问):

  • klp_ftrace_handler() — ftrace 上下文
  • klp_update_patch_state() — 任务安全点

六、一致性模型与过渡(transition.c)

6.1 问题背景

patch 生效后,可能仍有任务 栈上持有旧函数帧正在旧代码中执行。直接移除旧代码会导致 use-after-free 类崩溃。一致性模型确保:所有任务迁移到目标 patch_state 后才完成过渡

6.2 迁移路径

路径 触发点 条件
栈检查切换 klp_try_complete_transition() klp_have_reliable_stack()
内核退出切换 exit_to_user_mode_loop() TIF_PATCH_PENDING
idle 切换 do_idle() 循环 klp_patch_pending(current)
信号唤醒 klp_send_signals() 延迟工作重试超时
强制过渡 sysfs force=1 管理员承担风险

6.3 klp_update_patch_state()

安全点(返回用户态、idle 循环)调用:

1
2
3
4
5
6
7
void klp_update_patch_state(struct task_struct *task)
{
preempt_disable_notrace();
if (test_and_clear_tsk_thread_flag(task, TIF_PATCH_PENDING))
task->patch_state = READ_ONCE(klp_target_state);
preempt_enable_notrace();
}

kernel/entry/common.c 集成:

1
2
if (ti_work & _TIF_PATCH_PENDING)
klp_update_patch_state(current);

6.4 栈检查 klp_check_stack()

对 sleeping 任务保存可靠栈回溯,检查是否包含:

  • patching:旧函数或上一层 patched 函数地址范围
  • unpatching:新函数地址范围

若命中 → 任务暂不能切换,延迟重试。

6.5 klp_synchronize_transition()

RCU 非 watching 路径(如 user_exit() 之前),不能用普通 synchronize_rcu()

1
2
3
4
static void klp_synchronize_transition(void)
{
schedule_on_each_cpu(klp_sync); /* 强制所有 CPU schedule */
}

6.6 过渡完成 klp_complete_transition()

1
2
3
4
5
6
1. replace 模式:unpatch 旧补丁 + discard NOPs
2. unpatch 目标:klp_unpatch_objects() + synchronize
3. 所有 func->transition = false
4. 所有 task->patch_state = KLP_UNDEFINED
5. post_patch / post_unpatch 回调
6. klp_transition_patch = NULL

6.7 反向过渡与强制

  • klp_reverse_transition():sysfs enabled 在过渡中写反值,取消进行中的 enable/disable
  • klp_force_transition():对所有任务强制 klp_update_patch_state()可能破坏一致性,标记 patch->forced

七、Shadow 变量(shadow.c)

补丁常需为已有内核对象附加 私有扩展数据(如替换后的状态),Shadow 变量提供 <obj, id>data 映射。

7.1 数据结构

1
2
3
4
5
6
7
8
9
struct klp_shadow {
struct hlist_node node;
void *obj;
unsigned long id;
char data[]; /* 柔性数组 */
};

static DEFINE_HASHTABLE(klp_shadow_hash, 12); /* RCU 读 */
static DEFINE_SPINLOCK(klp_shadow_lock); /* 写保护 */

7.2 API

函数 说明
klp_shadow_get(obj, id) RCU 读,获取已有 shadow
klp_shadow_alloc(obj, id, size, gfp, ctor, data) 分配;重复则 WARN 返回 NULL
klp_shadow_get_or_alloc(...) 存在则返回,否则分配
klp_shadow_free(obj, id, dtor) 删除并 kfree_rcu
klp_shadow_free_all(id, dtor) 按 id 批量删除

并发:哈希表 RCU 读与 klp_shadow_free 并发安全;shadow 数据本身的互斥由调用者负责。


八、系统状态与累积补丁(state.c)

8.1 klp_state

1
2
3
4
5
struct klp_state {
unsigned long id; /* 非零自定义标识 */
unsigned int version; /* 状态版本号 */
void *data;
};

用于描述补丁修改的 系统级语义状态(如数据结构布局变更)。

8.2 兼容性检查

1
bool klp_is_patch_compatible(struct klp_patch *patch)

规则:

补丁类型 要求
replace(累积) 必须处理所有已有 patch 修改过的 state,且 version >= 旧版
非累积 可叠加,允许 touch 已有 state

辅助 API:

  • klp_get_state(patch, id) — 当前补丁的 state
  • klp_get_prev_state(id) — 过渡期间查已安装补丁的最新 state

九、符号解析与 KLP 重定位

9.1 符号查找 klp_find_object_symbol()

通过 kallsyms_on_each_symbol()module_kallsyms_on_each_symbol() 查找:

  • 支持 old_sympos 消歧(同名符号第 N 个)
  • sympos == 0 要求符号唯一,否则报错

9.2 KLP 重定位段

补丁模块 ELF 含特殊段:

段名格式 用途
.klp.rela.vmlinux.* 引用 vmlinux 未导出符号
.klp.rela.{module}.* 引用目标模块符号

符号名格式:.klp.sym.{objname}.{symname},{sympos}

两阶段应用

  1. klp 模块加载时:解析 vmlinux 重定位(klp_apply_section_relocs
  2. 目标模块加载时:解析 module 重定位(klp_init_object_loaded

这支持 late module patching(先加载补丁,后加载被 patch 模块)。


十、Sysfs 管理接口

挂载于 /sys/kernel/livepatch/

1
2
3
4
5
6
7
8
/sys/kernel/livepatch/
└── <patch_name>/
├── enabled (RW) 0=disable / 过渡中 reverse
├── transition (RO) 是否处于过渡
├── force (WO) 强制完成过渡
└── <object>/ vmlinux 或模块名
├── patched (RO)
└── <func,sympos>/ (函数目录)
属性 行为
enabled=0 触发 disable(若未在过渡)
enabled=1 过渡中 reverse;已 disable 的 patch 不可 re-enable
force=1 仅过渡中有效,强制所有任务切换

十一、动态模块补丁

11.1 klp_module_coming()

模块 MODULE_STATE_COMING 时:

1
2
3
4
mod->klp_alive = true
对每个 patch 中匹配 obj:
klp_init_object_loaded() → klp_patch_object()
若 patch 不在过渡中 → 立即 post_patch

失败则 拒绝加载模块

11.2 klp_module_going()

模块卸载/加载失败时:

1
2
mod->klp_alive = false
klp_cleanup_module_patches_limited() — unpatch + 释放符号

11.3 fork 继承 klp_copy_process()

子进程继承父进程 patch_stateTIF_PATCH_PENDING,由 tasklist_lock 与过渡操作串行化。


十二、RK3588/ARM64 平台说明

12.1 架构支持现状(Linux 6.1)

能力 ARM64/RK3588 说明
HAVE_DYNAMIC_FTRACE arch/arm64/Kconfig select
HAVE_DYNAMIC_FTRACE_WITH_REGS GCC/Clang 支持时 select
HAVE_LIVEPATCH 未 select(x86_64/s390/powerpc 有)
HAVE_RELIABLE_STACKTRACE 未 select(需 objtool/ORC 等)

因此 RK3588 默认 defconfig 下 CONFIG_LIVEPATCH 不可选;即使手动开启,也缺少架构官方 livepatch 支持与可靠栈,一致性模型主要依赖 内核退出/idle 切换,过渡可能缓慢或需 force

12.2 ftrace 与 IP 修改

ARM64 在 CONFIG_DYNAMIC_FTRACE_WITH_REGS 下,klp_ftrace_handler 通过 ftrace_instruction_pointer_set(fregs, new_func) 修改 pc,实现跳转。相关实现在:

  • arch/arm64/kernel/ftrace.c
  • arch/arm64/kernel/entry-ftrace.S

12.3 生产建议

  1. RK3588 嵌入式场景通常 不启用 livepatch;安全修复更常用 OTA 内核升级
  2. 若需实验:手动添加 select HAVE_LIVEPATCH 并满足全部 Kconfig 依赖,充分测试过渡与模块 patch
  3. 参考 samples/livepatch/lib/livepatch/ 测试模块

十三、完整启用/禁用时序

13.1 启用时序

各任务transition.cpatch.ccore.c补丁模块各任务transition.cpatch.ccore.c补丁模块loop[直到全部任务迁移]klp_enable_patch()klp_init_patch() 符号/sysfsklp_init_transition(KLP_PATCHED)patch_state = UNPATCHEDfunc->transition = truepre_patch_callback()klp_patch_object() / ftrace 注册klp_start_transition()TIF_PATCH_PENDING = 1klp_try_complete_transition()栈检查 或 等待安全点klp_update_patch_state()klp_complete_transition()post_patch_callback()

13.2 函数调用路径(运行时)

1
2
3
4
5
6
调用被 patch 函数
→ ftrace 入口 (mcount)
→ klp_ftrace_handler()
├── transition && patch_state==UNPATCHED → 旧实现
└── 否则 → ftrace_instruction_pointer_set(new_func)
→ 执行 new_func(补丁模块代码)

13.3 禁用时序(概要)

与启用对称:KLP_UNPATCHED 为目标态,初始态 KLP_PATCHED,完成后 klp_unpatch_objects() 注销 ftrace。


十四、总结

组件 职责
core.c 补丁生命周期、符号/KLP 重定位、sysfs、模块 coming/going
patch.c ftrace ops 管理、运行时 IP 重定向、func_stack 多层补丁
transition.c 一致性模型:任务状态机、栈检查、安全点切换、强制/反向过渡
shadow.c 补丁私有 per-object 数据
state.c 累积补丁 state 版本兼容

Livepatch 的核心难点不在「跳转」(ftrace 已成熟),而在 一致性模型:保证任意时刻每个任务执行的是与其 patch_state 一致的代码版本。RK3588/ARM64 在 Linux 6.1 上 未官方声明 HAVE_LIVEPATCH,部署前需评估架构支持与测试覆盖。


附录:源文件清单

文件 行数
kernel/livepatch/core.c 1291
kernel/livepatch/transition.c 666
kernel/livepatch/patch.c 289
kernel/livepatch/shadow.c 299
kernel/livepatch/state.c 119
kernel/livepatch/core.h 59
kernel/livepatch/patch.h 35
kernel/livepatch/transition.h 16
kernel/livepatch/state.h 9
kernel/livepatch/Kconfig 21
kernel/livepatch/Makefile 5
合计 ~2807

关联源码(目录外)

文件 行数(约) 说明
include/linux/livepatch.h 257 公共 API
kernel/entry/common.c 用户态返回路径
kernel/sched/idle.c idle 任务切换
arch/arm64/kernel/ftrace.c ARM64 ftrace 实现

kernel/locking 内核锁机制与原理详解

kernel/locking 内核锁机制与原理详解

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

该目录实现 Linux 内核 同步原语(Synchronization Primitives) 的核心运行时:自旋锁、互斥锁、读写信号量、计数信号量、实时互斥锁,以及 Lockdep 运行时锁依赖验证框架。这些原语是调度器、内存管理、文件系统、驱动等所有内核子系统并发控制的基础。

定位:内核并发控制的「基础设施层」;上层子系统通过 include/linux/spinlock.hmutex.hrwsem.h 等头文件调用本目录实现。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 始终编译
obj-y += mutex.o semaphore.o rwsem.o percpu-rwsem.o

# 条件编译
obj-$(CONFIG_DEBUG_IRQFLAGS) += irqflag-debug.o
obj-$(CONFIG_DEBUG_MUTEXES) += mutex-debug.o
obj-$(CONFIG_LOCKDEP) += lockdep.o lockdep_proc.o
obj-$(CONFIG_SMP) += spinlock.o
obj-$(CONFIG_LOCK_SPIN_ON_OWNER) += osq_lock.o
obj-$(CONFIG_QUEUED_SPINLOCKS) += qspinlock.o
obj-$(CONFIG_RT_MUTEXES) += rtmutex_api.o
obj-$(CONFIG_PREEMPT_RT) += spinlock_rt.o ww_rt_mutex.o
obj-$(CONFIG_QUEUED_RWLOCKS) += qrwlock.o
obj-$(CONFIG_LOCK_EVENT_COUNTS) += lock_events.o
配置项 说明
CONFIG_SMP 多核下编译 spinlock 通用实现
CONFIG_QUEUED_SPINLOCKS MCS 风格排队自旋锁(ARM64 默认 y)
CONFIG_QUEUED_RWLOCKS 排队读写自旋锁
CONFIG_MUTEX_SPIN_ON_OWNER mutex 乐观自旋(SMP + 原子 RMW)
CONFIG_RWSEM_SPIN_ON_OWNER rwsem 乐观自旋
CONFIG_RT_MUTEXES 实时互斥锁(PI 支持)
CONFIG_LOCKDEP 运行时锁依赖图验证
CONFIG_PROVE_LOCKING Lockdep 完整证明模式
CONFIG_LOCK_STAT 锁竞争统计(perf lock)
CONFIG_PREEMPT_RT RT 内核:spinlock→sleeping lock 转换

Kconfig 主要位于 kernel/Kconfig.lockslib/Kconfig.debug

1.2 源文件

文件 行数 功能
lockdep.c 6611 锁依赖图、死锁/逆序/IRQ 上下文检测
rwsem.c 1716 读写信号量:读者计数 + 写者独占
rtmutex.c 1834 RT 互斥锁:优先级继承、自适应自旋
mutex.c 1151 互斥锁:handoff、乐观自旋、WW-Mutex
locktorture.c 1117 锁压力测试(debug 配置)
lockdep_proc.c 730 /proc/lockdep* 接口
test-ww_mutex.c 683 WW-Mutex 自测
rtmutex_api.c 612 rtmutex 导出 API(含 #include rtmutex.c
qspinlock.c 596 排队自旋锁慢路径
ww_mutex.h 569 Wait/Wound 死锁避免算法
qspinlock_paravirt.h 562 虚拟化 paravirt qspinlock
spinlock.c 415 SMP spinlock/rwlock 通用实现
rwbase_rt.c 298 PREEMPT_RT 下 rwsem 基类
spinlock_rt.c 280 PREEMPT_RT spinlock→rtmutex 包装
percpu-rwsem.c 282 Per-CPU 读写信号量
semaphore.c 279 计数信号量
osq_lock.c 232 乐观自旋队列锁
lock_events.c 179 debugfs 锁事件计数
spinlock_debug.c 235 spinlock 调试检查
mutex-debug.c 118 mutex 调试检查
qrwlock.c 92 排队读写锁慢路径
ww_rt_mutex.c 101 PREEMPT_RT 下 WW-Mutex

相关头文件(目录外)

文件 功能
include/linux/spinlock.h spinlock/rwlock API
include/linux/mutex.h mutex API
include/linux/rwsem.h rwsem API
include/linux/rtmutex.h rtmutex API
include/linux/lockdep.h Lockdep 宏与 lock_class
arch/arm64/include/asm/spinlock.h ARM64 自旋锁类型与 inline
include/asm-generic/qspinlock.h 通用 qspinlock 实现

二、整体架构与锁分类

2.1 锁类型对比

锁类型 睡眠 上下文 典型用途
spinlock 否(自旋) 不可睡眠;IRQ 变体 短临界区、per-CPU、IRQ
rwlock 同上 读多写少短临界区
mutex 进程上下文 长临界区、严格所有权
rwsem 进程上下文 读多写少长临界区
semaphore 含 IRQ(down_trylock) 计数资源、遗留接口
rtmutex 进程上下文 PI、PREEMPT_RT、futex
percpu-rwsem 读者无锁 进程上下文 极多读、极少写

2.2 架构层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
应用 / 驱动 / 文件系统 / MM / 网络 ...


include/linux/{spinlock,mutex,rwsem,rtmutex,semaphore}.h


kernel/locking/ ← 本目录
├── qspinlock.c ← 自旋锁慢路径
├── mutex.c ← 睡眠锁
├── rwsem.c
├── rtmutex.c
├── lockdep.c ← 验证层(插桩于各锁 API)
└── osq_lock.c ← 乐观自旋辅助


arch/arm64/include/asm/spinlock.h ← 架构相关 fastpath
include/asm-generic/qspinlock.h

2.3 设计原则

  1. Fastpath / Slowpath 分离:热路径 inline 于头文件或架构代码,冷路径在本目录
  2. 乐观自旋(Optimistic Spinning):mutex/rwsem 在 owner 运行且未阻塞时短暂自旋,避免调度开销
  3. Handoff 机制:直接传递锁所有权,减少唤醒延迟
  4. Lockdep 插桩:通过 lock_acquire/lock_release 构建全局依赖图

三、自旋锁与排队自旋锁

3.1 spinlock.c — SMP 通用实现

CONFIG_GENERIC_LOCKBREAK 且非 debug 时,提供带 lockbreak 的 spin 循环:长时间自旋时 preempt_enable() 让出 CPU,并向持有者 CPU 发送 break 信号。

1
2
3
4
5
6
7
8
9
10
#define BUILD_LOCK_OPS(op, locktype) \
void __lockfunc __raw_##op##_lock(locktype##_t *lock) {
for (;;) {
preempt_disable();
if (likely(do_raw_##op##_trylock(lock)))
break;
preempt_enable();
arch_##op##_relax(&lock->raw_lock);
}
}

Lockdep 启用时 禁用 preempt spin-ops,因 lockdep 假设加锁期间不重新开中断。

3.2 qspinlock.c — MCS 排队自旋锁

传统 ticket/spinlock 在 SMP 上导致 缓存行乒乓;qspinlock 基于 MCS lock 改进,将 {tail, next->locked} 压缩进 32 位 spinlock_t

1
2
3
lock byte (1B)  : 锁状态 / pending
tail (3B) : (cpu+1)<<偏移 | nesting_level(2bit)
nesting: task / softirq / hardirq / nmi 最多 4 层

慢路径流程

1
2
3
4
5
6
queued_spin_lock_slowpath()
├── pending 位置自旋(减少 CAS 竞争)
├── 获取 per-CPU qnode(MAX_NODES=4)
├── MCS 队列入队 tail
├── 前驱 unlock 后 acquire
└── 队首获取锁,设置 _Q_LOCKED_VAL

Per-CPU 队列节点

1
static DEFINE_PER_CPU_ALIGNED(struct qnode, qnodes[MAX_NODES]);

3.3 qrwlock.c — 排队读写锁

读写锁慢路径:queued_read_lock_slowpath / queued_write_lock_slowpath

  • 读者:减 _QR_BIAS 入队等待,无写者时 acquire
  • 写者:设置 _QW_WAITING,等所有读者离开后 _QW_LOCKED
  • 中断上下文读者:若写者仅 waiting 未 holding,直接 spin acquire(避免睡眠)

3.4 mcs_spinlock.h

MCS 锁节点定义,供 qspinlock 与 osq_lock 共用:

1
2
3
4
struct mcs_spinlock {
struct mcs_spinlock *next;
int locked; /* 1=等待, 0=可运行 */
};

四、互斥锁 mutex(mutex.c)

4.1 数据结构

1
2
3
4
5
6
7
8
9
struct mutex {
atomic_long_t owner; /* task_struct* + 低 3 bit 标志 */
raw_spinlock_t wait_lock;
struct list_head wait_list;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq;
#endif
struct lockdep_map dep_map;
};

owner 标志位

Bit 名称 含义
0 MUTEX_FLAG_WAITERS 等待队列非空,unlock 需 wakeup
1 MUTEX_FLAG_HANDOFF 需 handoff 给 top waiter
2 MUTEX_FLAG_PICKUP handoff 完成,等待 pickup

4.2 Fastpath

1
2
3
4
5
6
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
if (!__mutex_lock_fastpath(lock))
__mutex_lock_slowpath(lock);
}

__mutex_trylock_common() 通过 CAS 设置 owner 为 current;失败进入 slowpath。

4.3 Slowpath 与 Handoff

1
2
3
4
5
__mutex_lock()
├── mutex_optimistic_spin() ← 见 §九
├── 加入 wait_list
├── schedule() 睡眠
└── 被唤醒后 __mutex_trylock(handoff=true) pickup

Handoff:unlock 时 __mutex_handoff(lock, top_waiter) 直接将 owner 设为等待者并置 PICKUP,避免「释放→再竞争」开销。

4.4 乐观自旋

1
2
3
4
5
mutex_optimistic_spin(lock, ww_ctx, waiter)
├── need_resched() / !owner_on_cpu() → 放弃
├── osq_lock(&lock->osq) ← 单 CPU 一个 MCS 节点
├── spin while owner->on_cpu && !need_resched
└── trylock 成功 → 返回 true

仅在 owner 正在其他 CPU 运行 时自旋;owner 睡眠则立即 schedule。

4.5 WW-Mutex 集成

ww_mutex_lock() 通过 ww_acquire_ctx 实现 Wait/Wound 机制(见 §十),用于 DRM/GPU 等多对象锁顺序场景。


五、读写信号量 rwsem(rwsem.c)

5.1 count 位域(64 位)

1
2
3
4
5
Bit 0     : RWSEM_WRITER_LOCKED  — 写者持有
Bit 1 : RWSEM_FLAG_WAITERS — 有等待者
Bit 2 : RWSEM_FLAG_HANDOFF — handoff 进行中
Bits 8-62 : 55-bit reader count
Bit 63 : RWSEM_FLAG_READFAIL — 读者获取失败守卫位

5.2 owner 字段

1
2
3
atomic_long_t owner;
/* Bit 0: RWSEM_READER_OWNED — 读者模式 */
/* Bit 1: RWSEM_NONSPINNABLE — 禁止乐观自旋 */

写者 acquire 时写入 task_struct*;读者设置 READER_OWNED。用于 乐观自旋 判断 owner 是否在运行。

5.3 读者/写者路径

操作 机制
down_read() atomic_long_fetch_add(READER_BIAS) fastpath
down_write() cmpxchg 获取 WRITER_LOCKED
竞争 wait_list + schedule
Lock stealing 写者 handoff 后读者可「偷取」锁
Optimistic spin 类似 mutex,spin on writer owner

5.4 PREEMPT_RT

#ifndef CONFIG_PREEMPT_RT 包裹主实现;RT 内核使用 rwbase_rt.c 将 rwsem 基于 rtmutex 实现。


六、实时互斥锁 rtmutex(rtmutex.c)

6.1 特性

  • 优先级继承(PI):解决优先级反转
  • 自适应自旋:owner 运行时 spin
  • Futex 基础:用户态锁的 kernel 侧实现
  • PREEMPT_RT:替代普通 mutex 作为睡眠锁

6.2 owner 编码

1
/* lock->owner: task_struct* + RT_MUTEX_HAS_WAITERS (bit 0) */

与 mutex 类似,bit 0 表示 waiters;fastpath 在 owner 无 waiters 时 CAS acquire/release。

6.3 PI 链

1
2
3
4
rt_mutex_adjust_prio_chain()
→ 沿 pi_waiters 链传播 boost
→ dl_server 支持 SCHED_DEADLINE
→ max_lock_depth = 1024 限制链深度

6.4 rtmutex_api.c

通过 #include "rtmutex.c" 编译,导出 rt_mutex_lock/unlockrt_mutex_init 等符号;CONFIG_DEBUG_LOCK_ALLOC 下提供 nested 变体。


七、计数信号量 semaphore(semaphore.c)

1
2
3
4
5
struct semaphore {
raw_spinlock_t lock;
unsigned int count; /* 剩余可用计数 */
struct list_head wait_list;
};
API 行为
down() count–;为 0 则睡眠(已废弃,应用 down_interruptible)
down_interruptible() 可被信号中断,返回 -EINTR
down_trylock() 非阻塞,可在 IRQ 上下文
up() count++;唤醒 wait_list

与 mutex 区别:无 owner 概念、可多次 down(计数>1)、调试能力弱;新代码优先 mutex。


八、Per-CPU 读写信号量(percpu-rwsem.c)

适用于 读者极多、写者极少 场景(如 mmap_lock 读者路径优化)。

1
2
3
4
5
6
7
struct percpu_rw_semaphore {
int __percpu *read_count;
atomic_t block; /* 写者阻塞新读者 */
struct rcu_sync rss; /* 与 RCU 协调 */
struct rcuwait writer;
wait_queue_head_t waiters;
};

读者 fastpath

1
2
3
4
5
6
preempt_disable()
this_cpu_inc(*read_count)
smp_mb()
if (!atomic_read_acquire(&sem->block)) → 成功
else → dec, 唤醒 writer, 重试/睡眠
preempt_enable()

写者atomic_xchg(&block, 1) 阻止新读者 → 等所有 per-CPU reader count 归零 → 写临界区 → rcu_sync + 解除 block。


九、乐观自旋 OSQ(osq_lock.c)

Optimistic Spin Queue:专为 sleeping lock 的 optimistic spin 设计的 MCS 变体。

1
static DEFINE_PER_CPU_SHARED_ALIGNED(struct optimistic_spin_node, osq_node);
  • 每 CPU 一个 spin node(sleeping lock 不在 IRQ 上下文 spin)
  • osq_lock() / osq_unlock() 维护全局 tail 队列
  • mutex、rwsem 在 spin 前 osq_lock(),保证 同一时刻每 CPU 只有一个 optimistic spinner

十、WW-Mutex 死锁避免(ww_mutex.h)

Wait/Wound / Acquired 机制,解决 AB-BA 类死锁(多 mutex 不同获取顺序):

1
2
3
4
5
6
Context A: 已持锁 1,等待锁 2
Context B: 已持锁 2,等待锁 1 → 经典死锁

WW-Mutex:
- 后启动的 context 被 "wound"(强制 drop 已持锁,重试)
- 或先启动的 "wait" 对方释放

实现要点(ww_mutex.h + mutex.c/rtmutex.c):

  • ww_acquire_ctx 记录 acquire 顺序
  • __ww_mutex_add_waiter() 按 stamp 排序等待队列
  • ww_mutex_lock() 检测冲突并 wound 等待者

DRM GPU 驱动广泛使用;PREEMPT_RT 下由 ww_rt_mutex.c 适配。


十一、Lockdep 锁依赖验证(lockdep.c)

11.1 检测能力

Lockdep 在运行时构建 锁依赖有向图,检测:

问题类型 说明
锁逆序(AB-BA) 即使未死锁,历史上不同顺序也被报告
循环依赖 锁环
IRQ 安全违规 在 IRQ 上下文使用非 IRQ-safe 锁
软 IRQ / Hard IRQ 混用 上下文不匹配

核心思想:证明观测到的所有锁序在数学上正确,而非仅检测当前是否死锁。

11.2 核心概念

1
2
3
struct lockdep_map;     /* 锁实例 → lock_class 映射 */
struct lock_class; /* 锁类(同类锁共享依赖规则) */
struct lock_chain; /* 锁获取链 */

Usage stateslockdep_states.h):

1
2
LOCKDEP_STATE(HARDIRQ)
LOCKDEP_STATE(SOFTIRQ)

每位锁类记录:在 HARDIRQ/SOFTIRQ/PROCESS 等上下文的使用与使能状态。

11.3 关键 API

1
2
3
4
5
void lock_acquire(struct lockdep_map *lock, unsigned int subclass,
int trylock, int read, unsigned long ip, ...);
void lock_release(struct lockdep_map *lock, unsigned long ip);
void lock_acquired(struct lockdep_map *lock, unsigned long ip);
void lock_contended(struct lockdep_map *lock, unsigned long ip);

各锁 *_lock() 宏展开时调用上述函数(CONFIG_PROVE_LOCKING 时完整验证)。

11.4 内部保护

Lockdep 自身用 raw arch_spinlock__lock)保护图结构,避免递归进入 lockdep:

1
2
static arch_spinlock_t __lock;
/* lockdep_lock() 要求 irqs_disabled() */

11.5 proc/debug 接口

  • lockdep_proc.c/proc/lockdep/proc/lockdep_chains
  • CONFIG_LOCK_STAT:竞争统计 → perf lock
  • CONFIG_LOCK_EVENT_COUNTS:debugfs lock_event_counts/

十二、调试与测试组件

组件 文件 功能
DEBUG_SPINLOCK spinlock_debug.c 未初始化/死锁检测
DEBUG_MUTEXES mutex-debug.c owner 校验、waiter 跟踪
DEBUG_IRQFLAGS irqflag-debug.c IRQ flag 状态一致性
LOCK_TORTURE_TEST locktorture.c 多锁类型压力测试
WW_MUTEX_SELFTEST test-ww_mutex.c WW-Mutex 单元测试
PREEMPT_RT spinlock_rt.c, rwbase_rt.c spinlock→rtmutex 语义转换

特殊编译标志

1
2
3
KCOV_INSTRUMENT := n          # 锁路径非确定性,不测 KCOV
KCSAN_SANITIZE_lockdep.o := n # 避免 lockdep ↔ KCSAN 递归
CFLAGS_REMOVE_lockdep.o = $(CC_FLAGS_FTRACE) # lockdep 不 ftrace

十三、RK3588/ARM64 平台说明

13.1 架构 Kconfig 选择

配置 ARM64 说明
ARCH_USE_QUEUED_SPINLOCKS ✅ select 使用 qspinlock
ARCH_USE_QUEUED_RWLOCKS ✅ select 使用 qrwlock
QUEUED_SPINLOCKS def_y SMP 下自动启用
QUEUED_RWLOCKS def_y 非 PREEMPT_RT
MUTEX_SPIN_ON_OWNER def_y SMP + ARCH_SUPPORTS_ATOMIC_RMW
RWSEM_SPIN_ON_OWNER def_y 同上
LOCKDEP_SUPPORT 支持 lockdep
ARCH_SUPPORTS_ATOMIC_RMW LL/SC 或 LSE 原子操作

13.2 自旋锁实现路径

1
2
3
4
5
6
arch/arm64/include/asm/spinlock.h
→ #include <asm/qspinlock.h>
arch/arm64/include/asm/Kbuild
→ generic-y += qspinlock.h ← 使用 asm-generic 实现
arch/arm64/include/asm/spinlock_types.h
→ #include <asm-generic/qspinlock_types.h>

ARM64 无独立 qspinlock 汇编 fastpath,使用 include/asm-generic/qspinlock.h 的原子操作 fastpath + kernel/locking/qspinlock.c 慢路径。

13.3 big.LITTLE 影响

RK3588 4×A76 + 4×A76 大小核:

  1. qspinlock 排队 减少跨核缓存乒乓,对大小核混合竞争有益
  2. 乐观自旋 依赖 owner_on_cpu():任务迁移后可能提前结束 spin
  3. Lockdep 开销较大,生产内核通常 CONFIG_LOCKDEP=n
  4. PREEMPT_RT 非 RK3588 默认;启用后 spinlock 语义变为可睡眠

13.4 典型 defconfig 预期

  • CONFIG_SMP=yCONFIG_QUEUED_SPINLOCKS=y
  • CONFIG_MUTEX_SPIN_ON_OWNER=y
  • CONFIG_LOCKDEP 通常 debug 配置启用
  • CONFIG_RT_MUTEXES=y(init/Kconfig 默认)

十四、典型加锁路径时序

14.1 mutex_lock 完整路径

调度器mutex slowpathoptimistic spinmutex fastpath任务调度器mutex slowpathoptimistic spinmutex fastpath任务alt[owner 运行中 && trylock 成功]alt[CAS 成功][CAS 失败]mutex_lock()持有锁mutex_optimistic_spin()持有锁__mutex_lock_slowpath()加入 wait_listschedule()被 handoff 唤醒__mutex_trylock(handoff)持有锁

14.2 qspinlock 竞争路径

1
2
3
4
CPU0: spin_lock() → fastpath CAS 成功
CPU1: spin_lock() → fastpath 失败 → queued_spin_lock_slowpath()
→ 分配 qnodes[0] → MCS 入队 → spin on prev->locked
CPU0: spin_unlock() → 释放 → CPU1 acquire

14.3 Lockdep 插桩路径

1
2
3
4
5
6
mutex_lock()
→ mutex_acquire(&lock->dep_map, ...) ← lock_acquire()
→ ... 实际加锁 ...
→ lock_acquired(&lock->dep_map, ip)
mutex_unlock()
→ lock_release(&lock->dep_map, ip)

十五、总结

组件 职责
qspinlock/qrwlock SMP 可扩展自旋锁,MCS 排队减少缓存竞争
mutex/rwsem 进程上下文睡眠锁,handoff + 乐观自旋优化
rtmutex PI + RT 内核基础 + futex 后端
semaphore 计数信号量(遗留,少用)
percpu-rwsem 多读少写极致读性能
osq_lock 乐观自旋排队,每 CPU 单节点
ww_mutex 多对象锁顺序死锁避免
lockdep 运行时锁依赖正确性证明

kernel/locking/ 是内核并发控制的基石;RK3588 ARM64 默认启用 排队自旋锁乐观自旋,在 8 核 big.LITTLE 上提供较好的锁扩展性。开发与调试阶段建议启用 Lockdep 捕获锁序问题,生产环境按需求权衡性能开销。


附录:源文件清单

文件 行数
kernel/locking/lockdep.c 6611
kernel/locking/rwsem.c 1716
kernel/locking/rtmutex.c 1834
kernel/locking/mutex.c 1151
kernel/locking/locktorture.c 1117
kernel/locking/lockdep_proc.c 730
kernel/locking/test-ww_mutex.c 683
kernel/locking/rtmutex_api.c 612
kernel/locking/qspinlock.c 596
kernel/locking/ww_mutex.h 569
kernel/locking/qspinlock_paravirt.h 562
kernel/locking/spinlock.c 415
kernel/locking/rwbase_rt.c 298
kernel/locking/spinlock_rt.c 280
kernel/locking/percpu-rwsem.c 282
kernel/locking/semaphore.c 279
kernel/locking/osq_lock.c 232
kernel/locking/lock_events.c 179
kernel/locking/spinlock_debug.c 235
kernel/locking/mutex-debug.c 118
kernel/locking/qrwlock.c 92
kernel/locking/ww_rt_mutex.c 101
头文件等 ~700
合计 ~19645

关联源码(目录外)

文件 说明
include/linux/spinlock.h 自旋锁 API 与 lockdep 宏
include/linux/mutex.h mutex 类型与 API
include/linux/rwsem.h rwsem API
include/linux/lockdep.h Lockdep 公共接口
arch/arm64/include/asm/spinlock.h ARM64 锁类型
include/asm-generic/qspinlock.h 通用 qspinlock fastpath
kernel/Kconfig.locks 锁相关 Kconfig
lib/Kconfig.debug Lockdep/调试 Kconfig
Documentation/locking/ 官方设计文档

kernel/module 内核模块(Loadable Kernel Module)机制与原理详解

kernel/module 内核模块(Loadable Kernel Module)机制与原理详解

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

该目录实现 Linux 可加载内核模块(LKM) 的核心运行时:模块的加载、重定位、符号解析、初始化/卸载、权限与安全校验,以及 sysfs/proc 管理接口。驱动、文件系统、网络协议栈等大量功能以 .ko 模块形式动态插入内核。

定位:连接用户态 insmod/modprobe/rmmod 与内核 ELF 加载器的桥梁;架构相关重定位在 arch/arm64/kernel/module.c,通用逻辑在本目录。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
11
12
13
obj-y += main.o strict_rwx.o

obj-$(CONFIG_MODULE_DECOMPRESS) += decompress.o
obj-$(CONFIG_MODULE_SIG) += signing.o
obj-$(CONFIG_LIVEPATCH) += livepatch.o
obj-$(CONFIG_MODULES_TREE_LOOKUP) += tree_lookup.o
obj-$(CONFIG_DEBUG_KMEMLEAK) += debug_kmemleak.o
obj-$(CONFIG_KALLSYMS) += kallsyms.o
obj-$(CONFIG_PROC_FS) += procfs.o
obj-$(CONFIG_SYSFS) += sysfs.o
obj-$(CONFIG_KGDB_KDB) += kdb.o
obj-$(CONFIG_MODVERSIONS) += version.o
obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING)+= tracking.o
配置项 说明
CONFIG_MODULES 启用可加载模块支持(总开关)
CONFIG_MODVERSIONS 符号 CRC 版本校验
CONFIG_MODULE_SIG 模块 PKCS#7 签名验证
CONFIG_MODULE_SIG_FORCE 拒绝未签名模块
CONFIG_STRICT_MODULE_RWX 模块 text/ro 只读、data 不可执行
CONFIG_MODULE_UNLOAD 允许卸载模块
CONFIG_MODULE_DECOMPRESS 内核内解压 .ko.gz/.ko.xz
CONFIG_MODULES_TREE_LOOKUP latch tree 加速 __module_address()
CONFIG_TRIM_UNUSED_KSYMS 裁剪未使用导出符号(与 livepatch 互斥)

Kconfig 位于 kernel/module/Kconfig

1.2 源文件

文件 行数 功能
main.c 3149 核心:加载/卸载/符号/系统调用/通知链
kallsyms.c 532 模块符号表构建与查询
sysfs.c 436 /sys/module/<name>/ 属性与参数
decompress.c 276 GZIP/XZ 模块解压
internal.h 305 内部 API、load_info、mod_tree
strict_rwx.c 143 模块内存 RWX 权限控制
procfs.c 146 /proc/modules
tracking.c 132 已卸载 taint 模块追踪
signing.c 125 签名验证
tree_lookup.c 117 latch tree 地址→模块查找
version.c 101 modversion CRC 校验
livepatch.c 74 livepatch 模块 ELF 持久化
kdb.c 62 KGDB/KDB 模块支持
debug_kmemleak.c 30 kmemleak 模块区域注册

架构相关(目录外)

文件 功能
arch/arm64/kernel/module.c module_alloc、AArch64 ELF 重定位
arch/arm64/kernel/kaslr.c 模块区域 KASLR 随机化
include/linux/module.h struct module、公共 API
include/linux/moduleloader.h 架构 loader 接口
kernel/module/ 外:kernel/kmod.c 内核侧 request_module

二、整体架构

2.1 用户态到内核态路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用户态                          内核态
────── ──────
insmod foo.ko args
→ init_module() ──────→ copy_module_from_user()
modprobe foo load_module()
→ do_init_module()
→ mod->init()

modprobe -r foo
→ delete_module() ──────→ mod->exit() → free_module()

finit_module(fd, args, flags)
→ kernel_read_file_from_fd()
→ [module_decompress()]
→ load_module()

2.2 模块与子系统关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────┐
│ 用户态: insmod / modprobe / rmmod │
└──────────────────────────┬──────────────────────────────────┘
│ init_module / finit_module / delete_module

┌─────────────────────────────────────────────────────────────┐
│ kernel/module/main.c │
│ load_module() → ELF 解析 → 重定位 → 符号 → init │
└──────┬──────────────┬──────────────┬────────────────────────┘
│ │ │
▼ ▼ ▼
signing.c strict_rwx.c kallsyms.c
version.c decompress.c sysfs.c / procfs.c
│ │ │
▼ ▼ ▼
arch/arm64/ ftrace/klp/ /sys/module/
kernel/module.c jump_label /proc/modules

2.3 全局同步

1
2
3
DEFINE_MUTEX(module_mutex);   /* 保护 modules 链表、mod_tree、module_use */
LIST_HEAD(modules); /* 所有已注册模块(RCU 遍历) */
struct mod_tree_root mod_tree; /* 地址范围索引 */

RCU 读路径find_symbol()__module_address() 可在 preempt_disable() 或持 module_mutex 下无锁遍历。


三、核心数据结构

3.1 struct module(include/linux/module.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct module {
enum module_state state;
struct list_head list; /* 全局 modules 链表 */
char name[MODULE_NAME_LEN];

struct module_layout core_layout; /* 常驻:text + ro + rw */
struct module_layout init_layout; /* 初始化后可释放 */
struct module_layout data_layout; /* 可选:vmalloc 数据区 */

struct module *init; /* 入口函数 */
void (*exit)(void); /* 卸载函数 */

const struct kernel_symbol *syms, *gpl_syms;
unsigned int num_syms, num_gpl_syms;
const s32 *crcs, *gpl_crcs;

atomic_t refcnt; /* MODULE_REF_BASE 偏移 */
struct list_head source_list; /* 本模块依赖的模块 (module_use) */
struct list_head target_list; /* 依赖本模块的模块 */

struct module_kobject mkobj; /* sysfs kobject */
struct mod_tree_node mtn; /* latch tree 节点 */
/* ftrace, jump_label, tracepoints, params, taints, klp_info ... */
};

3.2 struct module_layout

1
2
3
4
5
6
7
8
struct module_layout {
void *base;
unsigned int size;
unsigned int text_size; /* 可执行段 */
unsigned int ro_size; /* text + ro-data */
unsigned int ro_after_init_size; /* + ro-after-init */
struct mod_tree_node mtn;
};

内存分区CONFIG_STRICT_MODULE_RWX):

1
[ text (X) | ro-data (R) | ro-after-init (R) | writable data (RW, NX) ]

3.3 struct load_info(internal.h

加载过程中的临时上下文:

1
2
3
4
5
6
7
8
9
10
11
struct load_info {
const char *name;
struct module *mod;
Elf_Ehdr *hdr;
unsigned long len;
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
struct _ddebug_info dyndbg;
bool sig_ok;
struct { unsigned int sym, str, mod, vers, info, pcpu; } index;
};

四、模块状态机

1
2
3
4
5
6
enum module_state {
MODULE_STATE_LIVE, /* 正常运行 */
MODULE_STATE_COMING, /* 已形成,正在 module_init */
MODULE_STATE_GOING, /* 正在卸载 */
MODULE_STATE_UNFORMED, /* 加载中,尚未完成 formation */
};
状态 含义 可见性
UNFORMED ELF 解析/重定位中 仅在 modules 链表,kallsyms 不可见
COMING 符号已就绪,init 未返回 kallsyms 可见,try_module_get 失败
LIVE init 成功 完全可用
GOING exit 已调用或加载失败 正在 tear down

状态迁移:

1
2
3
4
UNFORMED → complete_formation() → COMING
COMING → do_init_module() 成功 → LIVE
LIVE → delete_module() → GOING → free_module()
任意 → 加载失败路径 → GOING → free_module()

五、加载流程(load_module)

load_module() 是加载核心,由 init_module / finit_module 系统调用触发。

5.1 主要步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1.  module_sig_check()           ← 签名验证,裁剪 sig 长度
2. elf_validity_check() ← ELF 头/段合法性
3. setup_load_info() ← 解析 section 索引
4. blacklist / rewrite_section_headers()
5. check_modstruct_version() ← module_layout CRC
6. layout_and_allocate() ← layout_sections + move_module
7. add_unformed_module() ← 加入 modules 链表 (UNFORMED)
8. percpu_modalloc()
9. find_module_sections() ← __ksymtab, __param, ftrace, jump_label...
10. check_module_license_and_versions()
11. setup_modinfo()
12. simplify_symbols() ← 解析未定义符号
13. apply_relocations() ← 架构重定位
14. post_relocation() ← extable排序, percpu, kallsyms, module_finalize
15. flush_module_icache()
16. complete_formation() → COMING
17. prepare_coming_module() ← ftrace, klp_module_coming, notifier
18. parse_args() ← 模块参数
19. mod_sysfs_setup()
20. do_init_module() → LIVE

5.2 layout_sections — 段布局

按优先级分配 core / init / data 区域:

轮次 段属性 目标 layout
0 SHF_EXECINSTR | SHF_ALLOC core text
1 SHF_ALLOC(非 WRITE) data ro
2 SHF_RO_AFTER_INIT data ro-after-init
3 SHF_WRITE | SHF_ALLOC data writable
init 段 .init*, __init* init_layout(带 INIT_OFFSET_MASK

5.3 move_module — 内存拷贝

1
2
3
4
mod->core_layout.base = module_alloc(core_size);   /* 架构实现 */
mod->init_layout.base = module_alloc(init_size);
/* ARCH_WANTS_MODULES_DATA_IN_VMALLOC: data 用 vzalloc */
/* 逐 SHF_ALLOC 段 memcpy 到最终地址 */

5.4 find_module_sections — 特殊段

解析并记录模块内各类元数据段:

段名 用途
__ksymtab / __ksymtab_gpl 模块导出符号
__param 模块参数
__ex_table 异常表
__jump_table jump label
__tracepoints_ptrs tracepoint
__dyndbg 动态 debug
.kprobes.text kprobes
.static_call_sites static call
.klp.rela.* livepatch 重定位

六、初始化与卸载

6.1 do_init_module()

1
2
3
4
5
6
7
8
9
10
11
12
do_mod_ctors(mod);                    /* .ctors / .init_array */
ret = mod->init(); /* 模块入口 */
mod->state = MODULE_STATE_LIVE;
blocking_notifier_call_chain(..., MODULE_STATE_LIVE, mod);
kobject_uevent(..., KOBJ_ADD); /* uevent 通知 udev */

/* init 段释放 */
ftrace_free_mem(mod, init_base, init_end);
rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms);
module_enable_ro(mod, true); /* ro-after-init 生效 */
mod_tree_remove_init(mod);
schedule_work(&init_free_wq); /* 异步释放 init 内存 */

init 段延迟释放:kallsyms 可能在 RCU 读路径遍历 init 区,故通过 workqueue + synchronize_rcu() 异步 module_memfree()

6.2 delete_module 系统调用

1
2
3
4
5
6
7
8
9
1. CAP_SYS_MODULE + !modules_disabled
2. find_module(name)
3. 检查 source_list 非空 → -EWOULDBLOCK(有被依赖)
4. state == LIVE
5. 有 init 无 exit → 需 force(-f)
6. try_stop_module() → refcnt 归零 → GOING
7. mod->exit()
8. notifier GOING + klp_module_going + ftrace_release_mod
9. free_module()

6.3 引用计数

1
2
3
4
atomic_t refcnt;   /* 加载时 +MODULE_REF_BASE */
try_module_get() /* LIVE 且 atomic_inc_not_zero */
module_put() /* atomic_dec_if_positive */
ref_module() /* 符号解析时建立 module_use 依赖 */

module_use 链表:模块 A 使用模块 B 的符号 → A.source_listB,卸载 B 前须先卸载所有依赖 A。


七、符号解析与版本校验

7.1 find_symbol()

1
2
3
4
5
bool find_symbol(struct find_symbol_arg *fsa)
{
/* 1. 内核 __ksymtab / __ksymtab_gpl(bsearch) */
/* 2. 遍历 modules 链表中各 mod->syms / gpl_syms */
}

搜索顺序:内核普通导出 → 内核 GPL 导出 → 各已形成模块的普通 → GPL。

7.2 resolve_symbol()

1
2
3
4
5
6
find_symbol(name)
→ GPL 许可检查(proprietary 模块不可用 GPL-only 符号)
→ inherit_taint(专有模块 taint 继承)
→ check_version(CRC) ← version.c
→ verify_namespace_is_imported ← EXPORT_SYMBOL_NS
→ ref_module(mod, owner) ← 建立依赖

7.3 modversion(version.c)

1
2
3
4
5
6
int check_version(info, symname, mod, crc)
{
/* 在 .modversions 段查找 symname 的 CRC */
/* 匹配 → OK;不匹配 → 拒绝加载 */
/* versindex==0 → try_to_force_load()(--force) */
}

module_layout 符号 CRC 校验 struct module 布局兼容性。

7.4 vermagic

1
2
static const char vermagic[] = VERMAGIC_STRING;
/* modinfo 中 vermagic 须匹配;有 CRC 时仅比较 SMP/preempt 等后缀 */

八、内存布局与 RWX 保护(strict_rwx.c)

CONFIG_STRICT_MODULE_RWX 启用时:

区域 权限
text RO + X(via module_enable_x
ro-data / ro-after-init RO + NX
writable data RW + NX
1
2
3
4
5
6
7
8
9
10
void module_enable_ro(mod, after_init)
{
frob_text(..., set_memory_ro); /* text 变只读 */
frob_rodata(..., set_memory_ro);
if (after_init)
frob_ro_after_init(..., set_memory_ro); /* init 完成后 */
}

void module_enable_nx(mod) /* data 段不可执行 */
void module_enable_x(mod) /* text 段可执行 */

module_enforce_rwx_sections() 拒绝同时具有 SHF_WRITE | SHF_EXECINSTR 的段。


九、模块签名(signing.c)

1
模块末尾: [ ELF 内容 | PKCS#7 签名 | module_signature 结构 | "~Module signature appended~\n" ]

流程:

1
2
3
4
5
6
7
module_sig_check(info, flags)
→ 检测 MODULE_SIG_STRING 标记
→ mod_verify_sig() → verify_pkcs7_signature()
→ sig_ok = true / taint TAINT_UNSIGNED_MODULE

is_module_sig_enforced() ← CONFIG_MODULE_SIG_FORCE 或 cmdline
→ 未签名/无效签名 → -EKEYREJECTED

Lockdown LSM 联动:lockdown 模式下未签名模块可能被拒绝。


十、模块解压(decompress.c)

finit_module(..., MODULE_INIT_COMPRESSED_FILE) 或内核内置解压:

格式 实现
GZIP (.ko.gz) zlib_inflateInit2 + 逐页输出
XZ (.ko.xz) xz_dec_run

解压到 load_info.pages[] 页数组,供后续 ELF 解析。用于 load pinning 等需内核自行读模块的场景。


十一、Kallsyms 与地址查找

11.1 kallsyms.c

  • layout_symtab() — 将符号表移入 init 段布局
  • add_kallsyms() — 构建 mod->core_kallsyms(symtab + strtab)
  • init 完成后切换:rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms)

11.2 地址→模块查找

tree_lookup.cCONFIG_MODULES_TREE_LOOKUP,PERF/TRACING 启用):

1
2
3
4
5
/* latch tree,RCU-sched 安全 */
struct module *mod_find(unsigned long addr, struct mod_tree_root *tree)
{
ltn = latch_tree_find(addr, &mod_tree.root, &mod_tree_ops);
}

否则线性扫描 modules 链表 + within_module()

1
2
struct module *__module_address(unsigned long addr)
struct module *__module_text_address(unsigned long addr) /* 仅 text 段 */

十二、Sysfs 与 Procfs 接口

12.1 Sysfs(sysfs.c)

1
2
3
4
5
6
7
8
9
10
11
/sys/module/<name>/
├── initstate (RO) Live/Loading/Unloading
├── coresize (RO)
├── initsize (RO)
├── refcnt (RO) [CONFIG_MODULE_UNLOAD]
├── taint (RO)
├── version (RO)
├── parameters/ (RW) 模块参数
├── sections/ (RO) 各段地址 [CONFIG_KALLSYMS]
├── holders/ (RO) 依赖本模块的模块
└── notes/ (RO) ELF notes

12.2 Procfs(procfs.c)

/proc/modules 格式:

1
name size refcnt deps address [taint_flags]

例:nvme 123456 0 - Live 0xffffffc008000000


十三、Livepatch 集成(livepatch.c)

livepatch 补丁模块加载时:

1
2
3
copy_module_elf(mod, info)
→ 持久化 Elf header、section headers、secstrings
→ symtab 指向 core_kallsyms.symtab(init 释放后仍可用)

配合 kernel/livepatch/core.cklp_apply_section_relocs() 解析 .klp.rela.* 段。


十四、RK3588/ARM64 平台说明

14.1 module_alloc(arch/arm64/kernel/module.c

1
2
3
4
5
6
7
8
9
void *module_alloc(unsigned long size)
{
p = __vmalloc_node_range(size, MODULE_ALIGN,
module_alloc_base,
module_alloc_base + MODULES_VSIZE,
GFP_KERNEL, PAGE_KERNEL, ...);
/* KASAN shadow 分配 */
return kasan_reset_tag(p);
}
  • 模块加载于 vmalloc 模块区域_etext 下方 MODULES_VSIZE
  • KASLRkaslr.c 随机化 module_alloc_base(21-bit 偏移)
  • MODULE PLT:静态区域耗尽时 fallback 到 2GB 窗口
  • ARCH_HAS_STRICT_MODULE_RWX:ARM64 支持严格 RWX

14.2 AArch64 重定位

arch/arm64/kernel/module.c 处理 ELF 重定位类型:

  • R_AARCH64_ABS64 / PREL32 / CALL26 / JUMP26
  • R_AARCH64_ADR_PREL_PG_HI21 等页相对重定位
  • alternativesftracestatic_call 模块段处理
  • CFI 影子类型校验(module_cfi_finalize

14.3 RK3588 驱动开发注意点

  1. 内核与模块须 相同 vermagic 或启用 CONFIG_MODVERSIONS 且 CRC 匹配
  2. 启用 CONFIG_MODULE_SIG_FORCE 时须用 scripts/sign-file 签名
  3. 树外模块编译:make modules + make modules_install;依赖 Kbuild 导出符号
  4. EXPORT_SYMBOL_NS() 须对应 MODULE_IMPORT_NS(),否则加载失败
  5. 卸载驱动前确认 refcnt=0 且无 holders(lsmod / /sys/module/.../holders/

14.4 架构 Kconfig

配置 ARM64
ARCH_USE_QUEUED_SPINLOCKS
ARCH_HAS_STRICT_MODULE_RWX ✅ select
ARCH_SUPPORTS_ATOMIC_RMW
MODULES_TREE_LOOKUP def_y(PERF/TRACING 时)

十五、完整加载/卸载时序

15.1 加载时序

arch/module.cdo_init_moduleload_module系统调用用户态arch/module.cdo_init_moduleload_module系统调用用户态finit_module(fd, args, 0)kernel_read_file + load_modulesig_check, elf_validity, layoutapply_relocationssimplify_symbols, post_relocationcomplete_formation (COMING)klp_module_coming, parse_argsdo_init_modulemod->init()state=LIVE, free init, module_enable_ro0

15.2 卸载时序

1
2
3
4
5
6
delete_module("foo")
→ try_stop_module: refcnt==0 → GOING
→ mod->exit()
→ klp_module_going, ftrace_release_mod
→ free_module: sysfs teardown, module_memfree, 从 modules 移除
→ wake_up(module_wq)

15.3 通知链

1
2
register_module_notifier()
blocking_notifier_call_chain(&module_notify_list, state, mod)

通知时机:COMING(prepare)、LIVE(init 成功)、GOING(卸载/失败)。


十六、总结

组件 职责
main.c 加载/卸载主流程、系统调用、符号解析、依赖管理
strict_rwx.c 模块内存 W^X 安全
signing.c PKCS#7 签名验证
version.c modversion CRC 兼容性
kallsyms.c 模块符号表
tree_lookup.c O(log n) 地址→模块
sysfs.c / procfs.c 用户态管理接口
decompress.c 压缩模块解压
livepatch.c 热补丁 ELF 持久化
tracking.c 卸载 taint 模块审计

内核模块机制将 ELF 对象 安全地映射进运行中内核:通过 vermagic/modversion 保证 ABI 兼容,通过签名与 RWX 保护增强安全,通过 refcnt 与 module_use 保证卸载安全。RK3588 驱动开发几乎完全依赖此框架。


附录:源文件清单

文件 行数
kernel/module/main.c 3149
kernel/module/kallsyms.c 532
kernel/module/sysfs.c 436
kernel/module/decompress.c 276
kernel/module/internal.h 305
kernel/module/strict_rwx.c 143
kernel/module/procfs.c 146
kernel/module/tracking.c 132
kernel/module/signing.c 125
kernel/module/tree_lookup.c 117
kernel/module/version.c 101
kernel/module/livepatch.c 74
kernel/module/kdb.c 62
kernel/module/debug_kmemleak.c 30
kernel/module/Kconfig 293
kernel/module/Makefile 21
合计 ~5628

关联源码(目录外)

文件 行数(约) 说明
arch/arm64/kernel/module.c 519 AArch64 重定位与 module_alloc
arch/arm64/kernel/kaslr.c 模块区域 KASLR
include/linux/module.h struct module 定义
kernel/kmod.c request_module 内核接口
scripts/mod/modpost.c 构建时 modpost 处理

kernel/power 内核电源管理与休眠机制与原理详解

kernel/power 内核电源管理与休眠机制与原理详解

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

该目录实现 Linux 系统级睡眠(System Sleep)电源管理基础设施:Suspend-to-RAM(STR)、Suspend-to-Idle(S2Idle)、Hibernation(STD/swsusp)、进程冻结、PM QoS、Energy Model,以及 /sys/power 用户接口。设备运行时 PM(runtime suspend)的核心逻辑在 drivers/base/power/,但系统休眠路径通过 DPM(Device Power Management) 与本目录紧密协作。

定位:协调「整机睡眠/唤醒」的框架层;平台相关休眠入口由 arch/arm64/drivers/soc/rockchip/ 通过 suspend_set_ops() 注册。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
11
12
13
obj-y += qos.o

obj-$(CONFIG_PM) += main.o
obj-$(CONFIG_FREEZER) += process.o
obj-$(CONFIG_SUSPEND) += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o
obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) += user.o
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o
obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o
obj-$(CONFIG_ENERGY_MODEL) += energy_model.o
obj-$(CONFIG_VT_CONSOLE_SLEEP) += console.o
obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
配置项 说明
CONFIG_PM 设备 PM 核心(本目录 main.c 部分功能)
CONFIG_PM_SLEEP 系统睡眠(依赖 SUSPEND 或 HIBERNATE_CALLBACKS)
CONFIG_SUSPEND Suspend-to-RAM / Standby / S2Idle
CONFIG_HIBERNATION 休眠到磁盘(swsusp)
CONFIG_FREEZER 睡眠前冻结用户态/内核线程
CONFIG_PM_AUTOSLEEP 无 wakeup source 时自动睡眠
CONFIG_PM_WAKELOCKS 用户态 wakelock 接口
CONFIG_ENERGY_MODEL DVFS 设备能耗模型(调度/thermal 用)
CONFIG_SUSPEND_FREEZER STR 前冻结进程(默认 y)

Kconfig 位于 kernel/power/Kconfig(由 arch/arm64/Kconfig source)。

1.2 源文件

文件 行数 功能
snapshot.c 2756 内存快照:PBE、页面备份/恢复、镜像读写
hibernate.c 1367 休眠流程编排、平台回调、resume 参数
swap.c 1619 快照镜像读写 swap 分区
main.c 940 PM 核心:sysfs、notifier、state 入口、统计
qos.c 678 PM QoS 约束聚合(latency、flags)
suspend.c 648 STR/S2Idle 状态机、platform ops
energy_model.c 438 CPU/GPU 等平台能耗表注册
user.c 466 uswsusp 用户态快照设备
wakelock.c 285 用户态 wakelock(Android 兼容)
process.c 235 睡眠前 freeze/thaw 进程
suspend_test.c 219 启动时 test_suspend 自检
console.c 152 VT 控制台 suspend/resume
autosleep.c 129 opportunistic sleep
poweroff.c 45 SysRq 关机辅助
power.h 328 内部 API、swsusp 结构

相关目录(目录外)

路径 功能
drivers/base/power/ 设备 runtime PM、wakeup source、DPM
kernel/freezer.c 任务 refrigerator 机制
arch/arm64/kernel/suspend.c CPU 级 suspend/resume(cpu_suspend
drivers/soc/rockchip/ RK 平台 suspend 模式、lite/ultra

二、整体架构

2.1 子系统分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
用户空间
echo mem > /sys/power/state
/sys/power/autosleep, wakeup_count, mem_sleep


kernel/power/ ← 本目录(系统睡眠框架)
main.c — sysfs、pm_notifier、pm_suspend 入口
suspend.c — STR/S2Idle 流程
hibernate.c + snapshot.c + swap.c — STD
process.c — freezer
qos.c / energy_model.c — 约束与能耗

├── dpm_suspend_* / dpm_resume_* → drivers/base/power/
├── syscore_suspend / syscore_resume
└── suspend_ops->enter() → 平台(Rockchip ATF/PSCI)


arch/arm64/kernel/suspend.c — cpu_suspend / __cpu_suspend_enter
drivers/soc/rockchip/ — ROCKCHIP_SUSPEND_MODE 等

2.2 与 Runtime PM 的区别

维度 System Sleep(本目录) Runtime PM(drivers/base/power)
粒度 整机 单个 device
触发 /sys/power/state、autosleep pm_runtime_suspend()
进程 通常 freeze 全部用户进程 不 freeze
状态 PM_SUSPEND_MEM runtime_status

两者通过 DPM 在系统休眠时统一 suspend 所有设备。


三、睡眠状态与术语

3.1 suspend_state_t

枚举 pm_labels(state) mem_sleep_labels 含义
PM_SUSPEND_TO_IDLE freeze s2idle 冻结进程+设备,CPU 进 idle
PM_SUSPEND_STANDBY standby shallow 浅睡(平台相关)
PM_SUSPEND_MEM mem deep 深睡 STR(内存保持)
PM_SUSPEND_MAX 写入 “disk” 触发 hibernate

RK 平台扩展(CONFIG_ROCKCHIP_LITE_ULTRA_SUSPEND):

状态 mem_sleep 说明
PM_SUSPEND_MEM_LITE lite 电子书等场景的轻量 mem 睡眠
PM_SUSPEND_MEM_ULTRA ultra 更深度的 ultra 睡眠

写入 mem_sleep 时选择实际 mem_sleep_currentpm_suspend() 会将 lite/ultra 映射为 PM_SUSPEND_MEM 再进入平台代码。

3.2 全局变量

1
2
3
suspend_state_t mem_sleep_current;      /* 当前 mem 睡眠深度 */
suspend_state_t pm_suspend_target_state;
struct mutex system_transition_mutex; /* 睡眠转换互斥 */

四、核心入口与 Sysfs(main.c)

4.1 /sys/power/ 主要属性

属性 读写 功能
state RW 写入 mem/freeze/disk 触发睡眠
mem_sleep RW 选择 s2idle/shallow/deep(及 lite/ultra)
sync_on_suspend RW 睡眠前是否 sync() 文件系统
wakeup_count RW 与 wakeup event 配合避免竞态
pm_async RW 设备异步 suspend/resume
pm_test RW 调试:在指定阶段停止(freezer/devices/platform…)
pm_print_times RW 打印各设备 suspend/resume 耗时
autosleep RW opportunistic sleep 目标状态
wake_lock / wake_unlock WO 用户态 wakelock(CONFIG_PM_WAKELOCKS)

4.2 state_store 流程

1
2
3
4
state = decode_state(buf, n);   /* "mem" → PM_SUSPEND_MEM, "disk" → hibernate */
if (state == PM_SUSPEND_MEM)
state = mem_sleep_current;
error = pm_suspend(state); /* 或 hibernate() */

4.3 PM Notifier

1
2
static BLOCKING_NOTIFIER_HEAD(pm_chain_head);
register_pm_notifier() / pm_notifier_call_chain()

通知阶段包括 PM_SUSPEND_PREPAREPM_POST_SUSPENDPM_HIBERNATION_PREPARE 等,供子系统(如 CPUFreq)在睡眠前后调整状态。

4.4 lock_system_sleep()

1
2
current->flags |= PF_NOFREEZE;
mutex_lock(&system_transition_mutex);

防止睡眠线程自身被 freezer 冻结;与 hibernation 的 snapshot 读写路径配合。


五、Suspend-to-RAM(suspend.c)

5.1 平台操作表

1
2
3
static const struct platform_suspend_ops *suspend_ops;

void suspend_set_ops(const struct platform_suspend_ops *ops);

典型回调:

回调 时机
valid(state) 检查平台是否支持该睡眠状态
prepare() 设备 suspend 之前
prepare_late() dpm_suspend_late 前后
enter(state) 关中断、停从核、进硬件睡眠
wake() 唤醒后早期处理

S2Idle 另有 platform_s2idle_opsprepare/check/wake)。

5.2 enter_state() — 顶层流程

1
2
3
4
5
6
7
8
9
10
11
12
mutex_trylock(system_transition_mutex)
sync_on_suspend → ksys_sync_helper()(可选)
suspend_prepare(state)
├── pm_notifier PM_SUSPEND_PREPARE
├── suspend_freeze_processes() ← process.c
├── dpm_suspend_start(PMSG_SUSPEND)
└── platform prepare
suspend_devices_and_enter(state)
suspend_finish()
├── thaw_processes()
└── pm_notifier PM_POST_SUSPEND
mutex_unlock(system_transition_mutex)

5.3 suspend_devices_and_enter()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
suspend_console()
dpm_suspend_start(PMSG_SUSPEND) /* 设备 early suspend */
suspend_enter(state)
├── dpm_suspend_late()
├── dpm_suspend_noirq()
├── [S2Idle] s2idle_loop()
├── [STR] pm_sleep_disable_secondary_cpus()
│ syscore_suspend()
│ suspend_ops->enter(state) ← 平台进睡眠
│ syscore_resume()
│ pm_sleep_enable_secondary_cpus()
└── dpm_resume_noirq / early / finish
dpm_resume_end(PMSG_RESUME)
resume_console()

5.4 Suspend-to-Idle(S2Idle)

不调用 suspend_ops->enter(),而是:

1
2
3
s2idle_loop():
wake_up_all_idle_cpus() /* 所有 CPU 进 idle */
swait_event(s2idle_wait_head) /* 等待 wakeup */

等价于:冻结进程 + 设备 suspend + CPU idle,功耗低于运行态但通常高于 STR。

5.5 suspend 统计

1
struct suspend_stats suspend_stats;  /* success/fail、failed_dev、errno、step */

可通过 debugfs suspend_stats 或 sysfs suspend_stats/ 查看。


六、进程冻结(process.c)

睡眠前须冻结用户态与可冻结内核线程,防止持有锁或 DMA 的进程在设备已 suspend 后继续运行。

6.1 freeze_processes()

1
2
3
4
5
__usermodehelper_disable(UMH_FREEZING)
current->flags |= PF_SUSPEND_TASK
pm_freezing = true
try_to_freeze_tasks(user_only=true) /* 对每个 task freeze_task() */
oom_killer_disable()

6.2 freeze_kernel_threads()

1
2
pm_nosig_freezing = true
try_to_freeze_tasks(user_only=false) /* 含 workqueue */

6.3 thaw_processes()

1
2
3
4
oom_killer_enable()
thaw_workqueues()
__thaw_task() 对所有进程
usermodehelper_enable()

超时默认 freeze_timeout_msecs = 20s;失败时打印 refusing-to-freeze 任务栈。


七、Hibernation 休眠到磁盘(hibernate.c / snapshot.c / swap.c)

7.1 原理(swsusp)

  1. 冻结进程、quiesce 设备
  2. snapshot.c:将内存页写入 PBE(Page Backup Entry) 链表,生成连续镜像
  3. swap.c:镜像压缩(LZO 等)写入 swap 分区
  4. 关机或重启
  5. 下次启动带 resume=/dev/swap:从 swap 读镜像、snapshot 恢复内存、跳回原内核上下文

7.2 关键结构(power.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct swsusp_info {
struct new_utsname uts;
u32 version_code;
unsigned long num_physpages;
int cpus;
unsigned long image_pages;
...
};

struct snapshot_handle {
unsigned int cur;
void *buffer;
int sync_read;
};

镜像通过 snapshot_read_next() / snapshot_write_next() 以 PAGE_SIZE 块流式读写。

7.3 hibernation_snapshot() 概要

1
2
3
4
5
6
platform_begin()
dpm_suspend_start(PMSG_FREEZE)
create_basic_memory_bitmaps()
swsusp_alloc() / swsusp_save() ← snapshot.c,保存 CPU/内存状态
swsusp_write(flags) ← swap.c,写 swap
platform_finish()

7.4 恢复路径

1
2
3
4
swsusp_check() — 检测 swap 上有效镜像
swsusp_read() — 读入镜像
restore_highmem() / resume_target_kernel()
arch 相关:swsusp_arch_resume、enable_restore_image_protection

7.5 平台模式

1
2
enum { HIBERNATION_PLATFORM, HIBERNATION_SHUTDOWN, HIBERNATION_REBOOT, ... };
hibernation_set_ops(platform_hibernation_ops);

ARM64 支持 ARCH_HIBERNATION_POSSIBLE + ARCH_HIBERNATION_HEADER


八、PM QoS(qos.c)

Quality of Service 约束框架:多个请求者注册 latency/flags 需求,内核聚合为 target_value

1
2
3
int pm_qos_update_target(struct pm_qos_constraints *c,
struct plist_node *node,
enum pm_qos_req_action action, int value);
聚合类型 规则
PM_QOS_MIN 取 plist 最小值(最严格 latency 要求)
PM_QOS_MAX 取最大值

变更时通过 blocking_notifier_call_chain(c->notifiers) 通知 CPUIdle/CPUFreq 等子系统。

常见约束:CPU latency QoS、freq QoS、device PM QoS flags(dev_pm_qos_* 在 drivers/base/power)。


九、Energy Model(energy_model.c)

DVFS 设备(主要是 CPU)注册 性能状态 → 功耗/成本 表,供调度器(EAS)、thermal 使用。

1
2
3
4
5
struct em_perf_domain {
struct em_perf_state *table; /* frequency, power, cost */
unsigned int nr_perf_states;
struct cpumask *cpus;
};

驱动通过 em_dev_register_perf_domain() 注册;debugfs 位于 /sys/kernel/debug/energy_model/

RK3588 big.LITTLE 场景下,A76/A55 各 cluster 可注册独立 perf domain,调度器据此做能效感知选核。


十、Autosleep 与 Wakelock

10.1 autosleep.c

1
2
3
4
5
/* /sys/power/autosleep 写入 "mem" 后 */
try_to_suspend(work):
pm_save_wakeup_count()
pm_suspend(autosleep_state) 或 hibernate()
queue_up_suspend_work() /* 循环尝试 */

仅当 无 active wakeup source 时进入睡眠;与 Android 式 power manager 类似。

10.2 wakelock.c

用户态通过 /sys/power/wake_lockwake_unlock 创建 wakeup_source

1
2
3
4
struct wakelock {
struct rb_node node;
struct wakeup_source *ws;
};

__pm_stay_awake() / __pm_relax() 阻止 autosleep;可选 GC(PM_WAKELOCKS_GC)回收长期未用 wakelock。


十一、辅助组件

文件 功能
console.c VT 控制台在睡眠前后 save/restore
user.c /dev/snapshot 字符设备,uswsusp 用户态休眠
suspend_test.c 内核参数 test_suspend=mem 启动时自动测 suspend
poweroff.c Magic SysRq 触发 orderly poweroff
tracking.c 记录已卸载 taint 模块(debugfs)

十二、RK3588/ARM64 平台说明

12.1 ARM64 架构支持

配置 说明
ARCH_SUSPEND_POSSIBLE y 支持 STR
ARCH_HIBERNATION_POSSIBLE y 依赖 CPU_PM
ARCH_HIBERNATION_HEADER y 休眠镜像含 arch 头
CPU_PM select if SUSPEND CPU 电源管理

arch/arm64/kernel/suspend.c 提供 cpu_suspend()

1
2
3
4
5
6
7
8
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
{
/* 保存 GPR、FP、debug、MMU 等到 sleep_stack_data */
if (__cpu_suspend_enter(&state))
ret = fn(arg); /* 平台 finisher,通常不再返回 */
else
__cpu_suspend_exit(); /* 唤醒恢复路径 */
}

12.2 Rockchip 平台扩展

配置 说明
ROCKCHIP_SUSPEND_MODE 通过 ATF 配置 suspend 模式
ROCKCHIP_LITE_ULTRA_SUSPEND mem_sleep 增加 lite/ultra(电子书等)
ROCKCHIP_SUSPEND_DEBUG suspend 调试
ROCKCHIP_SYSTEM_MONITOR 系统 monitor、early suspend

平台驱动在 init 时调用 suspend_set_ops(&rockchip_suspend_ops).enter() 最终通过 PSCI SYSTEM_SUSPEND 或 Rockchip ATF 进入硬件低功耗。

12.3 RK3588 常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看可用睡眠状态
cat /sys/power/state # freeze mem disk

# 选择 deep sleep 深度
cat /sys/power/mem_sleep # [s2idle] shallow deep (lite ultra)

# 进入 STR
echo mem > /sys/power/state

# S2Idle(仅 freeze,不进硬件 deep sleep)
echo freeze > /sys/power/state

# 唤醒源计数(避免睡眠竞态)
read /sys/power/wakeup_count
echo mem > /sys/power/state

12.4 big.LITTLE 注意点

  • STR 时 非 boot CPU 离线,boot CPU 在 suspend_ops->enter() 中最后进入 WFI/平台睡眠
  • 唤醒后 secondary CPU 重新 online;cpuidle 在 pm_sleep_disable/enable_secondary_cpus() 中 pause/resume
  • Energy Model 与 EAS 调度器配合,影响 睡眠前选核唤醒后负载迁移(逻辑在 kernel/sched/drivers/cpufreq/

十三、完整 Suspend 时序

platform/ATFdrivers/base/powerprocess.csuspend.cmain.c用户空间platform/ATFdrivers/base/powerprocess.csuspend.cmain.c用户空间CPU WFI / PSCI suspendecho mem > /sys/power/statepm_suspend(PM_SUSPEND_MEM)enter_state()suspend_freeze_processes()dpm_suspend_start()suspend_devices_and_enter()dpm_suspend_late/noirq()suspend_ops->enter()唤醒中断dpm_resume_*suspend_thaw_processes()返回用户空间

十四、总结

组件 职责
main.c /sys/power 接口、PM notifier、睡眠入口协调
suspend.c STR/S2Idle 状态机、DPM 与 platform ops 编排
process.c 睡眠前 freezer
hibernate.c + snapshot.c + swap.c 休眠到磁盘完整实现
qos.c PM QoS 约束聚合
energy_model.c DVFS 能耗表(EAS/thermal)
autosleep.c / wakelock.c opportunistic sleep 与 Android 式唤醒锁

kernel/power/ 是 Linux 系统级电源管理 的中枢;RK3588 实际进深睡依赖 Rockchip 平台驱动与 ATF/PSCI,本目录提供标准流程与 sysfs 接口。设备级 runtime PM 请结合 drivers/base/power/ 与具体驱动 dev_pm_ops 一并理解。


附录:源文件清单

文件 行数
kernel/power/snapshot.c 2756
kernel/power/hibernate.c 1367
kernel/power/swap.c 1619
kernel/power/main.c 940
kernel/power/qos.c 678
kernel/power/suspend.c 648
kernel/power/energy_model.c 438
kernel/power/user.c 466
kernel/power/wakelock.c 285
kernel/power/process.c 235
kernel/power/suspend_test.c 219
kernel/power/console.c 152
kernel/power/autosleep.c 129
kernel/power/poweroff.c 45
kernel/power/power.h 328
kernel/power/Kconfig 354
kernel/power/Makefile 24
合计 ~10305

关联源码(目录外)

文件 说明
drivers/base/power/main.c 设备 PM 核心
kernel/freezer.c 任务 refrigerator
arch/arm64/kernel/suspend.c CPU suspend 汇编入口
drivers/soc/rockchip/ RK3588 平台 suspend 配置

kernel/printk 内核日志(printk)机制与原理详解

kernel/printk 内核日志(printk)机制与原理详解

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

该目录实现 Linux 内核 日志输出核心printk() / pr_info() 等宏的底层存储、环形缓冲区、控制台驱动调度、用户态读取接口(/dev/kmsgsyslog),以及 NMI/递归安全路径。Linux 6.1 采用 无锁 printk ringbuffer(prb) 替代早期单一 log_buf 方案,支持多读者、多写者并发。

定位:内核诊断与调试的「消息总线」;串口/UART 控制台驱动在 drivers/tty/serial/,通过 register_console() 接入本框架。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
obj-y += printk.o

obj-$(CONFIG_PRINTK) += printk_safe.o
obj-$(CONFIG_PRINTK) += printk_support.o
printk_support-y := printk_ringbuffer.o
printk_support-$(CONFIG_SYSCTL) += sysctl.o

obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) += braille.o
obj-$(CONFIG_PRINTK_INDEX) += index.o
配置项 说明
CONFIG_PRINTK 启用 printk(默认 y)
CONFIG_LOG_BUF_SHIFT log buffer 大小 = 1 << shift(默认 17 = 128KB)
CONFIG_PRINTK_TIME 日志带时间戳
CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER ARM 架构定时器时间源(RK 非 GKI)
CONFIG_PRINTK_CALLER 显示 caller pid/cpu
CONFIG_PRINTK_INDEX debugfs 导出 printk 格式索引
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT safe 模式备用 buffer
CONFIG_CONSOLE_LOGLEVEL_DEFAULT 控制台默认 loglevel
CONFIG_A11Y_BRAILLE_CONSOLE 盲文控制台

Kconfig 分散在 init/Kconfiglib/Kconfig.debug

1.2 源文件

文件 行数 功能
printk.c 4053 核心:存储、console、syslog、devkmsg、kmsg_dump
printk_ringbuffer.c 2124 无锁 ringbuffer 实现
printk_ringbuffer.h 384 prb 数据结构与 API
index.c 194 debugfs printk 格式索引
sysctl.c 85 /proc/sys/kernel/printk*
internal.h 60 内部 API、printk_safe 宏
printk_safe.c 47 NMI/递归上下文 vprintk 路由
braille.c 58 盲文控制台
console_cmdline.h 16 命令行 console 解析

相关头文件(目录外)

文件 功能
include/linux/printk.h printk() 宏、pr_* 系列
include/linux/console.h struct console、register_console
include/linux/kmsg_dump.h panic/oops 时 kmsg 转储
drivers/tty/serial/ UART 控制台(如 8250、DesignWare)

二、整体架构

2.1 数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
printk() / pr_info() / dev_printk()


vprintk() ──► [NMI/recursion?] ──► vprintk_deferred()
│ │
└─ vprintk_default() │ (仅写 ringbuffer)
│ │
▼ │
vprintk_emit() │
│ │
├── vprintk_store() ────┘ → prb_reserve / prb_commit
│ (写入 ringbuffer)

├── console_trylock_spinning()
│ console_unlock() → 各 console->write()

└── wake_up_klogd() → /dev/kmsg 读者

2.2 双阶段设计

阶段 说明
存储 所有消息写入 printk ringbuffer,带 seq、timestamp、level
输出 持有 console_sem 的线程从 prb 读取并调用 console->write()

存储与输出解耦:在 IRQ/NMI 中可安全写 buffer,控制台驱动稍后在进程上下文刷出。

2.3 启动阶段 buffer

1
2
3
4
早期: __log_buf[1 << CONFIG_LOG_BUF_SHIFT]  + printk_rb_static
setup_log_buf(early=1) — 启动极早阶段
后期: setup_log_buf(early=0) — 分配动态 prb,迁移 static 记录
prb = &printk_rb_dynamic

三、printk Ringbuffer(printk_ringbuffer.c)

3.1 结构

1
2
3
4
5
struct printk_ringbuffer {
struct prb_desc_ring desc_ring; /* 描述符 + printk_info 元数据 */
struct prb_data_ring text_data_ring; /* 文本数据环 */
atomic_long_t fail;
};

三个逻辑环

  1. desc_ringprb_desc(state_var + text_blk_lpos)+ printk_info(seq、ts、level、caller_id)
  2. text_data_ring:变长文本块,每块 [ID | padding | text]

3.2 描述符状态机

状态 含义
desc_reserved 写者正在写入
desc_committed 数据一致,写者可 reopen
desc_finalized 不可修改,读者可见
desc_reusable 已回收,可复用
desc_miss 读者查询 ID 不匹配(伪状态)

state_varID 与 state 打包atomic_long_t,实现无锁 CAS 更新。

3.3 写者 API

1
2
3
4
5
prb_rec_init_wr(&r, reserve_size);
prb_reserve(&e, prb, &r); /* 分配描述符+文本空间 */
/* 填充 r.text_buf, r.info */
prb_commit(&e); /* 提交,可 continuation */
prb_final_commit(&e); /* 提交并 finalize(带换行) */

LOG_CONT 续行prb_reserve_in_last() 追加到上一条未 finalize 的记录。

3.4 读者 API

1
2
3
prb_rec_init_rd(&r, &info, buf, size);
prb_read_valid(prb, seq, &r); /* 按 seq 读一条 */
prb_first_seq() / prb_next_seq() /* 迭代 */

四、日志写入路径(printk.c)

4.1 vprintk_store()

核心存储函数:

1
2
3
4
5
6
7
1. printk_enter_irqsave() — 递归计数 ≤ PRINTK_MAX_RECURSION(3)
2. ts_nsec = get_local_clock() — ARM 可用 arch_timer
3. vsnprintf 预估长度 → prb_reserve
4. printk_parse_prefix() — 解析 "<6>" level 与 "c" continuation
5. printk_sprint() — 格式化正文(syslog 前缀、device 信息等)
6. prb_final_commit() 或 prb_commit()
7. printk_exit_irqrestore()

4.2 vprintk_emit()

1
2
3
4
5
6
7
8
9
10
if (suppress_printk) return 0;
printed_len = vprintk_store(...);

if (!in_sched) {
preempt_disable();
if (console_trylock_spinning())
console_unlock(); /* 立即尝试刷 console */
preempt_enable();
wake_up_klogd();
}

LOGLEVEL_SCHED 路径 defer 控制台输出,避免调度器内死锁。

4.3 printk_parse_prefix()

解析内核日志前缀:

前缀 含义
<0><7> syslog level
<c> / <N c> continuation(续行)
KERN_* 编译期嵌入 level

4.4 console_printk[]

1
2
3
4
5
6
int console_printk[4] = {
CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel — 控制台可见最高 level */
MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */
CONSOLE_LOGLEVEL_MIN,
CONSOLE_LOGLEVEL_DEFAULT,
};

/proc/sys/kernel/printk 四个整数:console_logleveldefault_message_loglevelminimum_console_logleveldefault_console_loglevel

4.5 递归保护

1
2
3
4
5
#define PRINTK_MAX_RECURSION 3
static DEFINE_PER_CPU(u8, printk_count);
#ifdef CONFIG_HAVE_NMI
static DEFINE_PER_CPU(u8, printk_count_nmi);
#endif

NMI 与普通上下文 分开计数,防止 printk 触发 WARN 再次 printk 无限递归。


五、控制台输出(console)

5.1 struct console

1
2
3
4
5
6
7
8
9
10
struct console {
char name[16];
void (*write)(struct console *, const char *, unsigned);
int (*read)(struct console *, char *, unsigned);
struct device *dev;
u32 seq; /* 该 console 已输出到的 prb seq */
unsigned long dropped;
short flags;
/* CON_PRINTBUFFER, CON_BOOT, CON_CONSDEV, CON_BRL ... */
};

5.2 register_console()

1
2
3
4
5
6
1. 匹配 cmdline / DT / SPCR preferred console
2. 插入 console_drivers 链表(CON_CONSDEV 在头)
3. 若 CON_PRINTBUFFER:从 syslog_seq 重放历史
4. 否则:seq = prb_next_seq(prb)(仅新消息)
5. console_unlock() 刷出
6. 真实 console 注册后 unregister bootconsole

RK3588 典型路径:earlycon=uart8250,... → boot console → ttyS* 驱动 register_console()

5.3 console_lock 与 spinning

传统 console_sem 串行化控制台访问。优化路径:

1
2
3
4
5
console_trylock_spinning()
→ 若当前持有者 same CPU 且可 schedule
→ console_waiter = 1,自旋等待 handover
console_lock_spinning_enable/disable()
→ 减少 printk 持锁期间阻塞其他 CPU

5.4 console_emit_next_record()

1
2
3
4
5
prb_read_valid(prb, con->seq, &r)
suppress_message_printing(level) → 高于 console_loglevel 则 skip
record_print_text() / info_print_ext_header()
call_console_driver(con, text, len)
con->seq++

5.5 特殊场景

场景 行为
oops_in_progress 放宽 console 锁检查
suppress_panic_printk panic 时非 panic CPU 抑制 printk
suspend_console() 睡眠前停止 console 输出
console_flush_on_panic() panic 时强制刷所有 console

六、printk-safe 与递归保护

6.1 printk_safe.c

1
2
3
4
5
6
7
asmlinkage int vprintk(const char *fmt, va_list args)
{
if (this_cpu_read(printk_context) || in_nmi())
return vprintk_deferred(fmt, args); /* 只写 buffer,不调 console */

return vprintk_default(fmt, args);
}

printk_safe_enter_irqsave() / __printk_safe_enter() 递增 per-CPU printk_context,标记「安全模式」—— 控制台驱动可能持锁,不可再调 console。

6.2 vprintk_deferred

仅调用 vprintk_store(),不触发 console_unlock();由 defer_console_output() 或后续 printk 刷出。


七、用户态接口

7.1 /dev/kmsg

字符设备(devkmsg_read/write):

  • :按 seq 读 ringbuffer 记录(含 facility、level、timestamp)
  • :用户态注入 kernel 日志(需 SYSLOG capability)
  • poll:新消息唤醒

devkmsg_logon / off / ratelimitprintk.devkmsg= 内核参数可永久锁定)

7.2 syslog 系统调用

1
int do_syslog(int type, char __user *buf, int len, int source)
type 功能
SYSLOG_ACTION_READ 读 ringbuffer
SYSLOG_ACTION_READ_ALL 读全部(含已读)
SYSLOG_ACTION_READ_CLEAR 读并清除
SYSLOG_ACTION_CLEAR 清除
SYSLOG_ACTION_SIZE_UNREAD 未读字节数

dmesg_restrict:限制非特权用户读 kernel 日志。

7.3 kmsg_dump

1
2
kmsg_dump_register(struct kmsg_dumper *dumper);
kmsg_dump(KMSG_DUMP_OOPS / PANIC / ...);

panic/oops 时 pstore、mtdoops、ramoops 等通过 kmsg_dump_get_line() 导出 ringbuffer。


八、Sysctl 与调试(sysctl.c / index.c)

8.1 /proc/sys/kernel/

节点 说明
printk 四个 loglevel 整数
printk_ratelimit 速率限制间隔
printk_ratelimit_burst burst 计数
printk_delay 每条 printk 延迟 ms(调试)
printk_devkmsg on/off/ratelimit
dmesg_restrict 限制 dmesg 访问
kptr_restrict %pK 指针隐藏级别

8.2 PRINTK_INDEX(index.c)

debugfs:/sys/kernel/debug/printk/index/vmlinux(及模块名)

导出编译期 .printk_index 段:

1
2
# <level/flags> filename:line function "format"
<6> sched/core.c:1234 schedule "scheduling..."

便于日志分析与静态格式检索。

8.3 其他调试

机制 说明
ignore_loglevel 忽略 console_loglevel(cmdline)
pm_test 分阶段 suspend 测试(power 子系统)
suspend_stats debugfs 与 power 共享统计思路
printk_time 控制台/syslog 显示时间戳

九、RK3588/ARM64 平台说明

9.1 时间戳源

1
2
3
4
5
6
7
8
9
#ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER
static u64 get_local_clock(void)
{
/* 使用 ARM architected timer (CNTVCT_EL0 / arch_timer_read_counter) */
return ns_from_counter(arch_timer_read_counter(), cntfrq);
}
#else
static inline u64 get_local_clock(void) { return local_clock(); }
#endif

CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER 依赖 ARM_ARCH_TIMER && NO_GKI,RK 非 GKI 内核可选,使 printk 时间与 arch timer 一致(boot 阶段更稳定)。

9.2 控制台与 earlycon

RK3588 常用配置:

1
2
3
console=ttyFIQ0,1500000
earlycon=uart8250,mmio32,0xfeb50000
loglevel=8
组件 路径
FIQ debugger / early console drivers/tty/serial/rockchip_fiq_debugger.c
8250/DesignWare UART drivers/tty/serial/8250/
register_console() 驱动 probe 后注册

9.3 log buffer 大小

默认 CONFIG_LOG_BUF_SHIFT=17(128KB)。RK3588 嵌入式若日志量大,可增大至 18–20(256KB–1MB),通过 log_buf_len= 或 menuconfig。

9.4 多核与 big.LITTLE

  • caller_id:task 为 pid;非 task 上下文为 0x80000000 + cpu_id
  • per-CPU printk_count:各 CPU 独立递归计数
  • console spinning handover:减少 big.LITTLE 上高并发 printk 时的锁竞争
  • panic 抑制suppress_panic_printk 防止多核同时 printk 拖慢 panic 处理

9.5 常用调试命令

1
2
3
4
5
6
7
8
9
10
11
# 查看内核 ringbuffer
dmesg -w
dmesg -T # 人类可读时间(用户态转换)

# 实时跟踪
cat /dev/kmsg

# 调整控制台级别(立即生效)
echo 8 > /proc/sys/kernel/printk # 仅第一个整数

# 忽略 level 刷全部(需 cmdline ignore_loglevel 或 sysctl)

十、完整 printk 时序

console->writeconsole_unlockprintk_ringbuffervprintk_storevprintk_emit调用者console->writeconsole_unlockprintk_ringbuffervprintk_storevprintk_emit调用者pr_info("msg")vprintk_store()prb_reserve + prb_final_commitconsole_trylock_spinning()prb_read_valid(con->seq)con->write(text)wake_up_klogd()

NMI 路径vprintkvprintk_deferred → 仅 vprintk_store,不进入 Console 分支;稍后由普通 printk 或 defer_console_output() 刷出。


十一、总结

组件 职责
printk_ringbuffer.c 无锁 prb:reserve/commit/read,支持 continuation
printk.c 格式化、存储、console 调度、syslog/devkmsg、kmsg_dump
printk_safe.c NMI/递归上下文路由到 deferred 路径
sysctl.c loglevel、ratelimit、dmesg_restrict 等 sysctl
index.c debugfs 导出编译期 printk 格式

Linux 6.1 printk 以 ringbuffer 为中心,将「快速存储」与「慢速 console 输出」分离,在 SMP、NMI、panic 场景下保持可预测行为。RK3588 平台通过 UART/earlycon 接入 register_console(),可选 ARM arch timer 作为时间戳源。


附录:源文件清单

文件 行数
kernel/printk/printk.c 4053
kernel/printk/printk_ringbuffer.c 2124
kernel/printk/printk_ringbuffer.h 384
kernel/printk/index.c 194
kernel/printk/sysctl.c 85
kernel/printk/internal.h 60
kernel/printk/braille.c 58
kernel/printk/braille.h 56
kernel/printk/printk_safe.c 47
kernel/printk/console_cmdline.h 16
kernel/printk/Makefile 10
合计 ~7077

关联源码(目录外)

文件 说明
include/linux/printk.h 公共 printk 宏
include/linux/console.h console 驱动接口
lib/Kconfig.debug PRINTK_TIME 等选项
init/Kconfig LOG_BUF_SHIFT、PRINTK
drivers/tty/serial/*.c UART 控制台驱动

kernel/rcu 内核 RCU(Read-Copy Update)机制与原理详解

kernel/rcu 内核 RCU(Read-Copy Update)机制与原理详解

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

该目录实现 Linux 内核 Read-Copy Update(RCU) 子系统的核心逻辑:一种以读侧几乎零开销、写侧延迟回收为特征的同步机制。RCU 广泛用于链表遍历、路由表、模块卸载、内存释放(kfree_rcu)等场景。Linux 6.1 在 SMP 系统上默认启用 Tree RCU(层次化合并树),配合 分段回调链表(segcblist) 管理多 grace period 并发的 call_rcu() 回调。

定位:内核最基础的「读多写少」同步原语之一;公开 API 在 include/linux/rcupdate.hinclude/linux/srcu.h,架构相关内存屏障由 include/asm/ 通用头文件提供。


目录


一、RCU 基本概念

1.1 设计思想

RCU 将同步问题拆成三部分:

角色 行为 典型 API
读侧(Reader) 进入临界区后无锁访问共享数据;允许看到旧版本 rcu_read_lock() / rcu_dereference()
写侧(Updater) 复制-修改-发布新版本;旧版本不能立即释放 rcu_assign_pointer() + synchronize_rcu() / call_rcu()
回收(Reclaimer) 等所有读侧退出后再执行回调释放内存 call_rcu(head, func)

Grace Period(宽限期):从写侧发布更新到所有 CPU 上「曾经存在的读侧临界区」全部结束之间的时间窗口。只有 GP 结束后,旧数据才可安全回收。

1.2 RCU 变体对比

变体 配置 读侧临界区 典型用途
RCU-sched TREE_RCU + 非抢占 关抢占/关中断/软中断 传统内核路径
RCU-preempt PREEMPT_RCU rcu_read_lock() 可嵌套、可抢占 抢占内核
SRCU TREE_SRCU 可睡眠、每实例独立 文件系统、KVM
Tasks RCU TASKS_RCU* 基于任务调度状态 特殊场景(tracing 等)
Tiny RCU TINY_RCU UP 非抢占简化版 嵌入式单核

二、源码目录结构

2.1 编译依赖(Makefile)

1
2
3
4
5
6
7
8
9
10
obj-y += update.o sync.o
obj-$(CONFIG_TREE_SRCU) += srcutree.o
obj-$(CONFIG_TINY_SRCU) += srcutiny.o
obj-$(CONFIG_TREE_RCU) += tree.o
obj-$(CONFIG_TINY_RCU) += tiny.o
obj-$(CONFIG_RCU_NEED_SEGCBLIST) += rcu_segcblist.o
# 测试模块(默认不编译进内核)
obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o
obj-$(CONFIG_RCU_SCALE_TEST) += rcuscale.o
obj-$(CONFIG_RCU_REF_SCALE_TEST) += refscale.o
文件 作用
tree.c Tree RCU 主实现:GP kthread、QS 上报、call_rcu、synchronize_rcu
update.c 各 RCU 变体共享的更新侧逻辑、lockdep、expedited 模式
sync.c rcu_sync 结构:动态读写路径切换
rcu_segcblist.c 分段回调链表操作
srcutree.c 层次化 SRCU 实现
srcutiny.c 单核 SRCU
tiny.c 单核非抢占 Tiny RCU
tree.h / tree_plugin.h Tree RCU 内部结构与 PREEMPT/SCHED 插件
tree_nocb.h No-CBs CPU 卸载回调
tree_stall.h CPU stall 警告
tree_exp.h Expedited grace period
tasks.h Tasks RCU 通用框架
rcu.h GP 序列号(rcu_seq_*)等公共内联函数

2.2 头文件包含关系

1
2
3
4
5
include/linux/rcupdate.h     ← 公开 RCU API(读/写侧宏)
include/linux/rcu_segcblist.h ← 分段链表定义
include/linux/srcu.h ← SRCU 公开 API
kernel/rcu/rcu.h ← 内部 GP 序列号管理
kernel/rcu/tree.h ← rcu_node / rcu_data / rcu_state

三、Kconfig 与 RK3588 默认配置

3.1 关键 Kconfig 选项

配置项 默认 说明
CONFIG_TREE_RCU SMP=y 层次化 Tree RCU
CONFIG_PREEMPT_RCU PREEMPTION=y 抢占 RCU(需内核可抢占)
CONFIG_TINY_RCU !SMP && !PREEMPTION 单核简化版
CONFIG_TREE_SRCU SRCU && !TINY_RCU 完整 SRCU
CONFIG_RCU_FANOUT 64(64 位) 合并树扇出
CONFIG_RCU_FANOUT_LEAF 16 叶节点扇出
CONFIG_RCU_NOCB_CPU Expert/NO_HZ_FULL 卸载回调到 kthread
CONFIG_RCU_BOOST PREEMPT_RT 优先级提升防 GP 阻塞

3.2 RK3588 Rockchip defconfig 特点

arch/arm64/configs/rockchip_linux_defconfig 为例:

1
2
3
4
CONFIG_PREEMPT_VOLUNTARY=y      # 自愿抢占,非 PREEMPT_RCU
CONFIG_RCU_EXPERT=y
CONFIG_RCU_NOCB_CPU=y # 支持将回调卸载到 rcuo kthread
CONFIG_RCU_CPU_STALL_TIMEOUT=60

含义

  • RK3588 使用 RCU-schedTREE_RCU),读侧临界区由 关抢占/关中断/软中断 界定,而非 rcu_read_lock() 嵌套计数(那是 PREEMPT_RCU 路径)。
  • 启用了 RCU_EXPERTRCU_NOCB_CPU,可通过启动参数 rcu_nocbs=CPU列表 将指定 CPU 的 RCU 回调卸载,降低 HPC/实时负载的 OS jitter。
  • 8 核(4+4 big.LITTLE)下 Tree RCU 通常为 两层合并树(64 扇出 >> 8 CPU)。

四、整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                 ┌─────────────────────────────────────┐
│ rcu_gp_kthread │
│ GP 初始化 → FQS 强制 QS → GP 清理 │
└──────────────┬──────────────────────┘

┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ rcu_node │◄──parent───│ rcu_node │◄──parent───│ rcu_node │ (合并树)
│ (root) │ │ (internal)│ │ (leaf) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ qsmask │ │ qsmask
│ │ ▼
│ │ ┌───────────┐
│ │ │ rcu_data │ (per-CPU)
│ │ │ cblist │
│ │ └───────────┘
▼ ▼
grace period 完成判定 quiescent state 逐级上报

Per-CPU 路径

  1. 读侧:rcu_read_lock() → 访问 RCU 保护数据 → rcu_read_unlock()
  2. 写侧:call_rcu() 将回调入队到本 CPU rcu_data.cblist
  3. GP 线程检测所有 CPU 均报告 QS → 推进 gp_seq → 将对应段回调移至 DONE → rcuc kthread / softirq 执行回调

五、核心数据结构

5.1 struct rcu_state(全局状态)

位于 tree.c,包含:

  • gp_seq:grace period 序列号(rcu_seq_* 管理)
  • gp_flags / gp_wq:GP kthread 唤醒标志与等待队列
  • node[]:合并树节点数组
  • expedited_sequence:加速 GP 序列号

5.2 struct rcu_node(合并树节点)

1
2
3
4
5
6
7
8
9
10
11
struct rcu_node {
raw_spinlock_t lock;
unsigned long gp_seq;
unsigned long qsmask; /* 尚未报告 QS 的 CPU/子节点位图 */
unsigned long qsmaskinit; /* 本 GP 初始 online CPU 掩码 */
int grplo, grphi; /* 管辖 CPU 范围 */
u8 level; /* 0=root */
struct rcu_node *parent;
struct list_head blkd_tasks; /* PREEMPT_RCU:阻塞 GP 的任务 */
// ... boost、expedited、nocb 等字段
};

Leaf 节点每位对应一个 rcu_data;非叶节点每位对应一个子 rcu_node。QS 从叶向根逐级清除 qsmask 位,根节点 qsmask==0 时 GP 完成。

5.3 struct rcu_data(Per-CPU)

1
2
3
4
5
6
7
8
9
10
struct rcu_data {
unsigned long gp_seq;
union rcu_noqs cpu_no_qs;
bool core_needs_qs;
struct rcu_node *mynode;
unsigned long grpmask;
struct rcu_segcblist cblist; /* 分段回调链表 */
long blimit; /* 单次批量处理回调上限 */
// dynticks、expedited、nocb 等
};

定义于 tree.h,每 CPU 一份 DEFINE_PER_CPU(struct rcu_data, rcu_data)

5.4 GP 序列号(rcu.h

1
2
3
4
5
6
7
#define RCU_SEQ_CTR_SHIFT  2
#define RCU_SEQ_STATE_MASK ((1 << RCU_SEQ_CTR_SHIFT) - 1)

static inline void rcu_seq_start(unsigned long *sp); /* GP 开始 */
static inline void rcu_seq_end(unsigned long *sp); /* GP 结束 */
static inline unsigned long rcu_seq_snap(unsigned long *sp); /* 快照 */
static inline bool rcu_seq_done(unsigned long *sp, unsigned long s);

低 2 bit 为状态位,高位为计数器;rcu_seq_snap() 返回「下一次 GP 完成」对应的序列值,供 poll API 使用。


六、Grace Period 生命周期

GP 由 rcu_gp_kthread 驱动(tree.c),主循环三阶段:

6.1 启动 GP(rcu_gp_init

  1. 检查 rcu_state.gp_flags & RCU_GP_FLAG_INIT
  2. rcu_seq_start(&rcu_state.gp_seq) 递增序列号
  3. 处理 CPU hotplug 缓冲的 online/offline 变更
  4. 广度优先遍历 rcu_node 树,设置 qsmask = qsmaskinit
  5. 离线 CPU 直接报告 QS(rcu_report_qs_rnp

6.2 强制 Quiescent State(rcu_gp_fqs_loop

若 GP 长时间未完成:

  • 调度器 tick / idle 入口报告 QS(rcu_check_callbacks
  • rcu_force_quiescent_state() 向其他 CPU 发 IPI 或唤醒
  • PREEMPT_RCU:rcu_preempt_check_blocked_tasks 处理阻塞读侧

6.3 结束 GP(rcu_gp_cleanup

  1. 根节点 qsmask == 0rcu_report_qs_rsp() 唤醒 kthread
  2. rcu_seq_end(&rcu_state.gp_seq)
  3. cblist 中 WAIT 段回调移至 DONE 段
  4. 唤醒 rcuc kthread 或触发 RCU_SOFTIRQ 执行回调
  5. 若仍有 pending 回调,设置 RCU_GP_FLAG_INIT 启动下一轮 GP

6.4 Quiescent State 上报(rcu_report_qs_rnp

1
2
3
4
5
6
7
8
9
10
11
12
13
static void rcu_report_qs_rnp(unsigned long mask, struct rcu_node *rnp,
unsigned long gps, unsigned long flags)
{
for (;;) {
rnp->qsmask &= ~mask; /* 清除本节点 QS 位 */
if (rnp->qsmask || rnp->gp_seq != gps)
break;
mask = rnp->grpmask;
rnp = rnp->parent; /* 向父节点传播 */
}
if (!rnp) /* 到达 root 且 qsmask==0 */
rcu_report_qs_rsp(flags);
}

七、读侧与写侧 API

7.1 读侧(include/linux/rcupdate.h

PREEMPT_RCUCONFIG_PREEMPT_RCU):

1
2
3
4
void __rcu_read_lock(void);   /* current->rcu_read_lock_nesting++ */
void __rcu_read_unlock(void);
#define rcu_read_lock() __rcu_read_lock()
#define rcu_read_unlock() __rcu_read_unlock()

RCU-sched(RK3588 默认路径):

1
2
static inline void __rcu_read_lock(void) { preempt_disable(); }
static inline void __rcu_read_unlock(void) { preempt_enable(); }

此外,关中断、关软中断、NMI 处理程序 也隐式构成 RCU 读侧临界区。

访问 RCU 保护指针

1
2
#define rcu_dereference(p) rcu_dereference_check(p, 0)
#define rcu_assign_pointer(p, v) smp_store_release(&p, v)

7.2 写侧同步

API 行为
synchronize_rcu() 阻塞直到 GP 完成
call_rcu(head, func) 异步 GP 后调用 func
kfree_call_rcu(head, func) 专用内存释放回调
get_state_synchronize_rcu() 非阻塞快照
poll_state_synchronize_rcu(old) 轮询 GP 是否完成
cond_synchronize_rcu(old) 仅在必要时等待

synchronize_rcu() 核心逻辑(tree.c):

1
2
3
4
5
6
7
8
9
10
11
12
void synchronize_rcu(void)
{
if (!rcu_blocking_is_gp()) {
if (rcu_gp_is_expedited())
synchronize_rcu_expedited();
else
wait_rcu_gp(call_rcu);
return;
}
/* 单 CPU 早期启动阶段:简化为内存屏障 + gp_seq 递增 */
...
}

八、call_rcu 与回调管理

8.1 入队流程

1
2
3
4
5
6
7
8
9
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
head->func = func;
local_irq_save(flags);
rdp = this_cpu_ptr(&rcu_data);
rcu_segcblist_enqueue(&rdp->cblist, head);
__call_rcu_core(rdp, head, flags); /* 必要时强制 GP */
local_irq_restore(flags);
}

8.2 回调执行

  • 默认RCU_SOFTIRQ 或 per-CPU rcuc kthread
  • CONFIG_RCU_NOCB_CPU:offloaded CPU 由 rcuop/N kthread 执行
  • blimit:单次处理回调数量上限,防止长时间占用 CPU

8.3 过载检测

当 per-CPU 回调数超过 qhimark 时,__call_rcu_core() 调用 rcu_force_quiescent_state() 加速 GP;叶节点 cbovldmask 标记过载 CPU。


九、分段回调链表(rcu_segcblist)

rcu_segcblist 将回调按 GP 阶段分为 4 段(include/linux/rcu_segcblist.h):

含义
DONE RCU_DONE_TAIL GP 已完成,可立即调用
WAIT RCU_WAIT_TAIL 等待当前 GP
NEXT_READY RCU_NEXT_READY_TAIL 下一 GP 已确定
NEXT RCU_NEXT_TAIL 新入队,GP 未启动
1
2
head → [DONE...] → [WAIT...] → [NEXT_READY...] → [NEXT...] → NULL
↑tails[0] ↑tails[1] ↑tails[2] ↑tails[3]

GP 结束时,rcu_segcblist_advance() 将 WAIT 段推进到 DONE;rcu_segcblist_accelerate() 在 GP 提前启动时推进 NEXT_READY。


十、update.c 公共逻辑

update.c 被所有 RCU 变体链入(obj-y),主要提供:

功能 说明
rcu_gp_is_normal() / rcu_gp_is_expedited() 运行时 GP 模式(rcu_normal / rcu_expedited 模块参数)
rcu_read_lock_sched_held() lockdep 检查
__wait_rcu_gp() 批量等待多个 GP
finish_rcuwait() GP 等待辅助
debugobjects init_rcu_head() / destroy_rcu_head()
rcu_set_runtime_mode() 启动后切换 expedited/normal 模式

启动参数(rcupdate. 前缀):

  • rcu_expedited=1:所有 GP 走 expedited 路径
  • rcu_normal=1:强制 normal GP
  • rcu_normal_after_boot=1:PREEMPT_RT 默认启动后用 normal GP

十一、rcu_sync 轻量读写切换(sync.c)

struct rcu_sync 提供 动态 fastpath/slowpath 切换,常用于 seqlock 式优化:

1
2
3
void rcu_sync_enter(struct rcu_sync *rsp);  /* 读者走 slowpath */
void rcu_sync_exit(struct rcu_sync *rsp); /* GP 后恢复 fastpath */
bool rcu_sync_is_idle(struct rcu_sync *rsp);

状态机:GP_IDLE → GP_ENTER → GP_PASSED → GP_EXIT → GP_IDLE

  • rcu_sync_enter() 首次调用时 synchronize_rcu() 同步等待
  • 后续通过 call_rcu(rcu_sync_func) 异步完成 GP 转换
  • 紧密连续的 enter/exit 可合并 GP,避免多余等待

十二、SRCU 可睡眠 RCU(srcutree.c)

SRCU(Sleepable RCU)允许在 读侧临界区内睡眠,每个 srcu_struct 实例独立。

12.1 核心 API

1
2
3
4
5
6
int idx = srcu_read_lock(ssp);
... /* 可阻塞 */
srcu_read_unlock(ssp, idx);

synchronize_srcu(ssp);
call_srcu(ssp, head, func);

12.2 实现要点

  • 使用 per-CPU 计数器数组srcu_lock_count / srcu_unlock_count)跟踪读侧
  • 两个计数器组交替使用(类似 classic RCU 的双缓冲)
  • 层次化 srcu_node 树(与 Tree RCU 类似)合并 QS
  • srcu_size_big:CPU 数 ≥ 128 或 contention 高时切换「大」实现

RK3588 上 SRCU 被 KVMarch/arm64/kvm/Kconfig select SRCU)、文件系统等模块使用。


十三、Tasks RCU(tasks.h)

Tasks RCU 不依赖 per-CPU QS,而是通过 扫描任务状态 判断 GP:

变体 配置 QS 条件
Tasks RCU TASKS_RCU voluntary context switch、idle、用户态
Tasks Rude RCU TASKS_RUDE_RCU 含抢占式 context switch
Tasks Trace RCU TASKS_TRACE_RCU rcu_read_lock_trace() 标记

通用框架 struct rcu_tasks 定义 GP 函数、回调队列、IPI 统计等;默认 不启用,需 RCU_EXPERT 强制开启。


十四、Expedited GP 与 Poll API

14.1 Expedited Grace Period

正常 GP 等待自然 tick/idle QS;Expedited GP 主动 IPI 所有 CPU 强制 QS,延迟更低但开销更大。

  • 实现于 tree_exp.h
  • synchronize_rcu_expedited() / rcu_gp_is_expedited()
  • PREEMPT_RT 内核默认 rcu_normal_after_boot=1 禁用 expedited

14.2 Poll 系列 API

适用于 避免阻塞 的场景(如自旋锁内不能睡眠):

1
2
3
4
5
6
unsigned long s = get_state_synchronize_rcu();
/* ... 做其他工作 ... */
if (poll_state_synchronize_rcu(s))
/* GP 已完成 */;
else
cond_synchronize_rcu(s); /* 必要时才阻塞 */

十五、RCU CPU Stall 检测

实现于 tree_stall.hCONFIG_RCU_STALL_COMMON):

  • 若某 CPU 在 GP 期间长时间未报告 QS,触发 stall 警告
  • RK3588 defconfig:CONFIG_RCU_CPU_STALL_TIMEOUT=60(60 秒)
  • 可通过 rcu_cpu_stall_timeout 启动参数调整
  • stall 信息包含 GP 序列号、阻塞任务栈、nocb 状态等

常见 stall 原因:CPU 关中断过长、PREEMPT_RCU 读侧阻塞、驱动 bug。


十六、RCU_NOCB_CPU 与 Boost

16.1 RCU_NOCB_CPU

tree_nocb.h 实现:指定 CPU 的 RCU 回调由 rcuop/N kthread 执行,本 CPU 仅入队。

  • 启动参数:rcu_nocbs=0-3(卸载 CPU 0–3)
  • Rockchip defconfig 启用 CONFIG_RCU_NOCB_CPU,适合 big.LITTLE 上将回调卸载到 little 核
  • 代价:call_rcu() 开销增加、可能增加 context switch

16.2 RCU_BOOST

CONFIG_RCU_BOOST(PREEMPT_RCU + RT_MUTEXES):当高优先级任务被 RCU 读侧阻塞时,提升读侧任务优先级,防止 GP 饥饿。RK3588 自愿抢占配置下通常 不启用 PREEMPT_RCU,故 RCU_BOOST 不适用。


十七、RK3588/ARM64 平台说明

17.1 配置总结

项目 RK3588 典型值
RCU 变体 TREE_RCU + RCU-sched
内核抢占 PREEMPT_VOLUNTARY(非 PREEMPT_RCU)
CPU 数 8(合并树 1–2 层)
RCU_FANOUT 64(64 位默认)
RCU_NOCB_CPU 已启用(Expert)
Stall 超时 60 秒
SRCU 启用(KVM 等依赖)

17.2 内存序

ARM64 为 弱内存序 架构。RCU 通过以下保证可见性:

  • 读侧:rcu_dereference()READ_ONCE + 编译屏障
  • 写侧:rcu_assign_pointer() 使用 smp_store_release()
  • GP 完成:call_rcu() 回调前,各 CPU 至少执行一次 full memory barrier

详见内核文档 Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst

17.3 big.LITTLE 注意点

  • A76/A55 共享同一 RCU 合并树;GP 需 所有 online CPU 报告 QS
  • CPU hotplug(core off/on)由 rcu_cpu_notify 处理,更新 qsmaskinit
  • 若使用 rcu_nocbs 将回调卸载到 A55,需注意 kthread 亲和性与 cache 一致性
  • idle 状态:context_tracking 将 idle 视为 extended quiescent state,A55 深 idle 不影响 GP

17.4 相关架构选项

  • CONFIG_MMU_GATHER_RCU_TABLE_FREE(arm64 Kconfig):页表释放走 call_rcu
  • KVM 模块 select SRCU

十八、完整 Grace Period 时序

rcuc/softirqrcu_gp_kthreadrcu_data (per-CPU)读侧 (rcu_read_lock)写侧 (call_rcu)rcuc/softirqrcu_gp_kthreadrcu_data (per-CPU)读侧 (rcu_read_lock)写侧 (call_rcu)等待所有 CPU QSrcu_segcblist_enqueue(NEXT段)触发 GP 启动 (gp_flags)rcu_gp_init() 设置 qsmasktick/idle 报告 QSrcu_report_qs_rnp 向上合并rcu_gp_cleanup() gp_seq_endadvance cblist WAIT→DONE唤醒回调执行head->func() 释放旧数据

十九、总结

要点 说明
读侧零锁 RCU 读路径无原子操作,仅关抢占/关中断
延迟回收 写侧通过 GP + 回调异步释放,换取读侧性能
Tree 合并 rcu_node 层次树将 8~数千 CPU 的 QS 合并为 O(log N)
segcblist 分段链表支持多 GP 并发、批量回调
RK3588 RCU-sched + 可选 NOCB 卸载;8 核 big.LITTLE 默认两层树
扩展 SRCU(可睡眠)、Tasks RCU(任务扫描)、rcu_sync(读写切换)

RCU 是 Linux 内核基础设施,networking、VFS、module、mm 等子系统大量依赖本目录实现。调试 RCU 问题时可关注:rcu_cpu_stalltrace/events/rcu.h 追踪点、以及 rcutorture 压力测试模块。


附录:源文件清单

文件 行数 说明
tree.c 4886 Tree RCU 主实现
tasks.h 1962 Tasks RCU 框架
tree_nocb.h 1590 No-CBs CPU 卸载
tree_plugin.h 1304 PREEMPT/SCHED 插件
tree_exp.h 1122 Expedited GP
tree_stall.h 1045 CPU stall 检测
rcuscale.c 924 RCU 规模测试
refscale.c 897 RCU 引用规模测试
srcutree.c 1854 Tree SRCU
rcutorture.c 3654 RCU torture 测试
rcu.h 599 GP 序列号等公共定义
rcu_segcblist.c 633 分段回调链表
update.c 628 公共更新侧逻辑
tree.h 489 内部数据结构
srcutiny.c 273 Tiny SRCU
tiny.c 267 Tiny RCU
sync.c 206 rcu_sync
rcu_segcblist.h 153 segcblist 内联辅助
合计 22486

行数统计:Linux 6.1,wc -l kernel/rcu/*.{c,h}