Linux平台上的mmap查询

Gop*_*ath 2 memory-management mmap linux-device-driver linux-kernel

在 Linux 机器上,尝试编写驱动程序并尝试将一些内核内存映射到应用程序以提高性能。在线检查 mmap 的驱动程序实现,找到不同的实现方式。根据手册页,mmap - 在调用进程的虚拟地址空间中创建新的映射。

1)mmap调用时谁分配物理地址空间?内核还是设备驱动程序?

看到以下各种驱动程序 mmap 实现。

a) 驱动程序创建连续的物理内核内存并将其映射到进程地址空间。

static int driver_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long size = vma->vm_end - vma->vm_start;

    pos = kmalloc(size); //allocate contiguous physical memory.
    while (size > 0) {
        unsigned long pfn;
        pfn = virt_to_phys((void *) pos) >> PAGE_SHIFT; // Get Page frame number
        if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED)) // creates mapping
            return -EAGAIN;
        start += PAGE_SIZE;
        pos += PAGE_SIZE;
        size -= PAGE_SIZE;
    }
}
Run Code Online (Sandbox Code Playgroud)

b) 驱动程序创建虚拟内核内存并将其映射到进程地址空间。

static struct vm_operations_struct dr_vm_ops = {
    .open = dr_vma_open,
    .close = dr_vma_close,
};
static int driver_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long size = vma->vm_end - vma->vm_start;
    void *kp = vmalloc(size);
    unsigned long up;

    for (up = vma->vm_start; up < vma->vm_end; up += PAGE_SIZE) {
        struct page *page = vmalloc_to_page(kp); //Finding physical page from virtual address
        err = vm_insert_page(vma, up, page); //How is it different from remap_pfn_range?
        if (err)
            break;
        kp += PAGE_SIZE;
    }

    vma->vm_ops = &dr_vm_ops;
    ps_vma_open(vma);
}
Run Code Online (Sandbox Code Playgroud)

c) 不确定在这种情况下谁分配内存。

static int driver_mmap(struct file *filp, 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)) // creates mapping
        return -EAGAIN;
}
Run Code Online (Sandbox Code Playgroud)

2)如果内核为mmap分配内存,a&b情况下不是浪费内存吗?

3) remap_pfn_range 用于映射多个页面,而 vm_insert_page 仅用于单页面映射。这是这两个 API 的唯一区别吗?

谢谢你,

戈皮纳斯。

Gil*_*ton 5

您使用哪个取决于您想要完成的任务。

(1) 设备驱动程序是内核的一部分,因此以这种方式区分并没有真正的意义。对于这些情况,设备驱动程序要求从整个内核可用的(物理)内存资源中分配内存供自己使用。

对于(a),正在分配物理上连续的空间。如果有某个外部硬件(例如 PCI 设备)将读取或写入该内存,则可以执行此操作。返回值kmalloc已经有到内核虚拟地址空间的映射。remap_pfn_range也用于将页面映射到当前进程的用户虚拟地址空间。

对于 (b),分配的是虚拟连续的空间。如果不涉及外部硬件,这就是您通常使用的。仍然有物理内存被分配给您的驱动程序,但不能保证这些页面在物理上是连续的——因此对可以分配哪些页面的限制较少。(它们在内核虚拟地址空间中仍然是连续的。)然后您只需使用不同的 API 来实现到用户虚拟地址空间的相同类型的映射。

对于 (c),被映射的内存是在某些其他子系统的控制下分配的。该vm_pgoff字段已设置为资源的基本物理地址。例如,内存可能对应于 PCI 设备的地址区域(例如网络接口控制器的寄存器),其中物理地址由您的 BIOS(或您的机器使用的任何机制)确定/分配。

(2) 不确定我是否理解这个问题。如果设备驱动程序和协作的用户进程正在使用内存,那么内存怎么会被“浪费”呢?而如果内核需要读写内存,就必须分配内核虚拟地址空间,并且需要将其映射到底层物理内存。同样,如果用户空间进程要访问内存,则必须分配用户虚拟地址空间,并且也必须将映射到物理内存。

“分配虚拟地址空间”本质上只是指为内存分配页表项。这是与实际分配物理内存分开完成的。并且它是针对内核空间和用户空间分别完成的。而“映射”就是设置页表项(页开头的虚拟地址)指向正确的物理页地址。

(3)是的。它们是不同的 API,但完成的事情大致相同。有时你有一个struct page,有时你有一个pfn。这可能会令人困惑:通常有多种方法可以完成同一件事。开发人员通常使用他们已经拥有的项目最明显的一个(“我已经有一个struct page。我可以计算它pfn。但是当有另一个接受 的 API 时为什么要这样做struct page?”)。