
# 内核异常事件分析指南
#### 背景说明
HCE运行时，不可避免地会出现一些内核事件，例如[soft lockup]、[RCU(Read-Copy Update) stall]、[hung task]、[global OOM]、[cgroup OOM]、[page allocation failure]、[list corruption]、[Bad mm_struct]、[I/O error]、[EXT4-fs error]、[MCE (Machine Check Exception)]、[fatal signal]、[warning]、[panic]、[oops]。本节为您介绍这些内核事件的原理及触发方法。
 #### soft lockup
soft lockup是内核检测CPU在一定时间内（默认20秒）没有发生调度切换时，上报的异常。
- 原理 soft lockup利用Linux内核watchdog机制触发。内核会为每一个CPU启动一个优先级最高的FIFO实时内核线程watchdog，名称为watchdog/0、watchdog/1以此类推。这个线程会定期调用watchdog函数，默认每4秒执行一次。同时调用过后会重置一个hrtimer定时器在2倍的watchdog_thresh时间后到期。watchdog_thresh是内核参数，对应默认超时时间为20秒。
  在超时时间内，如果内核线程watchdog没被调度，hrtimer定时器到期，即触发内核打印类似如下的soft lockup异常。
  ```
  BUG: soft lockup - CPU#3 stuck for 23s! [kworker/3:0:32]
  ```
  
- 触发方法 关闭中断或关闭抢占，软件执行死循环。
  
 
 #### RCU(Read-Copy Update) stall
RCU stall是一种rcu宽限期内rcu相关内核线程没有得到调度的异常。
- 原理 在RCU机制中，reader不用等待，可以任意读取数据，RCU记录reader的信息；writer更新数据时，先复制一份副本，在副本上完成修改，等待所有reader退出后，再一次性地替换旧数据。
  writer需要等所有reader都停止引用"旧数据"才能替换旧数据。这相当于给了这些reader一个优雅退出的宽限期，这个等待的时间被称为grace-period，简称GP。
  当reader长时间没有退出，writer等待的时间超过宽限期时，即上报RCU Stall。
  
- 触发方法 内核在Documentation/RCU/stallwarn.txt文档列出了可能触发RCU stall的场景：cpu在rcu reader临界区一直循环，cpu在关闭中断或关闭抢占场景中一直循环等。
  
 
 #### hung task
当内核检测到进程处于D状态超过设定的时间时，上报hung task异常。
- 原理 进程其中一个状态是TASK_UNINTERRUPTIBLE，也叫D状态，处于D状态的进程只能被wake_up唤醒。内核引入D状态时，是为了让进程等待IO完成。正常情况下，IO正常处理，进程不应该长期处于D状态。
  hung task检测进程长期处于D状态的原理，内核会创建一个线程khungtaskd，用来定期遍历系统中的所有进程，检查是否存在处于D状态超过设置时长（默认120秒）的进程。如果存在这样的进程，则打印并上报相关警告和进程堆栈。如果配置了hung_task_panic（通过proc或内核启动参数配置），则直接发起panic。
  

- 触发方法 创建内核线程，设成D状态，scheduler释放时间片。
  
 
 #### global OOM
Linux的OOM killer特性是一种内存管理机制，在系统可用内存较少的情况下，内核为保证系统还能够继续运行下去，会选择结束一些进程释放掉一些内存。
- 原理 通常oom_killer的触发流程是：内核为某个进程分配内存，当发现当前物理内存不够时，触发OOM。OOM killer遍历当前所有进程，根据进程的内存使用情况进行打分，然后从中选择一个分数最高的进程，终止进程释放内存。
  OOM killer的处理主要集中在mm/oom_kill.c，核心函数为out_of_memory，函数处理流程为：
  1. 通知系统中注册了oom_notify_list的模块释放一些内存，如果从这些模块中释放出了一些内存，直接结束oom killer流程；如果回收失败，进入下一步。
  
  2. 触发oom killer通常是由当前进程进行内存分配所引起。如果当前进程已经挂起了一个SIG_KILL信号或者正在退出，直接选中当前进程，终止进程释放内存；否则进入下一步。
  
  3. 检查panic_on_oom系统管理员的设置，决定OOM时是进行oom killer还是panic。如果选择panic，则系统崩溃并重启；如果选择oom killer，进入下一步。
  
  4. 进入oom killer，检查系统设置，系统管理员可设置终止当前尝试分配内存、引起OOM的进程或其它进程。如果选择终止当前进程，oom killer结束；否则进入下一步。
  
  5. 调用select_bad_process选中合适进程，然后调用oom_kill_process终止选中的进程。如果select_bad_process没有选出任何进程，内核进入panic。
   
