在这里插入图片描述

[toc]

[kernel/panic.c] [内核 Panic/Oops 终止路径] [在不可恢复错误时完成“停止系统/通知/转储/重启”等收尾流程]

介绍

kernel/panic.c 主要实现内核在致命错误时的统一收尾路径:

  • panic():不可继续运行时进入的终止流程(通常不返回)。
  • 与 Oops/BUG/WARN 的策略联动:例如 “Oops 是否升级为 panic”、是否自动重启、是否触发 kdump 等。
  • 提供 panic notifier、kmsg dump、停止其它 CPU、控制台输出解锁(bust spinlocks)等机制,尽量在系统已不稳定时仍完成关键动作。

历史与背景

诞生解决的问题

  • 内核遇到不可恢复错误时,继续运行可能导致数据破坏扩大;需要统一路径完成:

    • 记录信息(console/printk、dumpers)
    • 通知相关子系统(notifier)
    • 尝试转储(kdump/kmsg_dump/pstore 等)
    • 停止并发扰动(停其它 CPU/禁止中断)
    • 最终动作(停机/重启)

重要演进方向(你读文件时可重点关注的“常见结构”)

  • 从“只打印并死循环”演进到“可配置行为”:

    • panic 超时自动重启(panic_timeout)
    • oops/warn 升级为 panic 的策略(panic_on_oops / panic_on_warn)
    • 与 kexec/kdump 的联动(crash_kexec)
    • 更可控的输出与 dump(panic_print / kmsg_dump 等)

主流应用情况

  • 几乎所有架构与产品都依赖这条路径:服务器依赖 kdump,嵌入式常依赖 watchdog/自动重启与 pstore/ramoops 留痕。

核心原理与设计

核心流程(建议你按源码把每一步打上编号)

通常 panic() 会按以下“阶段化”组织(不同内核版本细节会略有差异):

  1. 进入不可恢复状态
  • 记录当前 CPU/状态,设置全局“已在 panic 中”的标志,避免递归/并发重复进入。
  • 关闭抢占/中断或进入更严格的上下文控制,尽量减少系统继续扰动。
  1. 保证关键输出尽可能可用
  • 触发类似 bust_spinlocks 的机制:当系统可能死在 console/锁上时,尝试“放宽锁保护”以便把 panic 信息打出去(代价是输出时序/一致性不再严格)。
  • 输出 panic 信息、调用栈、寄存器摘要等(其中部分信息来自其它文件,但 panic.c 负责组织终止阶段的打印策略)。
  1. 通知链(panic notifier)
  • 调用 panic notifier 链,让注册者做最后动作(例如:记录额外日志、切换硬件状态、触发特定 dump、点灯/蜂鸣等)。
  • 这一步常被产品化用来做“最后的板级动作”。
  1. dump / 转储
  • 触发 kmsg_dump(把日志交给不同 dumper,如 pstore/ramoops 等实现方)。
  • 如配置了 kdump:调用 crash_kexec 进入 crash kernel 做内存转储。
  1. 停止并发(尤其 SMP)
  • 尝试停止其它 CPU,避免其它核继续写内存/IO,降低 dump 过程中数据继续变化。
  1. 最终处置
  • 如果设置了 panic_timeout>0:等待超时后重启(或走架构相关重启路径)。
  • 否则通常进入死循环/停机等待外部看门狗复位(取决于配置与平台)。

主要优势

  • 统一终止语义:所有致命路径最终汇聚到明确的收尾逻辑。
  • 可配置策略:是否重启、是否升级 oops/warn、是否触发 kdump 等可由参数控制。
  • 可扩展:panic notifier 与 kmsg_dump 让平台/产品能在最后阶段插入必要动作。

局限性与不适用点

  • panic 环境不可信:锁、内存分配、调度、IO 都可能不可靠;因此很多代码必须避免睡眠与复杂依赖。
  • 输出不保证完整:console 可能卡死或丢日志;bust spinlocks 只是降低“完全打不出”的概率。
  • 并发与递归风险:NMI、双重故障、再次触发 panic 时,很多路径只能“尽力而为”。

使用场景

首选场景(它在系统中承担的角色)

  • 内核检测到不可恢复错误(内存破坏、严重 BUG、关键子系统不可继续)。
  • 产品希望:快速复位 + 留存最小诊断信息(串口日志/pstore/核心转储)。

