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

如何用mmap()映射设备IO内存到用户空间?兼析LDD3代码差异

关于《Linux设备驱动程序第三版》(LDD3)中mmap()两段代码的疑问解答

第一段代码的映射原理与映射对象

先看这段代码:

static int simple_remap_mmap(struct file *file, struct vm_area_struct *vma)
{
    if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
                vma->vm_end - vma->vm_start,
                vma->vm_page_prot))
        return -EAGAIN;

    vma->vm_ops = &simple_remap_vm_ops;
    simple_vma_open(vma);
    return 0;
}
  • 物理地址的传递方式:这段代码并非没用到物理地址,而是通过用户态mmap调用的offset参数间接传入。内核会把用户传入的offset右移PAGE_SHIFT位(转换为页框号PFN),存到vma->vm_pgoff里。remap_pfn_range的第三个参数就是这个PFN,对应设备IO内存的物理地址(物理地址 = PFN << PAGE_SHIFT)。
  • 映射对象:这段代码专门用来映射设备IO物理内存,比如外设寄存器、显存这类独立于系统RAM的硬件地址区域。这类地址不在内核的常规页管理体系中,所以可以直接通过PFN映射到用户空间。而普通RAM的物理页受内核页管理跟踪,直接用remap_pfn_range映射会破坏页表一致性,因此不能用这段代码映射RAM。

两段代码的差异原因

第二段代码如下:

static struct vm_operations_struct simple_remap_vm_ops = {
    .open =  simple_vma_open,
    .close = simple_vma_close,
};

static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int rv;
    size_t len = vma->vm_end - vma->vm_start;
    struct page *pages;

    /*
     * To prevent from memory over run. Limit the maximux mmap size here
     * to 10 * PAGE_SIZE
     */
    if (len >> PAGE_SHIFT > 10) {
        pr_err("Map size overflow! len=%ld pages\n", len >> PAGE_SHIFT);
        return -EFAULT;
    }

    /*
     * Allocate necessary pages and initialize it properly.
     * We initialize all memory to a special value: 0xdeadbeef
     */
    pages = alloc_pages(GFP_KERNEL, get_order(len));
    memset32(page_address(pages), 0xdeadbeef,
         (1 << get_order(len)) * PAGE_SIZE / 4);

    rv = remap_pfn_range(vma, vma->vm_start, page_to_pfn(pages),
                vma->vm_end - vma->vm_start,
                vma->vm_page_prot);
    if (rv) {
        return -EAGAIN;
    }

    pr_debug("vm_start: %#lx, vm_end: %#lx\n, len: %#lx, vm_pgoff: %#lx(%#lx), ops=%p\n",
         vma->vm_start, vma->vm_end, len,
         vma->vm_pgoff, vma->vm_pgoff << PAGE_SHIFT,
         vma->vm_ops);

    vma->vm_ops = &simple_remap_vm_ops;
    simple_vma_open(vma);

    return 0;
}

两段代码的核心差异在于映射对象完全不同:

  1. 第一段是映射预先存在的硬件IO物理内存:这类内存是硬件自带的固定物理地址区域,不需要内核分配,只需要建立虚拟地址到物理地址的页表映射即可。
  2. 第二段是映射内核动态分配的系统RAM:这里的目标是给用户空间提供一块共享内存,所以必须先通过alloc_pages从内核页分配器申请物理页,初始化后再把这些页的PFN(通过page_to_pfn获取)映射到用户空间。

简单来说,第一段是“映射已有硬件地址”,第二段是“先分配内存再映射”,这就是为什么第二段需要显式分配页面,而第一段不需要的根本原因。

内容的提问来源于stack exchange,提问作者bielu000

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:15:22