- 触发方法 执行占用大内存的程序，直到内存不足。
  
 
 #### cgroup OOM
- 与global OOM的区别 cgroup OOM与global OOM的内存范围不同。当FS cgroup组内进程使用内存超过了设置的上限，cgroup通过KILL相应的进程来释放内存。
  
 
- 触发方法 指定cgroup下某个进程持续占用大内存直至内存不足。
  
 #### page allocation failure
page allocation failure是申请空闲页失败时，系统上报的错误。当程序申请某个阶数（order）的内存，但系统内存中，没有比申请阶数高的空闲页，即触发内核报错。
- 原理 Linux使用伙伴系统（buddy system）内存分配算法。将所有的空闲页表（一个页表的大小为4K）分别链接到包含了11个元素的数组中，数组中的每个元素将大小相同的连续页表组成一个链表，页表的数量为1、2、4、8、16、32、64、128、256、512、1024，所以一次性可以分配的最大连续内存为1024个连续的4k页表，即4MB的内存。
  假设申请一个包括256个页表的内存，指定阶数order为6，系统会依次查找数组中的第9、10、11个链表，上一个为空，表示没有此阶数的空闲内存，查找下一个，直到最后一个链表。
  如果所有链表均为空，则申请失败，内核上报page allocation failure错误。输出报错信息，描述申请阶数为6的内存页失败：
  ```
  page allocation failure:order:6
  ```
  
- 触发方法 用alloc_pages连续申请高阶数内存页（例如order=10），不释放，直到申请失败。
  
 
 #### list corruption
