Linux中pinned页如何实现固定?get_user_pages固定DMA内存存疑
嘿,我太懂这种困惑了!当初我第一次用get_user_pages给DMA固定内存的时候,也盯着页标志找了半天“锁定”状态,结果啥也没看着——后来才搞明白,get_user_pages的“固定”逻辑和咱们想的页锁完全不是一回事儿。
为啥看不到预期的页标志?
首先得掰扯清楚:get_user_pages所谓的“固定”,不是靠设置PG_locked标志。那个标志是给页级同步用的(比如page_lock()锁住页防止并发修改),和DMA固定内存的目的完全不搭边。
get_user_pages真正做的事情是:
- 把用户态的虚拟地址映射到物理页,把页从swap里拉出来(如果之前被换走了)
- 提升物理页的引用计数,告诉内核“这页我正在用,别回收也别换走”
- 如果你调用时加了
FOLL_LONGTERM参数,还会把页加入进程的mlock列表,标记为长期不可回收(这时候page_mlocked(page)会返回true)
所以你看,它根本不会动PG_locked,自然和普通活跃页的标志没啥区别。
那怎么证明固定确实生效了?
这里给你几个实用的验证方法:
1. 检查物理页的引用计数
调用get_user_pages成功后,每个被固定的页的引用计数都会+1(内核用这个来跟踪谁在使用页)。你可以在驱动里打印这个值:
#define NR_PAGES 4 struct page *pages[NR_PAGES]; unsigned long addr = ...; // 用户态地址 int ret; ret = get_user_pages(current, current->mm, addr, NR_PAGES, FOLL_WRITE | FOLL_LONGTERM, pages, NULL, NULL); if (ret > 0) { for (int i = 0; i < ret; i++) { pr_info("Page %d: 引用计数 = %d, 是否长期锁定 = %d\n", i, page_count(pages[i]), page_mlocked(pages[i])); } // 用完别忘了释放引用! for (int i = 0; i < ret; i++) { put_page(pages[i]); } }
对比调用get_user_pages前后的计数(如果能提前拿到页的话),或者看调用后计数是否比基础值高——基础值一般是1(页本身存在),固定后会变成2。
2. 强制触发内存回收,看页会不会被换走
如果固定生效了,这些页绝对不会被内核回收或交换。你可以这么测试:
- 先让驱动固定好内存
- 用
echo 3 > /proc/sys/vm/drop_caches强制回收页缓存 - 再用
stress-ng --vm 4 --vm-bytes 80%压内存,触发swap - 然后看对应进程的
/proc/$PID/smaps文件,找到你固定的VMA区域,检查Swap:字段——如果固定成功,这个值应该是0,而且不会增长。
3. 检查页的长期锁定状态(如果用了FOLL_LONGTERM)
如果你调用get_user_pages时加了FOLL_LONGTERM参数,内核会把这些页标记为mlocked,这时候可以用page_mlocked(page)函数来检查,返回true就说明页被长期固定了。另外也可以看/proc/$PID/maps,对应的内存区域会有mlock标记。
最后说句实在话
其实你的功能已经正常运行,这本身就是最有力的证明——如果内存没被正确固定,DMA操作早就因为物理地址变化而崩溃了。那些页标志的“没变化”,只是因为咱们对“固定”的理解和内核的实现逻辑不一样而已。
内容的提问来源于stack exchange,提问作者Brad

