内核vmalloc内存映射至用户空间的实现及故障排查
用户态与内核态共享内存的问题及修复
核心需求
需在用户态与内核态间共享大内存空间,因kmalloc分配连续物理内存易出现不足,选择vmalloc分配非连续内存,但映射到用户态时遇到页错误等问题。
当前代码与错误信息
自定义mmap代码片段
{ int ret; unsigned long size = vma->vm_end - vma->vm_start; pr_info("vma->vm_start: %p\n", vma->vm_start); pr_info("vma->vm_end: %p\n", vma->vm_end); pr_info("memory: %p\n", memory); unsigned long pfn = vmalloc_to_pfn(k_qp); pr_info("pfn: %ld\n", pfn); if (size > PAGE_SIZE) { pr_err("size too large\n"); return -EINVAL; } ret = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); pr_info("remap_pfn_range ret: %d\n", ret); return ret; }
调试错误日志
[28288.407882] memory: 00000000a60b865d [28288.409341] vma->vm_start: 140257261522944 [28288.409351] vma->vm_end: 140257261527040 [28288.409362] pfn: 406657 [28288.414843] BUG: unable to handle page fault for address: ffffe9ff0b79a488 [28288.414856] #PF: supervisor read access in kernel mode [28288.414865] #PF: error_code(0x0000) - not-present page [28288.414873] PGD 0 P4D 0 [28288.414904] Oops: 0000 [#5] SMP NOPTI
错误原因分析
- 参数传递错误:代码中调用
vmalloc_to_pfn(k_qp),但实际vmalloc分配的内存指针是memory,传错参数导致获取的pfn对应无效虚拟地址,后续映射逻辑完全错误。 - vmalloc地址处理不当:
vmalloc_to_pfn要求输入地址必须是vmalloc分配的页对齐地址,若传入非对齐地址或无效地址,会返回错误的pfn值,进而触发页表访问异常。 - 页表访问失效:错误地址
ffffe9ff0b79a488属于vmalloc地址区间,但对应的页表项未建立,内核访问时触发页错误。
解决方案
一、vmalloc内存的逐页映射方法
vmalloc分配的是离散物理页,需遍历每个页单独映射:
- 按
PAGE_SIZE拆分vmalloc内存区间 - 用
vmalloc_to_page获取对应页的struct page指针 - 通过
page_to_pfn得到页帧号,或直接用vm_insert_page完成映射
二、无需转换物理地址的替代方案
使用vm_insert_page函数,直接将内核struct page关联到用户态VMA,无需手动计算pfn,该函数会自动处理页表建立逻辑,更适合离散物理页的场景。
修复后的代码示例
static int custom_mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long size = vma->vm_end - vma->vm_start; unsigned long kernel_addr = (unsigned long)memory; // memory为vmalloc分配的有效指针 unsigned long offset = 0; struct page *page; extern size_t alloc_size; // 提前保存的vmalloc分配总大小 pr_info("vma range: %p - %p, size: %lu\n", vma->vm_start, vma->vm_end, size); pr_info("vmalloc addr: %p\n", (void*)kernel_addr); // 校验请求大小是否超过内核分配的内存总量 if (size > alloc_size) { pr_err("request size exceeds allocated memory\n"); return -EINVAL; } // 逐页映射到用户态VMA while (offset < size) { page = vmalloc_to_page((void*)(kernel_addr + offset)); if (!page) { pr_err("failed to get page for addr %p\n", (void*)(kernel_addr + offset)); return -EFAULT; } if (vm_insert_page(vma, vma->vm_start + offset, page)) { pr_err("vm_insert_page failed at offset %lu\n", offset); return -EFAULT; } offset += PAGE_SIZE; } // 设置VMA属性,允许读写与共享 vma->vm_flags |= VM_SHARED | VM_MAYREAD | VM_MAYWRITE; return 0; }
关键注意事项
- 确保
memory是vmalloc返回的有效指针,且未被提前释放 vmalloc_to_page仅对vmalloc分配的地址有效,不可用于kmalloc或栈内存- 用户态调用
mmap时,请求大小最好是PAGE_SIZE的整数倍,避免部分页映射的异常 - 需正确管理内存生命周期,禁止在用户态仍在使用时释放内核侧内存
内容的提问来源于stack exchange,提问作者Sandw1tchzs
相关产品推荐
相关产品推荐