不推荐“依赖 panic 解决”的场景

  • 把 panic 当作“正常错误处理机制”是不合适的:panic 是终止流程,不是恢复机制。
  • 如果你只是想在异常时收集信息但继续运行,应考虑更上层的容错(比如局部重启、隔离、降级),而不是依赖 panic。

对比分析

panic vs oops vs BUG vs WARN(从行为与代价角度)

  • WARN:提示性告警,通常继续运行;可配置升级为 panic(用于把“疑似内存破坏前兆”变成快速复位策略)。
  • BUG:通常意味着逻辑不可接受,常导致 oops 或直接触发更严重路径。
  • Oops:发生严重异常但内核可能尝试继续(风险是继续运行导致二次破坏);可配置 panic_on_oops 直接升级为 panic。
  • panic:明确终止系统运行;更倾向“止损 + 诊断 + 重启/停机”。

对嵌入式产品常见策略:

  • 关键设备:panic_on_oops=1 + 合理 panic_timeout + watchdog,保证快速恢复;
  • 研发阶段:保留更长日志与更完整 dump(pstore/ramoops/kdump),降低复现难度。

总结

关键特性

  • panic() 组织“打印/通知/dump/停核/重启”的终止流程。
  • 通过参数控制“是否自动重启、是否把 oops/warn 升级为 panic”等策略。
  • panic notifier 与 kmsg_dump 为平台/产品留出最后阶段扩展点。

vpanic / panic:内核进入不可恢复致命路径时的统一收敛点(输出诊断、调用 panic 通知链、可选 kdump/重启/最终停机)


vpanic:核心致命路径(禁止中断/抢占、打印、通知链、dump、可选重启、最终自旋/闪烁)

  1. 不可睡眠上下文收敛:先关本地中断与抢占,避免在 panic 期间被调度打断或被中断处理再次触发 panic。
  2. 避免递归放大panic_on_warn 在当前执行流上被清零,用于防止 panic 路径中再次触发 WARN 导致递归进入 panic。
  3. 最小依赖打印:使用静态缓冲区 buf[1024],避免 panic 阶段依赖动态内存分配。
  4. panic 通知链:通过 atomic_notifier_call_chain(&panic_notifier_list, ...) 调用已注册的“原子回调”,让关键子系统补充信息或切换到更安全状态(例如你前面看到的 heartbeat 触发器置位停止标志)。
  5. 不依赖常规定时器的延迟/闪烁:panic 后常规定时器与调度时序不可信,因此重启前等待使用 mdelay() 的忙等循环,并通过 panic_blink() 进行LED闪烁提示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* Stop ourself in panic -- architecture code may override this
*/
void __weak __noreturn panic_smp_self_stop(void)
{
while (1)
cpu_relax();
}


/**
* @brief vpanic:进入不可恢复致命路径并收敛系统状态(不返回)。
* @param fmt 格式化字符串。
* @param args 可变参数列表。
*
* 该函数面向“系统状态不可信”的阶段:优先保证不再睡眠/不再被调度打断,
* 尽可能输出诊断信息,执行 panic 通知链与必要的 dump,最后重启或永久停机。
*/
void vpanic(const char *fmt, va_list args)
{
static char buf[1024]; /**< 静态缓冲区:避免 panic 阶段依赖 kmalloc 等动态分配。 */
long i, i_next = 0, len;
int state = 0;
bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers; /**< 控制 kdump 在通知链之前/之后执行的策略快照。 */

if (panic_on_warn) {
/**
* @note 防递归技巧:panic 路径中可能再次触发 WARN。
* 清零 panic_on_warn 可以避免当前执行流在 panic 过程中再次因 WARN 进入 panic。
*/
panic_on_warn = 0;
}

/**
* @note 关键约束:panic 阶段禁止本地中断与抢占,降低再入与死锁风险。
* 在 ARMv7-M 上通常对应屏蔽可屏蔽中断与禁止内核抢占。
*/
local_irq_disable();
preempt_disable_notrace();

/**
* @note 进入资格控制:只允许一个执行流进入 panic 核心区。
* 单核配置下主要用于防止“递归 panic/并发再入”,而不是解决多核并行。
*/
if (panic_try_start()) {
/* 允许继续执行 */
} else if (panic_on_other_cpu())
panic_smp_self_stop(); /**< 多核时用于自停;单核构建通常退化或不启用该路径。 */

console_verbose();
bust_spinlocks(1); /**< 解除可能阻塞控制台输出的锁/状态,优先保证 panic 信息可见。 */
len = vscnprintf(buf, sizeof(buf), fmt, args); /**< 写入静态缓冲区,避免溢出。 */

if (len && buf[len - 1] == '\n')
buf[len - 1] = '\0';

pr_emerg("Kernel panic - not syncing: %s\n", buf);

/**
* @note 防止重复堆栈转储:若已经处于 oops/panic 相关流程,避免再次 dump_stack 造成噪声与风险。
*/
if (test_taint(TAINT_DIE) || oops_in_progress > 1) {
panic_this_cpu_backtrace_printed = true;
} else if (IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE)) {
dump_stack();
panic_this_cpu_backtrace_printed = true;
}

