当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场景测试,而不仅仅是模拟

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

相关推荐
毕竟是shy哥6 小时前
vs code连接远程服务器docker环境及配置免密登录
服务器·docker·vs code
徐小黑ACG7 小时前
nginx配置文件
linux·服务器·nginx
新时代牛马7 小时前
Linux VFS 完整篇:从path_openat、dentry/inode 到page cache 与挂载排障
linux·运维·服务器
java_logo7 小时前
Docker 部署 Rocky Linux:轻松搭建 RHEL 兼容企业级基础镜像平台
linux·docker·容器·rocky linux·基础镜像·轩辕镜像·rhel 兼容
拂拉氏8 小时前
【知识讲解】 Linux虚拟地址空间认识
linux·虚拟地址空间
新时代牛马9 小时前
Linux 驱动中断与定时完整篇:从 request_threaded_irq 到hrtimer 选型与排障
linux·运维·服务器
分支预测失败12 小时前
RISC-V 核间中断 IPI 实战:从 MSIP 寄存器到 IMSIC 消息投递
linux·后端
这个DBA有点耶12 小时前
同样48核配置TPS差1倍?高性价比数据库一体机的“软硬协同”才是分水岭
服务器·数据库·架构
GeW12 小时前
RHCE考试避坑指南"90%考生容易忽略的细节
linux
小哈里12 小时前
【执行】个人操作系统架构图 v1(硬件层,OS层,软件层,Agent层,横向控制面)
系统架构·操作系统·agent·架构图·执行