文章目录
Linux 版本:Linux version 3.18.24
panic函数原型路径在:kernel/panic.c
panic 函数详解
panic函数的主要作用是停止当前的系统运行,在系统监测到异常时调用该函数。
void panic(const char *fmt, ...)
-> local_irq_disable(); // 关闭本地中断,防止任务抢占
// 有可能直接从恐慌断言来到这里,而不禁用先发制人。从这里调用的一些函数希望禁用抢占。不过,以后再启用它也没有意义…只允许一个CPU从这里执行紧急代码。对于多个并行panic调用,所有其他CPU要么停止自己,要么等待第一个CPU使用smp send stop()停止它们。
-> if (!spin_trylock(&panic_lock))
panic_smp_self_stop();
-> console_verbose(); // 修改console 级别,以便printk能把消息打印出来。
-> smp_send_stop(); // smp关闭函数
-> atomic_notifier_call_chain(&panic_notifier_list, 0, buf); // 通知运行panic处理函数
-> kmsg_dump(KMSG_DUMP_PANIC); // dump CPU寄存器的状态值已经一些堆栈的信息输出
-> if (panic_timeout > 0) { // 如果sysctl配置了panic_timeout > 0则在panic_timeout后重 * 启系统
pr_emerg("Rebooting in %d seconds..", 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);
i_next = i + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
}
-> local_irq_enable(); // 本地锁打开,进行死循环,抢占被禁止,CPU一直运行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);
}
实际应用
了解上面panic函数具体行为之后,就可以利用panic函数做一些log记录功能等。
现象: 小伙伴有没有遇到这样一个场景。当用户手上的系统设备没有接上串口,此时系统发生了不知名的panic 崩溃,看门狗重启了。这时再去分析查看panic 信息log已经消失了。
解决方案: 小编在项目中遇到这种情况,利用panic 函数将系统重启之前有必要的一些打印保存下来再重启。这就保证看门狗重启之后,我还能查看到系统重启之前的相关系统环境。
结合 Linux内核(十六)Linux 内核态进行读写文件的函数 使用和解析文章实现log记录。
有需要的小伙伴领取代码: Linux内核系统 panic log 记录代码
(注:如果要将panic信息保存在文件系统里,在系统重启后可以查看这个panic。可以在打印堆栈信息之后,添加该函数将信息存在文件系统中。)
log保存在文件系统中,效果图:
在启动脚本中可以限制log个数,不至于log信息堆积影响系统正常运行(实例中只保留最新的5个panic log)
panic_file="/mnt/log"
if [ -d "$panic_file" ];then
if [ $(ls -l /mnt/log/ |grep "^-"|wc -l ) -gt 6 ]; then
cd /mnt/log
rm `ls -t |tail -n +6`
fi
else
mkdir /mnt/log
fi
Bug调试
在调试的过程中出现一个bug:使用vfs_write写文件出现失败
源码中写文件
size_t ret = 0;
ret = vfs_read(fkmsg, p, size, &pos_kmsg)
n = vfs_write(ffile, p, ret, &pos_file);
原因:
vfs_write和vfs_read返回值是ssize_t类型,传入参数是size_t类型
size_t: 是标准C库中定义的,32位系统:unsigned int,在64为系统: long unsigned int。
在C++中,设计 size_t 就是为了适应多个平台的,增强可移植性。
在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节.
ssize_t:
这个数据类型用来表示可以被执行读写操作的数据块的大小。它和size_t类似,但必需是signed.意即:它表示的是signed
size_t类型的(typedef signed int size_t)。 改成下面的代码就能解决问题:
改成下面的代码就能解决问题:
ssize_t ret = 0;
ret = vfs_read(fkmsg, p, size, &pos_kmsg)
n = vfs_write(ffile, p, (size_t)ret, &pos_file);