当linux触发panic后进行自定义收尾回调处理

大家好,我是bug菌~

如何在内核panic时注册回调函数,有时候系统panic后需要进行一些收尾工作:

完整的示例代码

c 复制代码
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/reboot.h>  // 可能需要
#include <linux/delay.h>

static int my_panic_handler(struct notifier_block *nb,
                            unsigned long reason, void *arg)
{
    // reason 参数表示panic的原因,可以是:
    // PANIC_REASON_GENERIC
    // PANIC_REASON_HW_BUG
    // PANIC_REASON_HUNG_TASK
    // PANIC_REASON_STACKLEAK
    // PANIC_REASON_CORRUPTED_STACK
    // PANIC_REASON_CGROUP_DESTROY_FAILED
    
    pr_emerg("Kernel panic detected in my module!\n");
    
    // 获取panic消息(如果有)
    const char *msg = (const char *)arg;
    if (msg)
        pr_emerg("Panic message: %s\n", msg);
    
    // 紧急操作 - 注意:
    // 1. 不要分配内存(如kmalloc)
    // 2. 不要获取可能已持有的锁
    // 3. 避免复杂操作,系统可能已不稳定
    
    // 示例:通知硬件模块
    // hardware_emergency_shutdown();
    
    // 示例:保存关键数据到持久存储
    // emergency_data_flush();
    
    // 示例:发送信号到其他处理器
    // send_ipi_to_all_cpus(EMERGENCY_IPI);
    
    return NOTIFY_DONE;
}

static struct notifier_block panic_nb = {
    .notifier_call = my_panic_handler,
    .priority = INT_MAX,  // 优先级:数字越大,优先级越高
    // 或者使用 .next = NULL,
};

static int __init panic_module_init(void)
{
    int ret;
    
    pr_info("Registering panic notifier\n");
    
    // 注册到panic通知链
    ret = atomic_notifier_chain_register(&panic_notifier_list, &panic_nb);
    if (ret) {
        pr_err("Failed to register panic notifier: %d\n", ret);
        return ret;
    }
    
    // 也可以注册到重启通知链,如果系统会重启
    // register_reboot_notifier(&reboot_nb);
    
    return 0;
}

static void __exit panic_module_exit(void)
{
    pr_info("Unregistering panic notifier\n");
    
    // 注销通知
    atomic_notifier_chain_unregister(&panic_notifier_list, &panic_nb);
}