kgdb_panic(buf); /**< 若启用 KGDB,panic 前给调试入口一个机会;多数嵌入式裁剪配置会编译掉。 */

/**
* @note kdump 执行时机控制:
* - 若配置为“通知链之前执行”,此处直接进入 crash kexec。
* - 否则先走通知链与 dump,后续再进入 crash kexec。
*/
if (!_crash_kexec_post_notifiers)
__crash_kexec(NULL);

panic_other_cpus_shutdown(_crash_kexec_post_notifiers); /**< 多核停其他 CPU;单核配置通常为空或简化。 */

printk_legacy_allow_panic_sync();

/**
* @brief 调用 panic 原子通知链。
* @note 这是 panic_notifier_list 的实际触发点:回调应避免睡眠与复杂依赖。
*/
atomic_notifier_call_chain(&panic_notifier_list, 0, buf);

sys_info(panic_print);
kmsg_dump_desc(KMSG_DUMP_PANIC, buf);

if (_crash_kexec_post_notifiers)
__crash_kexec(NULL);

console_unblank();

/**
* @note 控制台缓冲刷新:panic 可能打断正常的 printk/console 提交流程,
* 这里尽量强制把重要信息刷出。
*/
debug_locks_off();
console_flush_on_panic(CONSOLE_FLUSH_PENDING);

if ((panic_print & SYS_INFO_PANIC_CONSOLE_REPLAY) ||
panic_console_replay)
console_flush_on_panic(CONSOLE_REPLAY_ALL);

if (!panic_blink)
panic_blink = no_blink; /**< 若平台未提供 blink 回调,用空实现保证流程可继续。 */

if (panic_timeout > 0) {
/**
* @note panic 后延时不使用常规定时器:
* panic 阶段定时器子系统可能不可信,因此用 mdelay 忙等。
*/
pr_emerg("Rebooting in %d seconds..\n", panic_timeout);

for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
touch_nmi_watchdog();
if (i >= i_next) {
i += panic_blink(state ^= 1); /**< 通过平台 blink 回调闪烁(常用于 LED)。 */
i_next = i + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
}

if (panic_timeout != 0) {
/**
* @note 非干净重启:panic 状态下无法保证完整关机序列,
* 这里只尝试尽可能触发紧急重启。
*/
if (panic_reboot_mode != REBOOT_UNDEFINED)
reboot_mode = panic_reboot_mode;
emergency_restart();
}

#if defined(CONFIG_S390)
disabled_wait();
#endif

pr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf);

suppress_printk = 1; /**< 抑制后续滚屏:优先保留已输出的重要 panic 信息可读性。 */

console_flush_on_panic(CONSOLE_FLUSH_PENDING);
nbcon_atomic_flush_unsafe(); /**< 强制刷新 nbcon 原子通道,适配某些延迟打印场景。 */

local_irq_enable(); /**< 进入最终循环前打开中断(实现细节依赖体系结构与配置)。 */

