You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核6.6.6x中__pollwake函数内pr_info打印触发内核Panic问题排查

问题分析与修复方案

这是典型的内核竞态条件导致的内存非法访问——虽然你在if判断时成功访问了pwq->polling_task->comm,但从判断到执行pr_info的间隙,目标进程的task_struct已经被销毁或内存被复用,导致后续访问失效。

为什么会出现这种“前后不一致”的崩溃?

  1. 崩溃根源:从调用栈可以看到,崩溃发生在string_nocheck函数(属于vsnprintf处理%s的逻辑),说明此时pwq->polling_task->comm指向的内存已经不再是合法的struct task_struct成员:

    • 要么对应的进程已经退出,task_struct的引用计数归0,被内核回收进slab缓存;
    • 要么这块内存已经被其他内核对象复用改写。
  2. 为什么if里的strstr没崩溃?
    这是内核竞态里的“偶然存活”场景:

    • 当你执行strstr(pwq->polling_task->comm, "process1")时,polling_task对应的进程可能还未完成退出流程,或者task_struct虽然已经退出,但内存还没被内核重新分配/覆盖,那次字符读取刚好没触发页错误。
    • 但内核的调度和内存回收是异步的,哪怕只有几个指令周期的间隙,内核都可能完成进程销毁、内存复用的操作,等到执行pr_info时,comm所在的内存已经彻底无效,触发panic。

内核中task_struct的生命周期说明

进程退出时,内核会调用put_task_struct递减其引用计数:

  • 当计数大于0时,task_struct会被保留(比如有其他代码在引用它);
  • 当计数归0时,task_struct会被放入slab缓存(不会立即释放回物理内存),后续其他内核分配请求会复用这块内存,导致原来的comm内容被覆盖。

修复方案

要解决这个问题,核心是确保你访问task_struct期间,它不会被销毁,同时使用内核安全的打印方式:

  1. 增加task_struct的引用计数:
    用get_task_struct()获取进程引用,确保在你使用期间,task_struct不会被回收;使用完成后用put_task_struct()释放引用。
  2. 使用安全的进程名打印格式:
    内核提供了%ps格式符,专门用于安全打印进程名称,即使task_struct状态异常也能避免崩溃(内部会处理无效指针的情况)。

修复后的代码片段示例:

static int __pollwake(wait_queue_entry_t *wait, unsigned mode, int sync, void *key) {
    struct poll_wqueues *pwq = wait->private;
    DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);

    smp_wmb();
    pwq->triggered = 1;

    if (wait->private && (wait->func == pollwake) && pwq->polling_task) {
        // 获取task_struct引用,确保访问期间不会被销毁
        struct task_struct *task = get_task_struct(pwq->polling_task);
        if (strstr(task->comm, "process1")) {
            pr_info("LWB %s:%d__pollwake: waking task current: %s (pid %d)\n", __func__, __LINE__, current->comm, current->pid);
            WARN_ON(!pwq || !task);
            // 用%ps安全打印进程名,替代直接访问comm
            pr_info("LWB DONE %ps \n", task);
        }
        // 释放引用
        put_task_struct(task);
    }
    
    int rtn = default_wake_function(&dummy_wait, mode, sync, key);
    
    // 后续的判断块也需要同样的引用保护,这里不再重复
    if (wait->private && (wait->func == pollwake) && pwq->polling_task) {
        struct task_struct *task = get_task_struct(pwq->polling_task);
        if (strstr(task->comm, "process1")) {
            pr_info("LWB %s:%d__pollwake: waking task current: %s (pid %d)\n", __func__, __LINE__, current->comm, current->pid);
            WARN_ON(!pwq || !task);
            pr_info("LWB DONE %ps\n", task);
        }
        put_task_struct(task);
    }
    
    return rtn;
}

额外提醒

内核代码中任何涉及跨临界区的对象访问都要警惕竞态:

  • 像task_struct这种生命周期受外部调度影响的对象,必须用引用计数或锁来保护;
  • 避免直接访问结构体的字符串成员进行打印,优先使用内核提供的安全格式符。

内容的提问来源于stack exchange,提问作者Henry Leong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 12:52:32