天天看点

Linux内核panic核心执行逻辑

什么是OOPS

Oops是美国人比较常有的口语。就是有点意外,吃惊,或突然的意思。“oops”并不是很严重.对于linux内核来说,Oops就意外着内核出了异常,此时会将产生异常时出错原因,CPU的状态,出错的指令地址、数据地址及其他寄存器,函数调用的顺序甚至是栈里面的内容都打印出来,然后根据异常的严重程度来决定下一步的操作:杀死导致异常的进程或者挂起系统。

最典型的异常是在内核态引用了一个非法地址,通常是未初始化的野指针Null,这将导致页表异常,最终引发Oops。

Linux系统足够健壮,能够正常的反应各种异常。异常通常导致当前进程的死亡,而系统依然能够继续运转,但是这种运转都处在一种不稳定的状态,随时可能出问题。对于中断上下文的异常及系统关键资源的破坏,通常会导致内核挂起,不再响应任何事件。

内核的异常级别

内核的异常主要有三个级别,按照严重程度加深,可以分为BUG级->Oops级->Panic级。

Linux内核panic核心执行逻辑

BUG级:

Bug是指那些不符合内核的正常设计,但内核能够检测出来并且对系统运行不会产生影响的问题,比如在原子上下文中休眠,再内核中用BUG标识。

Linux内核panic核心执行逻辑

Oops

程序在内核态时,进入一种异常情况,比如引用非法指针导致的数据异常,数组越界导致的取指异常,此时异常处理机制能够捕获此异常,并将系统关键信息打印到串口上,正常情况下Oops消息会被记录到系统日志中去,可以通过journalctl -k命令查看.

Oops发生时,进程处在内核态,很可能正在访问系统关键资源,并且获取了一些锁,当进程由于Oops异常退出时,无法释放已经获取的资源,导致其他需要获取此资源的进程挂起,对系统的正常运行造成影响。通常这种情况,系统处在不稳定的状态,很可能崩溃。

Panic

当Oops发生在中断上下文中或者在进程0和1中,系统将彻底挂起,因为中断服务程序异常后,将无法恢复,这种情况即称为内核panic。另外当系统设置了panic标志时,无论Oops发生在中断上下文还是进程上下文,都将导致内核Panic。由于在中断复位程序中panic后,系统将不再进行调度,rsyslogd将不会再运行,因此这种情况下,Oops的消息仅仅打印到串口上,不会被记录在系统日志中。

Linux内核panic核心执行逻辑

oops要不要panic依据几个体哦见,分别是:

  1. 在中断中oops.
  1. 通过设置/proc/sys/kernel/panic_on_oops将panic_on_oops置位。
  1. 进程为0号或者1号进程.

kexec_should_crash(current)会判断进程是否为0号或者1号进程,如果是的话,oops会转变为panic.

Linux内核panic核心执行逻辑

panic的执行逻辑

Linux内核panic核心执行逻辑

流程中的细节要点:

  1. 代码尽其所能避免复杂性和可能的死锁
  1. KERN_EMERG 内核打印信息 “Kernel panic - not syncing”
  1. panic_print_sys_info();确定并显示更多系统信息–例如所有任务信息、内存、计时器、锁、ftrace信息和所有内核打印。具体取决于panic_print的bitmask
Linux内核panic核心执行逻辑
  1. 函数所做的最后一件事就是在单个启用的处理器核上无限循环;在循环中,它重置非屏蔽中断(NMI),然后定期调用一个名为架构依赖的panic_blink函数;在x86上,该事件会引起键盘LED会闪烁.
  1. 由于在函数入口处关闭了调度,所以控制台不会在有反应,系统卡死,只能冷上电。

oops要不要转成panic

前面说到,内核文件系统有一个节点/proc/sys/kernel/panic_on_oops,可以控制在oops执行结束前,要不要执行panic.

代码中的逻辑如下:

Linux内核panic核心执行逻辑

当检测到设置了panic_on_oops时,执行panic

Linux内核panic核心执行逻辑

打开/proc/sys/kernel/panic_on_oops测试,触发越界访问的oops导致的panic.PC直接挂死,电脑无反应。

Linux内核panic核心执行逻辑

panic_timeout

panic_timeout通过 /proc/sys/kernel/panic 节点去设置,代表panic后等待几秒系统重启,默认情况为0代表不会重启,一直等待。

Linux内核panic核心执行逻辑

重启调用接口emergency_restart。

Linux内核panic核心执行逻辑

其它panic控制变量还包括如下例表中的项:

-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_on_io_nmi
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_on_oops
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_on_rcu_stall
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_on_unrecovered_nmi
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_on_warn
-rw-r--r-- 1 root root 0 2月   1 19:26 /proc/sys/kernel/panic_print
           

这些参数不但可以通过sysctl建立的procfs去设置,还可以通过启动命令行控制。

Linux内核panic核心执行逻辑

以qemu为例,启动参数命令行中传入panic=88

qemu-system-arm -M vexpress-a9 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8 panic=88" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic
           
Linux内核panic核心执行逻辑

panic_timeout can also be set from config macro.int panic_timeout = CONFIG_PANIC_TIMEOUT;

Linux内核panic核心执行逻辑

驱动注册自定义的panic函数

驱动可以通过panic_notifier_list 链表注册自己的panic通知函数,示例代码如下:

#define pr_fmt(fmt) "%s:%s(): " fmt, KBUILD_MODNAME, __func__
#include <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/delay.h>
 
static void    dev_ring_alarm(void)
{
    pr_emerg("!!! ALARM !!!\n");
}

static int mypanic_handler(struct notifier_block *nb, unsigned long val, void *data)
{
    //执行的函数
    dev_ring_alarm();

    dump_stack();
 
    return NOTIFY_OK;
}
 
//结构体
static struct notifier_block mypanic_nb = {
    .notifier_call = mypanic_handler,//回调函数
//    .priority = INT_MAX
};
 
static int __init panic_notifier_init(void)
{
    //注册函数
    atomic_notifier_chain_register(&panic_notifier_list, &mypanic_nb);
    pr_info("Registered panic notifier.\n");
 
    return 0;
}
 
static void __exit panic_notifier_exit(void)
{
    atomic_notifier_chain_unregister(&panic_notifier_list, &mypanic_nb);
    pr_info("Unregistered panic notifier\n");
}
 
module_init(panic_notifier_init);
module_exit(panic_notifier_exit);
MODULE_LICENSE("Dual MIT/GPL");
           

结束!

继续阅读