/**
* @brief 最终停机循环:周期性喂 watchdog 并按 blink 策略闪烁。
* @note 该循环不返回,作为 panic 的最终收敛状态。
*/
for (i = 0; ; i += PANIC_TIMER_STEP) {
touch_softlockup_watchdog();
if (i >= i_next) {
i += panic_blink(state ^= 1);
i_next = i + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
}
EXPORT_SYMBOL(vpanic);

panic:可变参数包装,转入 vpanic

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief panic:可变参数版本,最终进入 vpanic(不返回)。
* @param fmt 格式化字符串。
*/
void panic(const char *fmt, ...)
{
va_list args;

va_start(args, fmt);
vpanic(fmt, args);
va_end(args);
}
EXPORT_SYMBOL(panic);

panic_try_start / panic_reset / panic_in_progress / panic_on_this_cpu / panic_on_other_cpu:panic 进入资格与并发态判定

panic_cpu:panic 状态的全局发布点

1
2
3
4
5
6
7
8
/**
* @brief 记录当前正在执行 panic 路径的 CPU 编号;若为 PANIC_CPU_INVALID 表示未进入 panic。
*
* 设计要点:
* - 在 SMP 下用于选举“panic 主 CPU”,避免多个 CPU 并行进入 panic/崩溃转储路径互相干扰。
* - 在单核下主要用于发布“panic 已开始”的全局状态,供其他分支快速判定。
*/
atomic_t panic_cpu = ATOMIC_INIT(PANIC_CPU_INVALID);

panic_try_start:尝试成为“panic 主执行 CPU”

作用与实现原理

该函数使用 atomic_try_cmpxchg()panic_cpuPANIC_CPU_INVALID 原子地改写为当前 CPU 号。

  • 成功:表示当前执行流成为“第一个宣布 panic 的 CPU”。
  • 失败:表示此前已有 CPU(或同一 CPU 的某个更早路径,例如 NMI/异常路径)宣布进入 panic。

关键技巧是:用 CAS(比较并交换) 实现一次性“资格获取”,从而让 panic 与 crash_kexec 之类路径共享同一互斥条件,避免并行执行导致的相互停顿或资源争用。

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
/**
* @brief 尝试启动 panic 关键区(获取 panic 主执行资格)。
*
* @return true 表示成功把 panic_cpu 从无效值更新为当前 CPU 编号;
* false 表示 panic 已由其他 CPU 或更早路径宣布开始。
*/
bool panic_try_start(void)
{
int old_cpu, this_cpu;

/**
* 使用同一个 panic_cpu 仲裁 panic 与 crash_kexec 等崩溃相关路径,
* 在 SMP 下避免并行进入造成互相停止或资源竞争。
*/
old_cpu = PANIC_CPU_INVALID; /**< 期望的旧值:仅当尚未进入 panic 才允许写入。 */
this_cpu = raw_smp_processor_id(); /**< 获取当前 CPU 编号;在单核配置通常恒为 0。 */

/**
* @note atomic_try_cmpxchg 语义:
* - 若 *panic_cpu == old_cpu,则写入 this_cpu 并返回 true;
* - 否则不写入并返回 false,同时会把 old_cpu 更新为当时读到的值(便于调试/分支判断)。
*/
return atomic_try_cmpxchg(&panic_cpu, &old_cpu, this_cpu);
}
EXPORT_SYMBOL(panic_try_start);

panic_reset:清除 panic 状态(恢复为“未进入 panic”)

作用与实现原理

panic_cpu 重置为无效值,用于某些场景下的测试/恢复流程或特殊控制路径。其本质是一次原子写入发布。

1
2
3
4
5
6
7
8
/**
* @brief 重置 panic 状态,使 panic_cpu 回到 PANIC_CPU_INVALID。
*/
void panic_reset(void)
{
atomic_set(&panic_cpu, PANIC_CPU_INVALID);
}
EXPORT_SYMBOL(panic_reset);

panic_in_progress:判定系统是否处于 panic 状态

作用与实现原理

读取 panic_cpu 是否仍为无效值,从而获得“panic 是否已开始”的全局判定。这里用 unlikely() 仅是性能提示(不改变语义)。

1
2
3
4
5
6
7
8
9
10
/**
* @brief 判断是否已进入 panic(任意 CPU 宣布过 panic)。
*
* @return true 表示 panic_cpu 已被设置为某个有效 CPU 编号。
*/
bool panic_in_progress(void)
{
return unlikely(atomic_read(&panic_cpu) != PANIC_CPU_INVALID);
}
EXPORT_SYMBOL(panic_in_progress);

panic_on_this_cpu:判定“panic 是否发生在当前 CPU”

作用与实现原理

该函数判断 panic_cpu 是否等于当前 CPU 号。其关键前提是:一旦 panic_cpu 被设置,任务迁移在逻辑上不再成立(panic 路径通常会很快禁止抢占/中断并停止其他 CPU),因此可以使用 raw_smp_processor_id() 做判定,而不要求常规的迁移安全约束。

单核 上它退化为:panic_cpu == 0 时为 true(只要进入 panic 基本就成立)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @brief 判断当前 CPU 是否为宣布/执行 panic 的 CPU。
*
* @return true 表示 panic_cpu 与当前 CPU 编号一致。
*/
bool panic_on_this_cpu(void)
{
/**
* 依赖的关键假设:
* - panic_cpu 一旦被设置,后续不会发生“迁移到/迁移离 panic_cpu”的正常调度语义;
* - 因此可直接使用 raw_smp_processor_id() 做一致性判断。
*/
return unlikely(atomic_read(&panic_cpu) == raw_smp_processor_id());
}
EXPORT_SYMBOL(panic_on_this_cpu);

panic_on_other_cpu:判定“panic 是否发生在远端 CPU”

作用与实现原理

1
2
3
4
5
6
7
8
9
10
/**
* @brief 判断 panic 是否发生在其他 CPU(远端 CPU)。
*
* @return true 表示已进入 panic 且当前 CPU 不是 panic 主 CPU。
*/
bool panic_on_other_cpu(void)
{
return (panic_in_progress() && !panic_on_this_cpu());
}
EXPORT_SYMBOL(panic_on_other_cpu);

TAINT_* / taint_flags / print_tainted_seq / _print_tainted / add_taint:内核 taint 位索引、字符映射表与查询打印接口

先说明一个边界:如果 tainted_mask == 0,无论 verbose 是 true/false,都直接输出同一句:

  • Not tainted

下面举“同一组 taint 位已置位”的对比示例(假设已置位:TAINT_PROPRIETARY_MODULE(0)TAINT_WARN(9)TAINT_OOT_MODULE(12))。


示例 1:verbose=false(固定长度字符序列)

它会输出 20 个字符TAINT_FLAGS_COUNT),每一位都打印:置位打印 c_true,未置位打印 c_false(绝大多数是空格,只有第 0 位未置位时打印 G)。

