You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 13:53:17