vmalloc非连续物理内存映射用户空间触发总线错误排查
问题分析与解决
核心错误原因
vmalloc()分配的虚拟地址属于内核的非连续虚拟地址区,这类地址不在直接映射范围内,不能用virt_to_phys()直接转换为物理地址——这个函数仅对kmalloc()/__get_free_pages()分配的直接映射虚拟地址有效。如果错误地用virt_to_phys()处理vmalloc地址,得到的物理地址是无效的,最终导致用户态访问错误的物理页,触发总线错误。
关键修正点
要映射vmalloc分配的内存到用户空间,需按以下步骤修正:
- 用
vmalloc_to_page()获取vmalloc虚拟地址对应的struct page,而非virt_to_phys() - 通过
page_to_pfn()从struct page得到有效页帧号(PFN) - 循环调用
remap_pfn_range时,确保每页的虚拟地址、PFN、长度参数准确匹配
修正后的驱动核心代码
my_mmap函数示例
static int my_mmap(struct file *filp, struct vm_area_struct *vma) { void *vmalloc_buf = filp->private_data; // 保存的vmalloc分配地址 unsigned long vma_base = vma->vm_start; unsigned long vmalloc_vaddr = (unsigned long)vmalloc_buf; const int PAGE_COUNT = 16; // 校验映射长度是否匹配 if (vma->vm_end - vma->vm_start != PAGE_COUNT * PAGE_SIZE) return -EINVAL; // 逐个映射每页 for (int i = 0; i < PAGE_COUNT; i++) { struct page *page = vmalloc_to_page((void *)(vmalloc_vaddr + i * PAGE_SIZE)); if (!page) return -ENOMEM; unsigned long pfn = page_to_pfn(page); // 执行映射,使用vma自带的权限属性 if (remap_pfn_range(vma, vma_base + i * PAGE_SIZE, pfn, PAGE_SIZE, vma->vm_page_prot)) { return -EAGAIN; } } return 0; }
额外注意事项
- 页面保留:
vmalloc()分配的页面默认已被内核标记为保留,无需手动调用SetPageReserved - 权限一致性:驱动中不要修改
vma->vm_page_prot,保持与用户态mmap传入的权限(如PROT_READ|PROT_WRITE)一致 - 地址对齐:
vmalloc返回的地址本身是页对齐的,循环时按PAGE_SIZE偏移即可
用户态测试代码验证
确保mmap长度与驱动分配的16页一致:
#include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #define PAGE_COUNT 16 #define MAP_SIZE (PAGE_COUNT * PAGE_SIZE) int main() { int fd = open("/dev/vmalloc_mapper", O_RDWR); if (fd < 0) { perror("open failed"); return 1; } char *mapped_addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mapped_addr == MAP_FAILED) { perror("mmap failed"); close(fd); return 1; } printf("Mapped address: %p\n", mapped_addr); // 读取测试数据示例 for (int i = 0; i < 32; i++) { printf("%02x ", mapped_addr[i]); if ((i + 1) % 16 == 0) printf("\n"); } munmap(mapped_addr, MAP_SIZE); close(fd); return 0; }
内容的提问来源于stack exchange,提问作者jon doe
相关产品推荐
相关产品推荐