输出形态大致是:

1
Tainted: P        W  O       

为了避免空格难以看清,可以按“位序→字符”理解(这里只列关键位):

  • 位 0:置位 → P
  • 位 9:置位 → W
  • 位 12:置位 → O
  • 其他位:未置位 → 大多是 ' '(空格)
  • 位 0 若置位:会是 G(这是唯一一个“未置位也会打印非空格字符”的位)

这种格式的特点:长度固定、位置固定,便于脚本或日志按位解析。


示例 2:verbose=true(只输出已置位项,带描述)

它只输出“已置位”的项,格式为:[字符]=描述,并用逗号分隔:

1
Tainted: [P]=PROPRIETARY_MODULE, [W]=WARN, [O]=OOT_MODULE

这种格式的特点:不固定长度,但对人更友好,直接给出“哪些 taint 被置位”以及它们的名称。


示例 3:只置位 TAINT_PROPRIETARY_MODULE(0) 的对比(更容易看出差异)

  • verbose=false:依旧打印 20 位占位字符(第 0 位为 P,其余多为空格)
1
Tainted: P                   
  • verbose=true:只打印已置位项
1
Tainted: [P]=PROPRIETARY_MODULE

平台关注:单核、无 MMU、ARMv7-M(STM32H750)

  • tainted_mask 的更新与读取通过 set_bit()/test_bit() 完成。即使在单核平台,这类位操作仍需保证与中断/抢占并发下的可见性与原子性;实现通常通过架构原子指令或临界区保证单次位更新不被打断。
  • _print_tainted() 使用静态缓冲区返回指针,这在无 MMU 场景下没有地址空间隔离,因此调用者必须遵守“下一次调用会覆盖内容”的约束,否则会出现复用覆盖导致的内容变化。
  • panic_on_taint 分支在嵌入式平台上通常意味着进入不可恢复的错误处理路径,行为取决于平台 panic 实现与复位策略。