list corruption是内核检查链表有效性失败的报错，报错类型分为list_add corruption和list_del corruption。
- 原理 内核提供list_add和list_del，对传入的链表先检查链表的有效性（valid），检查通过后，修改链表增加或删除节点。如果检查链表失败，则上报corruption错误。检查和报错代码在内核lib/list_debug.c。
  这种错误通常为内存异常操作导致，例如内存踩踏、内存损坏等。
  图1报错类型list_add corruption和list_del corruption   
  ![](https://support.huaweicloud.com/usermanual-hce/zh-cn_image_0000002480089042.png "点击放大") 
- 触发方法 用list.h的内核标准接口创建链表，非法修改链表节点的prev或next指针，再调用内核list_add/list_del接口。
  
 
 #### Bad mm_struct
Bad mm_struct错误通常是由于内核中的一个或多个mm_struct数据结构被破坏或损坏所导致。
- 原理 mm_struct是Linux内核中的一个重要数据结构，用于跟踪进程的虚拟内存区域。如果该数据结构被破坏，可能会导致进程崩溃或系统崩溃。这种错误通常由内存异常导致，例如mm_struct区域的内存被踩踏、内存越界等。
  
- 触发方法 无人为触发方法，当硬件错误，或者Linux系统内核代码错误时会触发Bad mm_struct。
  
 
 #### I/O error
Linux I/O error报错通常表示输入/输出操作失败，在网卡、磁盘等IO设备驱动异常，或文件系统异常都可能打印这个错误。
- 原理 错误原因取决于代码执行失败的条件。常见的触发异常的原因是硬件故障、磁盘损坏、文件系统错误、驱动程序问题、权限问题等。例如当系统尝试读取或写入磁盘上的数据时，如果发生错误，就会出现I/O错误。
  
- 触发方法 系统读写磁盘过程，拔出磁盘，导致磁盘数据损坏。
  
 
 #### EXT4-fs error
EXT4-fs error是由于ext4格式的文件系统中，文件节点的错误导致。
- 原理 文件储存的最小存储单位叫做"扇区"（sector），连续多个扇区组成"块"（block）。inode节点储存文件的元信息，包括文件的创建者、创建日期、大小、属性、实际存储的数据块（block number）。EXT4格式的inode信息校验失败会触发EXT4-fs error。
  内核ext4校验使用checksum校验inode信息，当出现分区表错误、磁盘硬件损坏时，内核返回-EIO错误码，系统上报EXT4-fs error checksum invalid错误。
  图2EXT4文件系统结构   
  ![](https://support.huaweicloud.com/usermanual-hce/zh-cn_image_0000002480089044.png "点击放大") 
- 触发方法 使用磁盘过程中强行拔盘，重新接入读盘。
  
 
 #### MCE (Machine Check Exception)
MCE是CPU发现硬件错误时触发的异常（exception），上报中断号是18，异常的类型是abort。
- 原理 导致MCE的原因主要有：总线故障、内存ECC校验错、cache错误、TLB错误、内部时钟错误等。不仅硬件故障会引起MCE，不恰当的BIOS配置、firmware bug、软件bug也有可能引起MCE。
  MCE中断上报，操作系统检查一组寄存器称为Machine-Check MSR，根据寄存器的错误码执行对应的处理函数（函数实现依赖不同的芯片架构实现）。
  
- 触发方法 无人为触发方法，当总线故障、内存ECC校验错、cache错误、TLB错误、内部时钟错误等时会触发MCE。
  
 
 #### fatal signal
fatal signal指信号处理方式不能被设置为忽略或执行自定义处理函数的信号类型，包括SIGKILL、SIGSTOP、SIGILL等。
- 原理 Linux信号（signal）机制，用于系统中进程间通讯，是一种异步的通知机制。当一个信号发送给一个进程，而操作系统中断了进程正常的控制流程时，任何非原子操作都将被中断。
  如果SIG符合条件，即为fatal信号：
  图3fatal信号示例   
  ![](https://support.huaweicloud.com/usermanual-hce/zh-cn_image_0000002479929070.png "点击放大") 
 
- 触发方法 用户态程序执行非法指令、kill -9杀进程。
  
 #### warning
warning是指操作系统在运行时检测到需要立即注意的内核问题（issue），而采取的上报动作，记录并打印发生时的调用栈信息。上报后，系统继续运行。
- 原理 warning是通过调用WARN、WARN_ON、WARN_ON_ONCE等宏来触发的。
  导致warning的原因有多种，需要根据调用栈回溯，找到调用warning宏的具体原因。warning宏并不会导致系统运行状态发生改变，也不提供处理warning的指导。
  
- 触发方法 根据系统调用构造warning条件。
  
 
 #### panic
Kernel panic是指操作系统在监测到内部的致命错误，并无法安全处理此错误时采取的动作。内核触发到某种异常情况，运行kernel_panic函数，并尽可能把异常发生时获取的全部信息打印出来。
- 原理 导致异常的原因多种多样，通过异常打印的调用信息，找到调用kernel_panic的原因。常见的原因包括内核堆栈溢出、内核空间的除0异常、内存访问越界、内核陷入死锁等。
  
- 触发方法 内核态读0地址。
  
 
 #### oops
oops 是内核在检测到严重错误时生成的错误报告。这些错误通常包括内核代码中的非法内存访问、除零错误、无效的指令等。
- 原理 当内核检测到这些错误时，会生成一个 oops 报告，记录错误发生时的上下文信息，包括寄存器状态、调用栈、内存状态等。oops 报告通常会打印到系统日志中，并且可以通过 dmesg 命令查看。内核会尝试恢复执行，但如果错误严重到无法恢复，可能会导致系统崩溃或重启。
  
- 触发方法
  1. 非法内存访问：访问未分配或已释放的内存。
  
  2. 除零错误：在内核代码中进行除零操作。
  
  3. 无效指令：执行非法或未定义的指令。
  
  4. 硬件故障：如内存故障、CPU故障等。
   
 
