Linux内核6.6.6x中__pollwake函数内pr_info打印触发内核Panic问题排查
问题分析与修复方案
这是典型的内核竞态条件导致的内存非法访问——虽然你在if判断时成功访问了pwq->polling_task->comm,但从判断到执行pr_info的间隙,目标进程的task_struct已经被销毁或内存被复用,导致后续访问失效。
为什么会出现这种“前后不一致”的崩溃?
崩溃根源:从调用栈可以看到,崩溃发生在
string_nocheck函数(属于vsnprintf处理%s的逻辑),说明此时pwq->polling_task->comm指向的内存已经不再是合法的struct task_struct成员:- 要么对应的进程已经退出,
task_struct的引用计数归0,被内核回收进slab缓存; - 要么这块内存已经被其他内核对象复用改写。
- 要么对应的进程已经退出,
为什么
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期间,它不会被销毁,同时使用内核安全的打印方式:
- 增加
task_struct的引用计数:
用get_task_struct()获取进程引用,确保在你使用期间,task_struct不会被回收;使用完成后用put_task_struct()释放引用。 - 使用安全的进程名打印格式:
内核提供了%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
相关产品推荐
相关产品推荐