taint_flags:taint 位到输出字符与描述的映射表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* @brief TAINT_* 位索引定义。
* @details 这些常量作为 tainted_mask 的位编号使用;使用宏而非枚举,便于在汇编源码中引用同一编号。
*/
#define TAINT_PROPRIETARY_MODULE 0
#define TAINT_FORCED_MODULE 1
#define TAINT_CPU_OUT_OF_SPEC 2
#define TAINT_FORCED_RMMOD 3
#define TAINT_MACHINE_CHECK 4
#define TAINT_BAD_PAGE 5
#define TAINT_USER 6
#define TAINT_DIE 7
#define TAINT_OVERRIDDEN_ACPI_TABLE 8
#define TAINT_WARN 9
#define TAINT_CRAP 10
#define TAINT_FIRMWARE_WORKAROUND 11
#define TAINT_OOT_MODULE 12
#define TAINT_UNSIGNED_MODULE 13
#define TAINT_SOFTLOCKUP 14
#define TAINT_LIVEPATCH 15
#define TAINT_AUX 16
#define TAINT_RANDSTRUCT 17
#define TAINT_TEST 18
#define TAINT_FWCTL 19
#define TAINT_FLAGS_COUNT 20
#define TAINT_FLAGS_MAX ((1UL << TAINT_FLAGS_COUNT) - 1)

/**
* @brief taint 位输出规则。
*/
struct taint_flag {
char c_true; /**< 对应 taint 位被置位时打印的字符。 */
char c_false; /**< 对应 taint 位未置位时打印的字符。 */
const char *desc; /**< taint 位的文本描述,verbose 模式下用于输出。 */
};

extern const struct taint_flag taint_flags[TAINT_FLAGS_COUNT];

enum lockdep_ok {
LOCKDEP_STILL_OK,
LOCKDEP_NOW_UNRELIABLE,
};

static unsigned long tainted_mask =
IS_ENABLED(CONFIG_RANDSTRUCT) ? (1 << TAINT_RANDSTRUCT) : 0; /**< 编译期条件:启用 RANDSTRUCT 时默认置位对应 taint。 */

#define TAINT_FLAG(taint, _c_true, _c_false) \
[ TAINT_##taint ] = { \
.c_true = _c_true, .c_false = _c_false, \
.desc = #taint, \
}

/**
* @brief taint 位的字符映射表。
* @details 下标与 TAINT_* 位编号严格一致;非 verbose 输出为固定位序字符序列,verbose 输出为“已置位项”的描述集合。
*/
const struct taint_flag taint_flags[TAINT_FLAGS_COUNT] = {
TAINT_FLAG(PROPRIETARY_MODULE, 'P', 'G'),
TAINT_FLAG(FORCED_MODULE, 'F', ' '),
TAINT_FLAG(CPU_OUT_OF_SPEC, 'S', ' '),
TAINT_FLAG(FORCED_RMMOD, 'R', ' '),
TAINT_FLAG(MACHINE_CHECK, 'M', ' '),
TAINT_FLAG(BAD_PAGE, 'B', ' '),
TAINT_FLAG(USER, 'U', ' '),
TAINT_FLAG(DIE, 'D', ' '),
TAINT_FLAG(OVERRIDDEN_ACPI_TABLE, 'A', ' '),
TAINT_FLAG(WARN, 'W', ' '),
TAINT_FLAG(CRAP, 'C', ' '),
TAINT_FLAG(FIRMWARE_WORKAROUND, 'I', ' '),
TAINT_FLAG(OOT_MODULE, 'O', ' '),
TAINT_FLAG(UNSIGNED_MODULE, 'E', ' '),
TAINT_FLAG(SOFTLOCKUP, 'L', ' '),
TAINT_FLAG(LIVEPATCH, 'K', ' '),
TAINT_FLAG(AUX, 'X', ' '),
TAINT_FLAG(RANDSTRUCT, 'T', ' '),
TAINT_FLAG(TEST, 'N', ' '),
TAINT_FLAG(FWCTL, 'J', ' '),
};

