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

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

相关推荐
旖旎夜光1 小时前
Linux(13)(中)
linux·网络
威迪斯特2 小时前
CentOS图形化操作界面:理论解析与实践指南
linux·运维·centos·组件·图形化·桌面·xserver
行走正道2 小时前
CANN仓库日志系统架构 分级日志与性能开销优化源码解读
系统架构·cann
一方热衷.2 小时前
在线安装对应版本NVIDIA驱动
linux·运维·服务器
独自归家的兔2 小时前
ubuntu系统安装dbswitch教程 - 备份本地数据到远程服务器
linux·运维·ubuntu
m0_694845572 小时前
tinylisp 是什么?超轻量 Lisp 解释器编译与运行教程
服务器·开发语言·云计算·github·lisp
ONE_SIX_MIX2 小时前
ubuntu 24.04 用rdp连接,桌面黑屏问题,解决
linux·运维·ubuntu
龙飞052 小时前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
*小海豚*2 小时前
在linux服务器上DNS正常,但是java应用调用第三方解析域名报错
java·linux·服务器
June`2 小时前
muduo项目排查错误+测试
linux·c++·github·muduo网络库