mmap实际上返回了什么?

时间:2015-01-03 20:48:49

标签: c linux-kernel linux-device-driver

我对如何定义mmap调用实际返回的地址感到有点困惑,因为我看到了一段代码,其中这个地址被转换为uint64_t并用作物理地址。 如果它是一个虚拟地址,并且我们需要一个物理地址,可以使用一个公式找到它,其中在打开proc / self / map后涉及带有pagesize等的模数。只是抽象它。

无论是否有大页面,我们如何对待此地址都很重要?

此外,如果哪种地址适合被归类为DMA-ABLE地址。通过代码示例,在内核中我们使用pci_alloc_consistentpci_map_single来查找dma地址。假设在用户空间应用程序中,我希望dma TO / FROM设备并通过malloc或mmap为其tx和rx环分配一部分内存,我想要与此地址关联的物理地址。我应该只使用类型转换uint64_t(addr)还是编写一个函数将此虚拟地址转换为等效于pci alloc consistent返回的dma句柄的物理地址。

从开源DPDK代码添加样本

    mcfg = rte_eal_get_configuration()->mem_config;

/* hugetlbfs can be disabled */
if (internal_config.no_hugetlbfs) {
    addr = mmap(NULL, internal_config.memory, PROT_READ | PROT_WRITE,
            MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    if (addr == MAP_FAILED) {
        RTE_LOG(ERR, EAL, "%s: mmap() failed: %s\n", __func__,
                strerror(errno));
        return -1;
    }
    mcfg->memseg[0].phys_addr = (phys_addr_t)(uintptr_t)addr;  -----???
    mcfg->memseg[0].addr = addr;
    mcfg->memseg[0].len = internal_config.memory;
    mcfg->memseg[0].socket_id = SOCKET_ID_ANY;
    return 0;
}

在另一种情况下,它会通过此方式将从大页面映射的地址转换为物理地址。

    /*
 * Get physical address of any mapped virtual address in the current process.
 */
phys_addr_t
rte_mem_virt2phy(const void *virtaddr)
{
    int fd;
    uint64_t page, physaddr;
    unsigned long virt_pfn;
    int page_size;
    off_t offset;

    /* standard page size */
    page_size = getpagesize();

    fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        RTE_LOG(ERR, EAL, "%s(): cannot open /proc/self/pagemap: %s\n",
            __func__, strerror(errno));
        return RTE_BAD_PHYS_ADDR;
    }

    virt_pfn = (unsigned long)virtaddr / page_size;
    offset = sizeof(uint64_t) * virt_pfn;
    if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
        RTE_LOG(ERR, EAL, "%s(): seek error in /proc/self/pagemap: %s\n",
                __func__, strerror(errno));
        close(fd);
        return RTE_BAD_PHYS_ADDR;
    }
    if (read(fd, &page, sizeof(uint64_t)) < 0) {
        RTE_LOG(ERR, EAL, "%s(): cannot read /proc/self/pagemap: %s\n",
                __func__, strerror(errno));
        close(fd);
        return RTE_BAD_PHYS_ADDR;
    }

    /*
     * the pfn (page frame number) are bits 0-54 (see
     * pagemap.txt in linux Documentation)
     */
    physaddr = ((page & 0x7fffffffffffffULL) * page_size)
        + ((unsigned long)virtaddr % page_size);
    close(fd);
    return physaddr;
}

3 个答案:

答案 0 :(得分:1)

mmap()返回指向新映射内存的指针。该指针指向程序看到的地址空间,可以像void*类型的任何其他指针一样使用。如果失败,mmap()会返回MAP_FAILED,这是一个通常值为(void*)-1的常量。

答案 1 :(得分:1)

正如其他人已经回答的那样,mmap()会返回一个虚拟地址。如果您需要物理地址(例如用户空间dma驱动程序),则可以转换 通过读取/ proc / self / pagemap和执行页面框架来虚拟到物理使用 数字数学,例如引用的DPDK例子。您需要注意几个警告:

  • 根据内核版本,页面帧编号位可能有效,也可能无效(有关详细信息,请查看Documentation / vm / pagemap.txt)
  • 单独的mmap操作可能不足以让内核保留物理内存。访问内存(通过read | write | lock)会导致页面错误,提示内核保留物理内存。
  • 如果您需要多个页面大小的内存,则可能需要更多工作 - 例如使用大页面或实现类似DPDK按物理地址对内存进行排序的操作,然后重新映射虚拟地址以便物理和虚拟内存都是连续的。
  • 为了使虚拟到物理映射保持有效,内核不得将页面换出内存。 DPDK通过每个页面的mlock()来处理这个问题。

答案 2 :(得分:0)

mmap()返回由remap_pfn_range为特定物理地址和长度创建的虚拟地址。 您可以使用virt_to_phys()获取物理地址。当然,这与像页面大小等模数相同,但完全依赖于体系结构,因为MMU在体系结构中是主观的。

总线地址用于将数据DMA到硬件。 pci_alloc_consistent等函数返回两个地址。 1.虚拟地址 2.公交车地址。

您的驱动程序可以使用虚拟内存寻址一大块RAM。因为MMU在CPU执行该指令时转换virt_to_phys。但在DMA操作期间,CPU被旁路,设备无法使用虚拟地址进行寻址。该设备插在连接到总线的接口(如PCI插槽)上(如PCI总线)。因此它只能使用总线地址来寻址。 使用总线地址的事实条件是不应该换出相应的存储器页面。 pci_alloc_consistent就是这么做的。 只要我们有一致的映射,我们就可以毫无问题地进行DMA。

uint64_t(addr)不会从虚拟地址中获取物理地址。而是使用virt_to_phys()。在这种情况下,当你可以使用virt和bus地址解决时,我不明白为什么你需要一个物理地址。如果需要,请使用它。