内核模块访问task->mm->pgd致系统无响应的原因及解决方法
问题原因与解决方法
问题原因
- 缺少进程链表并发保护:内核的
task_struct进程链表是全局共享资源,进程创建、退出、调度等操作会在多CPU环境下并发修改链表。直接通过for_each_process遍历而不持有tasklist_lock自旋锁,会导致遍历过程中链表结构被破坏,触发非法内存访问或死锁,最终系统失去响应。 mm结构访问存在竞态:即使判断了p->mm != NULL,在多核心场景下,目标进程可能正处于退出销毁流程,mm结构随时可能被释放,此时访问p->mm->pgd会触发野指针访问,引发系统崩溃。
解决方法
1. 增加进程链表锁保护
遍历进程链表时必须持有tasklist_lock自旋锁,保证遍历过程中链表结构不会被并发修改:
- 进程上下文(如模块函数调用):使用
read_lock/read_unlock(遍历属于读操作,无需写锁) - 中断上下文:使用
read_lock_irqsave/read_unlock_irqrestore,关闭本地中断并保存状态
2. 安全访问mm结构
通过引用计数或读写锁保证mm结构的稳定性:
- 调用
get_task_struct(p)增加进程引用计数,防止进程在访问期间被销毁,访问完成后用put_task_struct(p)释放引用 - 对于用户态进程(
p->mm != NULL),持有mm->mmap_sem读锁,避免mm结构被并发修改
修改后的代码示例
#include <linux/sched/signal.h> #include <linux/sched.h> #include <linux/mm.h> void testFunc(void) { struct task_struct *p; printk(KERN_INFO "testFunc\n"); pid_t pid = current->pid; // 安全访问当前进程的pgd if (current->mm && current->mm->pgd) { down_read(¤t->mm->mmap_sem); printk(KERN_INFO "current PID - %d, pgd %px\n", pid, current->mm->pgd); up_read(¤t->mm->mmap_sem); } // 持有进程链表读锁,保护遍历流程 read_lock(&tasklist_lock); for_each_process(p) { get_task_struct(p); // 增加进程引用,防止遍历中进程被销毁 printk(KERN_INFO "pid - %d\tpname - %s\n", p->pid, p->comm); if (p->mm && p->mm->pgd) { // 持有mmap_sem读锁,保护mm结构不被并发修改 down_read(&p->mm->mmap_sem); printk(KERN_INFO "pgd: %px\n", p->mm->pgd); up_read(&p->mm->mmap_sem); } put_task_struct(p); // 释放进程引用 } read_unlock(&tasklist_lock); }
内容的提问来源于stack exchange,提问作者DeemoAlice
相关产品推荐
相关产品推荐