module_init(panic_module_init);
module_exit(panic_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Kernel panic handler module");

关键注意事项

  1. 操作限制
c 复制代码
// 在panic处理程序中应避免:
- 内存分配(kmalloc, vmalloc等)
- 获取锁(mutex_lock, spin_lock等)
- 调度相关操作(schedule, sleep等)
- 复杂I/O操作
- 可能失败的操作

// 应该做:
- 简单的硬件寄存器操作
- 紧急状态保存到非易失存储
- 发送硬件信号或中断
- 打印关键调试信息
  1. 优先级控制
c 复制代码
static struct notifier_block panic_nb = {
    .notifier_call = my_panic_handler,
    .priority = INT_MAX,  // 最高优先级
    // 或者:
    // .priority = 0,      // 最低优先级
    // .priority = 100,    // 自定义优先级
};
  1. 多处理器注意事项
c 复制代码
static int my_panic_handler(struct notifier_block *nb,
                            unsigned long reason, void *arg)
{
    // panic处理程序在所有CPU上都会运行!
    // 使用 smp_processor_id() 获取当前CPU ID
    int cpu = smp_processor_id();
    
    // 如果是第一个panic的CPU,执行清理操作
    if (cpu == 0) {
        // 主清理操作
    } else {
        // 从处理器只需简单清理
    }
    
    return NOTIFY_DONE;
}
  1. 系统挂起处理
c 复制代码
#include <linux/freezer.h>

static int my_panic_handler(struct notifier_block *nb,
                            unsigned long reason, void *arg)
{
    // 尝试解冻进程(如果需要)
    thaw_processes();
    
    // 停止所有可停止的进程
    // emergency_ops();
    
    return NOTIFY_DONE;
}

调试技巧

  1. 添加调试信息
c 复制代码
static int my_panic_handler(struct notifier_block *nb,
                            unsigned long reason, void *arg)
{
    pr_emerg("=== My Module Panic Handler ===\n");
    pr_emerg("CPU: %d\n", smp_processor_id());
    pr_emerg("Panic reason: %lu\n", reason);
    pr_emerg("Stack trace:\n");
    dump_stack();  // 打印堆栈跟踪
    
    // 保存寄存器状态
    show_regs(get_irq_regs());
    
    return NOTIFY_DONE;
}
  1. 使用Kprobes进行测试
c 复制代码
// 可以通过Kprobes触发panic来测试
#include <linux/kprobes.h>

static struct kprobe kp = {
    .symbol_name = "panic",
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    pr_info("About to trigger panic\n");
    return 0;
}

static int __init test_init(void)
{
    int ret;
    
    kp.pre_handler = handler_pre;
    ret = register_kprobe(&kp);
    
    // 之后可以手动触发panic来测试
    // panic("Test panic");
    
    return ret;
}

替代方案

  1. 使用die_notifier
c 复制代码
#include <linux/kdebug.h>

static int my_die_handler(struct notifier_block *self,
                         unsigned long val, void *data)
{
    struct die_args *args = data;
    
    if (val == DIE_OOPS || val == DIE_PANIC) {
        pr_emerg("Die notifier triggered\n");
    }
    
    return NOTIFY_DONE;
}

static struct notifier_block die_nb = {
    .notifier_call = my_die_handler,
};
  1. 使用module参数控制
c 复制代码
static bool enable_panic_handler = true;
module_param(enable_panic_handler, bool, 0644);

static int my_panic_handler(struct notifier_block *nb,
                            unsigned long reason, void *arg)
{
    if (!enable_panic_handler)
        return NOTIFY_DONE;
    
    // 处理逻辑
    return NOTIFY_DONE;
}

最佳实践

  1. 保持处理程序简单 - 系统已不稳定,复杂操作可能失败
  2. 避免依赖其他模块 - 其他模块可能已经卸载或损坏
  3. 记录关键状态 - 保存到不易丢失的存储中
  4. 考虑硬件状态 - 确保硬件处于安全状态
  5. 测试充分 - 在实际panic场景测试,而不仅仅是模拟

以上就是我的一些技巧和注意事项分享了。

相关推荐
开开心心就好9 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
css趣多多9 小时前
add组件增删改的表单处理
java·服务器·前端
予枫的编程笔记9 小时前
【Linux进阶篇】从基础到实战:grep高亮、sed流编辑、awk分析,全场景覆盖
linux·sed·grep·awk·shell编程·文本处理三剑客·管道命令
Sheep Shaun9 小时前
揭开Linux的隐藏约定:你的第一个文件描述符为什么是3?
linux·服务器·ubuntu·文件系统·缓冲区
Tfly__10 小时前
在PX4 gazebo仿真中加入Mid360(最新)
linux·人工智能·自动驾驶·ros·无人机·px4·mid360
野犬寒鸦10 小时前
从零起步学习并发编程 || 第七章:ThreadLocal深层解析及常见问题解决方案
java·服务器·开发语言·jvm·后端·学习
陈桴浮海10 小时前
【Linux&Ansible】学习笔记合集二
linux·学习·ansible
生活很暖很治愈10 小时前
Linux——环境变量PATH
linux·ubuntu
?re?ta?rd?ed?10 小时前
linux中的调度策略
linux·运维·服务器
深圳市九鼎创展科技10 小时前
瑞芯微 RK3399 开发板 X3399 评测:高性能 ARM 平台的多面手
linux·arm开发·人工智能·单片机·嵌入式硬件·边缘计算