【发布时间】:2018-01-11 18:45:46
【问题描述】:
我想知道是否存在用于访问获取虚拟地址的物理地址的现有系统调用/API? 如果没有,那么关于如何使其工作的一些方向?
另外,MMIO的物理地址是不可分页的物理内存,如何获取?
【问题讨论】:
标签: macos kernel virtual-memory kernel-extension
我想知道是否存在用于访问获取虚拟地址的物理地址的现有系统调用/API? 如果没有,那么关于如何使其工作的一些方向?
另外,MMIO的物理地址是不可分页的物理内存,如何获取?
【问题讨论】:
标签: macos kernel virtual-memory kernel-extension
答案在于IOMemoryDescriptor 和IODMACommand 对象。
如果有问题的内存是内核分配的,则应首先通过创建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。
【讨论】:
IODMACommand API,但您还没有完全弄清楚您想要哪个,所以我不得不猜测。