Linux内核6.1.x加载内核模块时遍历虚拟内存的方法
内核6.1+遍历进程VMAs计算虚拟地址空间总大小的解决方法
内核6.1版本对vm_area_struct结构体做了重构,移除了旧的vm_next/vm_prev指针,改用struct list_head vm_node链表节点管理VMA的链接关系,直接导致你原来的遍历代码编译失败。下面给出两种可行的遍历方案,以及修改后的完整代码。
核心修改点
原来的for (vma = mm->mmap_base; vma; vma = vma->vm_next)遍历方式已失效,需要替换为基于链表的遍历逻辑,优先选择内核封装的宏或标准链表迭代器。
方案一:使用内核推荐的mm_for_each_vma宏(优先选择)
内核在<linux/mm.h>中提供了mm_for_each_vma宏,专门用于遍历进程的所有VMA,宏会自动处理链表遍历细节,兼容性和可维护性更好。
替换后的遍历代码:
mm_for_each_vma(vma, mm) { virt_size += vma->vm_end - vma->vm_start; }
方案二:直接遍历mm->mmap链表
如果需要手动操作链表,可以使用list_for_each_entry迭代器,遍历挂在mm->mmap上的所有vm_area_struct节点:
list_for_each_entry(vma, &mm->mmap, vm_node) { virt_size += vma->vm_end - vma->vm_start; }
完整修改后的代码
同时要添加mm_struct和task_struct的释放逻辑,避免内存泄漏:
static int __init init(void) { struct task_struct *task; struct mm_struct *mm; struct vm_area_struct *vma; unsigned long virt_size = 0; // 此处省略获取目标进程task_struct的代码,例如通过find_get_task_by_vpid(target_pid)获取 ... mm = get_task_mm(task); if (!mm) { printk(KERN_ERR "Failed to get mm_struct for PID %d\n", target_pid); put_task_struct(task); return -EINVAL; } // 使用mm_for_each_vma宏遍历所有VMA并计算总大小 mm_for_each_vma(vma, mm) { virt_size += vma->vm_end - vma->vm_start; } printk(KERN_INFO "PID: %d, Allocated Virtual Address Space: %lu KB\n", target_pid, virt_size >> 10); // 释放引用的mm_struct和task_struct,避免资源泄漏 mmput(mm); put_task_struct(task); return 0; }
关键注意事项
- 引用计数管理:
get_task_mm会增加mm_struct的引用计数,必须调用mmput(mm)释放;如果通过find_get_task_by_vpid等接口获取了task_struct,也要用put_task_struct释放。 - 空指针检查:获取
mm后必须检查是否为NULL,避免后续空指针访问。 - 兼容性:
mm_for_each_vma宏是内核推荐用法,后续内核版本如果对VMA链表结构有调整,这个宏会同步适配,无需修改业务代码。
内容的提问来源于stack exchange,提问作者Soumik Dutta
相关产品推荐
相关产品推荐

