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

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

相关推荐
csdn_aspnet11 小时前
TCP/IP协议栈深度解析:从基石到前沿
服务器·网络·tcp/ip
lcreek11 小时前
Linux信号机制详解:阻塞信号集与未决信号集
linux·操作系统·系统编程
shandianchengzi12 小时前
【记录】Tailscale|部署 Tailscale 到 linux 主机或 Docker 上
linux·运维·docker·tailscale
John Song12 小时前
Linux机器怎么查看进程内存占用情况
linux·运维·chrome
sichuanwuyi12 小时前
Wydevops工具的价值分析
linux·微服务·架构·kubernetes·jenkins
持戒波罗蜜13 小时前
ubuntu20解决intel wifi 驱动问题
linux·驱动开发·嵌入式硬件·ubuntu
不做无法实现的梦~13 小时前
使用ros2来跑通mid360的驱动包
linux·嵌入式硬件·机器人·自动驾驶
梁辰兴13 小时前
计算机网络基础:虚拟专用网
服务器·网络·计算机网络·vpn·虚拟专用网·计算机网络基础·梁辰兴
点云SLAM13 小时前
C++内存泄漏检测之Windows 专用工具(CRT Debug、Dr.Memory)和Linux 专业工具(ASan 、heaptrack)
linux·c++·windows·asan·dr.memory·c++内存泄漏检测·c++内存管理
LuiChun13 小时前
Docker Compose 容器服务查询与文件查看操作指南(Windows Docker Desktop 版)【一】
linux·运维·windows·docker·容器