#undef TAINT_FLAG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @brief 将当前 tainted_mask 格式化输出到 seq_buf。
*
* @param s 目标缓冲区封装,负责边界控制与追加写入。
* @param verbose 为 true 时输出“已置位项”的描述集合;为 false 时输出固定位置字符序列。
*/
static void print_tainted_seq(struct seq_buf *s, bool verbose)
{
const char *sep = ""; /**< verbose 模式下用于分隔多项输出。 */
int i;

if (!tainted_mask) {
seq_buf_puts(s, "Not tainted");
return;
}

seq_buf_printf(s, "Tainted: ");
for (i = 0; i < TAINT_FLAGS_COUNT; i++) {
const struct taint_flag *t = &taint_flags[i]; /**< 取出该位对应的字符与描述规则。 */
bool is_set = test_bit(i, &tainted_mask); /**< 以位操作判断第 i 位是否置位。 */
char c = is_set ? t->c_true : t->c_false; /**< 非 verbose 模式下固定输出该字符占位。 */

if (verbose) {
if (is_set) {
seq_buf_printf(s, "%s[%c]=%s", sep, c, t->desc); /**< 仅输出已置位项,并给出字符与描述名。 */
sep = ", ";
}
} else {
seq_buf_putc(s, c); /**< 固定输出 TAINT_FLAGS_COUNT 个字符,位置与位编号一一对应。 */
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 返回 taint 状态字符串指针。
* @details 返回值指向静态缓冲区;后续再次调用会覆盖之前内容。
*
* @param verbose 控制输出格式,语义同 print_tainted_seq()。
* @return 指向内部静态缓冲区的字符串指针。
*/
static const char *_print_tainted(bool verbose)
{
static char buf[sizeof(taint_flags)]; /**< 以 taint_flags 数组大小作为保守上界,降低溢出风险但占用静态内存。 */
struct seq_buf s;

BUILD_BUG_ON(ARRAY_SIZE(taint_flags) != TAINT_FLAGS_COUNT); /**< 编译期一致性约束:表项数必须匹配位数定义。 */

seq_buf_init(&s, buf, sizeof(buf)); /**< 初始化 seq_buf,后续写入自动进行边界保护。 */

print_tainted_seq(&s, verbose);

return seq_buf_str(&s); /**< 获取以 NUL 结尾的结果字符串指针。 */
}

1
2
3
4
5
6
7
8
/**
* @brief 返回简略 taint 字符串。
* @details 输出为固定位置字符序列,长度与 TAINT_FLAGS_COUNT 对应。
*/
const char *print_tainted(void)
{
return _print_tainted(false);
}

1
2
3
4
5
6
7
8
/**
* @brief 返回 verbose taint 字符串。
* @details 仅包含已置位项,输出形式为若干 “[字符]=描述” 片段。
*/
const char *print_tainted_verbose(void)
{
return _print_tainted(true);
}

test_taint:测试指定 taint 位是否置位

1
2
3
4
5
6
7
8
9
10
11
/**
* @brief 测试指定 taint 位。
*
* @param flag TAINT_* 位编号。
* @return 非 0 表示置位,0 表示未置位。
*/
int test_taint(unsigned flag)
{
return test_bit(flag, &tainted_mask); /**< 位测试接口:对外提供稳定语义。 */
}
EXPORT_SYMBOL(test_taint);

get_taint:获取当前 taint 位掩码

1
2
3
4
5
6
7
8
/**
* @brief 获取当前 taint 掩码。
* @return 当前 tainted_mask 的值。
*/
unsigned long get_taint(void)
{
return tainted_mask; /**< 直接返回掩码;调用者自行按 TAINT_* 位编号解释。 */
}

add_taint:置位 taint 并按策略影响 lockdep 与 panic 行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @brief 增加一个 taint 位。
*
* @param flag TAINT_* 位编号。
* @param lockdep_ok 指示 lockdep 是否仍可靠;若不可靠则可能关闭锁调试以避免误导。
*/
void add_taint(unsigned flag, enum lockdep_ok lockdep_ok)
{
if (lockdep_ok == LOCKDEP_NOW_UNRELIABLE && __debug_locks_off())
pr_warn("Disabling lock debugging due to kernel taint\n"); /**< lockdep 不再可信时关闭锁调试并给出告警。 */

set_bit(flag, &tainted_mask); /**< 原子置位:与并发读取/写入保持一致性;单核下仍需覆盖中断并发场景。 */

if (tainted_mask & panic_on_taint) { /**< 若该 taint 位被配置为触发 panic,则进入致命路径。 */
panic_on_taint = 0; /**< 先清除触发配置,降低重复触发导致的递归风险。 */
panic("panic_on_taint set ..."); /**< 进入 panic:嵌入式平台上通常意味着停机或复位流程。 */
}
}
EXPORT_SYMBOL(add_taint);