受污染的内核

当发生某些可能在以后调查问题时相关的事情时,内核会将其自身标记为“受污染”。 不要太担心这一点,大多数时候运行受污染的内核不是问题; 该信息主要是在有人想要调查某些问题时才感兴趣,因为它的真正原因可能是导致内核被污染的事件。 这就是为什么来自受污染内核的错误报告通常会被开发人员忽略的原因,因此请尝试使用未受污染的内核重现问题。

请注意,即使您撤消了导致污染的原因(即,卸载专有内核模块),内核仍将保持污染状态,以表明该内核仍然不可信任。 这也是为什么当内核注意到内部问题(“内核错误”)、可恢复的错误(“内核 oops”)或不可恢复的错误(“内核 panic”)并将有关此的调试信息写入日志 dmesg 输出时,内核会打印受污染状态。 也可以通过 /proc/ 中的文件在运行时检查受污染状态。

错误、Oops 或 Panic 消息中的受污染标志

您会在以 “CPU:” 开头的行中的顶部附近找到受污染的状态; 如果内核被污染了,或者为什么会被污染,则会在进程 ID (“PID:”) 和触发事件的命令的缩短名称 (“Comm:”) 之后显示

BUG: unable to handle kernel NULL pointer dereference at 0000000000000000
Oops: 0002 [#1] SMP PTI
CPU: 0 PID: 4424 Comm: insmod Tainted: P        W  O      4.20.0-0.rc6.fc30 #1
Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2011
RIP: 0010:my_oops_init+0x13/0x1000 [kpanic]
[...]

如果事件发生时内核未被污染,您会在那里找到“Not tainted: ”; 如果被污染了,则会打印“Tainted: ”和字符,这些字符可以是字母或空格。 在上面的示例中,它看起来像这样

Tainted: P        W  O

下表解释了这些字符的含义。 在这种情况下,内核早些时候因加载了专有模块 (P)、发生警告 (W) 以及加载了外部构建的模块 (O) 而被污染。 要解码其他字母,请使用下表。

在运行时解码受污染状态

在运行时,您可以通过读取 cat /proc/sys/kernel/tainted 来查询受污染状态。 如果返回 0,则内核未被污染; 任何其他数字都指示其被污染的原因。 解码该数字的最简单方法是脚本 tools/debugging/kernel-chktaint,您的发行版可能会将其作为名为 linux-toolskernel-tools 的软件包的一部分提供; 如果没有,您可以从 git.kernel.org 下载该脚本并使用 sh kernel-chktaint 执行它,这将在日志中具有较早引用的语句的计算机上打印如下内容

Kernel is Tainted for following reasons:
 * Proprietary module was loaded (#0)
 * Kernel issued warning (#9)
 * Externally-built ('out-of-tree') module was loaded  (#12)
See Documentation/admin-guide/tainted-kernels.rst in the Linux kernel or
 https://linuxkernel.org.cn/doc/html/latest/admin-guide/tainted-kernels.html for
 a more details explanation of the various taint flags.
Raw taint value as int/string: 4609/'P        W  O     '

您可以尝试自己解码该数字。 如果只有一个原因导致您的内核被污染,那么这很容易,在这种情况下,您可以在下表中找到该数字。 如果有多个原因,您需要解码该数字,因为它是一个位字段,其中每个位都表示是否存在特定类型的污染。 最好将其留给前面提到的脚本,但如果您需要快速的东西,您可以使用此 shell 命令来检查设置了哪些位

$ for i in $(seq 18); do echo $(($i-1)) $(($(cat /proc/sys/kernel/tainted)>>($i-1)&1));done

用于解码受污染状态的表格

日志

数字

导致内核被污染的原因

0

G/P

1

加载了专有模块

1

_/F

2

模块被强制加载

2

_/S

4

内核在超出规格的系统上运行

3

_/R

8

模块被强制卸载

4

_/M

16

处理器报告了机器检查异常 (MCE)

5

_/B

32

引用了坏页或某些意外的页面标志

6

_/U

64

用户空间应用程序请求的污染

7

_/D

128

内核最近死机,即发生了 OOPS 或 BUG

8

_/A

256

用户覆盖的 ACPI 表

9

_/W

512

内核发出警告

10

_/C

1024

加载了暂存驱动程序

11

_/I

2048

应用了针对平台固件中错误的解决方法

12

_/O

4096

加载了外部构建的(“树外”)模块

13

_/E

8192

加载了未签名的模块

14

_/L

16384

发生了软锁

15

_/K

32768

内核已被实时修补

16

_/X

65536

辅助污染,为发行版定义和使用

17

_/T

131072

内核是使用 struct 随机化插件构建的

18

_/N

262144

已运行内核测试

注意:字符 _ 在此表中表示空格,以使阅读更容易。

更详细的污染解释

  1. 如果加载的所有模块都具有 GPL 或兼容许可证,则为 G,如果加载了任何专有模块,则为 P。 没有 MODULE_LICENSE 或具有 insmod 未识别为 GPL 兼容的 MODULE_LICENSE 的模块被认为是专有的。

  2. 如果任何模块由 insmod -f 强制加载,则为 F,如果所有模块都正常加载,则为 ' '

  3. 如果内核在超出规格的处理器或系统上运行,则为 S:硬件已置于不受支持的配置中,因此无法保证正常执行。 如果例如内核被污染

    • 在 x86 上:在英特尔 CPU(例如 Pentium M)上通过 forcepae 强制使用 PAE,这些 CPU 不报告 PAE 但可能具有功能实现,SMP 内核在非正式支持 SMP 的 Athlon CPU 上运行,MSR 从用户空间进行探测。

    • 在 arm 上:内核在某些 CPU(例如 Keystone 2)上运行,而没有启用某些内核功能。

    • 在 arm64 上:CPU 之间存在不匹配的硬件功能,引导加载程序以不同模式引导 CPU。

    • 某些驱动程序在不受支持的架构上使用(例如,在 x86_64 以外的架构上使用 scsi/snic,在非 x86/x86_64/itanium 上使用 scsi/ips,arm64 上 irqchip/irq-gic 的固件设置已损坏 ...)。

    • x86/x86_64:微码后期加载是危险的,会导致内核污染。 它要求所有 CPU 会合以确保在系统尽可能处于静止状态时发生更新。 但是,更高优先级的 MCE/SMI/NMI 可以将控制流从该会合中移开并中断更新,这对机器可能不利。

  4. 如果模块被 rmmod -f 强制卸载,则为 R,如果所有模块都正常卸载,则为 ' '

  5. 如果任何处理器报告了机器检查异常,则为 M,如果没有发生机器检查异常,则为 ' '

  6. 如果页面释放函数发现错误的页面引用或某些意外的页面标志,则为 B。 这表示硬件问题或内核错误; 日志中应有其他信息指示发生此污染的原因。

  7. 如果用户或用户应用程序明确请求设置 Tainted 标志,则为 U,否则为 ' '

  8. 如果内核最近死机,即发生了 OOPS 或 BUG,则为 D

  9. 如果 ACPI 表被覆盖,则为 A

  10. 如果内核之前发出过警告(尽管某些警告可能会设置更具体的污点标志),则为 W

  11. 如果已加载暂存驱动程序,则为 C

  12. 如果内核正在处理平台固件(BIOS 或类似固件)中的严重错误,则为 I

  13. 如果已加载外部构建(“树外”)模块,则为 O

  14. 如果在支持模块签名的内核中加载了未签名的模块,则为 E

  15. 如果系统之前发生过软锁死,则为 L

  16. 如果内核已进行实时修补,则为 K

  17. 辅助污点,由 Linux 发行版定义和使用,则为 X

  18. 内核是使用 randstruct 插件构建的,该插件可以故意产生非常不寻常的内核结构布局(甚至是性能病态的布局),这在调试时很重要。在构建时设置,则为 T

  19. 如果已运行内核内测试(例如 KUnit 测试),则为 N