【问题标题】:How to get the physical address in macosx kernel for a virtual address for a particular process?如何在 macosx 内核中获取特定进程的虚拟地址的物理地址?
【发布时间】:2018-01-11 18:45:46
【问题描述】:

我想知道是否存在用于访问获取虚拟地址的物理地址的现有系统调用/API? 如果没有,那么关于如何使其工作的一些方向?

另外,MMIO的物理地址是不可分页的物理内存,如何获取?

【问题讨论】:

    标签: macos kernel virtual-memory kernel-extension


    【解决方案1】:

    答案在于IOMemoryDescriptorIODMACommand 对象。

    如果有问题的内存是内核分配的,则应首先通过创建IOBufferMemoryDescriptor 来分配它。如果这是不可能的,或者如果它是在用户空间中分配的缓冲区,则可以使用 IOMemoryDescriptor::withAddressRange(address, length, options, task) 或其他工厂函数之一包装相关指针。在withAddressRange的情况下,传入的address必须是虚拟的,在task的地址空间中。

    您可以通过调用getPhysicalSegment() 函数直接从IOMemoryDescriptor 中获取物理地址范围(仅在prepare()complete() 调用之间有效)。但是,通常您会这样做以创建分散-聚集列表 (DMA),为此,Apple 强烈建议使用 IODMACommand。您可以使用IODMACommand::withSpecification() 创建这些。然后使用genIOVMSegments()函数生成分散-聚集列表。

    现代 Mac 和一些旧的 PPC G5 包含一个 IOMMU(英特尔称之为 VT-d),因此您传递给 PCI/Thunderbolt 设备的系统内存地址实际上不是物理的,而是 IO-Mapped。 IODMACommand 将为您执行此操作,只要您使用“系统映射器”(默认)并将mappingOptions 设置为kMapped。如果您正在为 CPU 而不是设备准备地址,则需要关闭映射 - 在 IOMemoryDescriptor 选项中使用 kIOMemoryMapperNone。根据您要执行的操作,在这种情况下您可能也不需要IODMACommand

    注意:汇集和重用您的 IODMACommand 对象通常是明智的做法,而不是释放和重新分配它们。

    关于 MMIO,我假设您的意思是 PCI BAR 和类似的 - 对于 IOPCIDevice,您可以使用 getDeviceMemoryWithRegister() 和类似函数获取代表内存映射设备范围的 IOMemoryDescriptor

    示例:

    如果您想要的只是某个任务中给定虚拟内存范围的纯 CPU 空间物理地址,您可以执行以下操作(未经测试作为使用它的完整 kext 会相当大):

    // INPUTS:
    mach_vm_address_t virtual_range_start = …; // start address of virtual memory
    mach_vm_size_t virtual_range_size_bytes = …; // number of bytes in range
    task_t task = …; // Task object of process in which the virtual memory address is mapped
    IOOptionBits direction = kIODirectionInOut; // whether the memory will be written or read, or both during the operation
    IOOptionBits options =
        kIOMemoryMapperNone  // we want raw physical addresses, not IO-mapped
        | direction;
    
    // Process for getting physical addresses:
    
    IOMemoryDescriptor* md = IOMemoryDescriptor::withAddressRange(
        virtual_range_start, virtual_range_size_bytes, direction, task);
    // TODO: check for md == nullptr
    
    // Wire down virtual range to specific physical pages
    IOReturn result = md->prepare(direction);
    // TODO: do error handling
    
    IOByteCount offset = 0;
    while (offset < virtual_range_size_bytes)
    {
        IOByteCount segment_len = 0;
        addr64_t phys_addr = md->getPhysicalSegment(offset, &len, kIOMemoryMapperNone);
        // TODO: do something with physical range of segment_len bytes at address phys_addr here
    
        offset += segment_len;
    }
    
    /* Unwire. Call this only once you're done with the physical ranges
     * as the pager can change the physical-virtual mapping outside of
     * prepare…complete blocks. */
    md->complete(direction);
    md->release();
    

    如上所述,这不适用于为设备 I/O 生成 DMA 分散-聚集列表。另请注意,此代码仅对 64 位内核有效。如果您仍需要支持古老的 32 位内核(OS X 10.7 和更早版本),则需要小心,因为虚拟地址和物理地址仍然可以是 64 位(分别为 64 位用户进程和 PAE),但不是所有内存描述符函数都是为此设置的。有 64 位安全变体可用于 32 位 kext。

    【讨论】:

    • 我明白了你所说的大部分内容,我想要虚拟地址到物理地址的一对一映射,你能告诉我什么功能可以帮助我实现吗?所以,在深入研究 IOMemoryDe​​scriptor、pmap、vm_map 时,我还没有找到一个直接的函数来进行翻译
    • @Anurag 您可以使用 IOMemoryDe​​scriptor 来完成。当我回到办公室时,我将添加一个示例。
    • @Anurag 我添加了一个示例,用于获取虚拟范围的纯 CPU 物理地址。对于 (PCI) DMA,您需要改用 IODMACommand API,但您还没有完全弄清楚您想要哪个,所以我不得不猜测。
    • @SteffX DriverKit 有类似的 API,我认为从 macOS 11 开始。
    • @pmdj — 谢谢。验证后,它在 macOS 10.15 中可用,所以目前对我没有帮助。但我很感激你的回答。
    猜你喜欢
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-12
    • 2013-03-10
    • 2019-12-06
    • 2015-05-04
    • 2013-05-05
    相关资源
    最近更新 更多