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

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

相关推荐
安当加密2 小时前
无网环境下的终端登录安全:一个被忽视的等保盲区
服务器·安全
石像鬼₧魂石2 小时前
服务器安全配置自查清单(可打印版)
运维·服务器·安全
微爱帮监所写信寄信2 小时前
微爱帮监狱寄信写信小程序信件内容实时保存技术方案
java·服务器·开发语言·前端·小程序
一只旭宝3 小时前
Linux专题十二:mysql数据库以及redis数据库
linux·数据库·mysql
赵民勇3 小时前
paste命令用法详解
linux·shell
素素.陈3 小时前
根据图片中的起始位置的特殊内容将图片进行分组
java·linux·windows
闻道且行之3 小时前
Ubuntu 20.04 下 NVIDIA Tesla P40 驱动安装指南(核显桌面 + 计算卡分离方案)
linux·运维·ubuntu·nvidia·p40
oMcLin3 小时前
Ubuntu 24.04 使用 systemd 时 Nginx 服务无法启动的原因分析与解决
linux·nginx·ubuntu