这就是IOUserClient::CopyClientMemoryForType
DriverKit 的用途,当您的用户进程IOConnectMapMemory64
从IOKit.framework
. 顺便说一下,kext 等价物的IOUserClient::clientMemoryForType
工作原理完全相同。
要使其工作,您需要覆盖CopyClientMemoryForType
用户客户端子类中的虚函数。
在类定义中.iig
:
virtual kern_return_t CopyClientMemoryForType(
uint64_t type, uint64_t *options, IOMemoryDescriptor **memory) override;
在实现.cpp
中,大致如下:
kern_return_t IMPL(MyUserClient, CopyClientMemoryForType) //(uint64_t type, uint64_t *options, IOMemoryDescriptor **memory)
{
kern_return_t res;
if (type == 0)
{
IOBufferMemoryDescriptor* buffer = nullptr;
res = IOBufferMemoryDescriptor::Create(kIOMemoryDirectionInOut, 128 /* capacity */, 8 /* alignment */, &buffer);
if (res != kIOReturnSuccess)
{
os_log(OS_LOG_DEFAULT, "MyUserClient::CopyClientMemoryForType(): IOBufferMemoryDescriptor::Create failed: 0x%x", res);
}
else
{
*memory = buffer; // returned with refcount 1
}
}
else
{
res = this->CopyClientMemoryForType(type, options, memory, SUPERDISPATCH);
}
return res;
}
在用户空间中,您会调用:
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
IOReturn res = IOConnectMapMemory64(connection, 0 /*memoryType*/, mach_task_self(), &address, &size, kIOMapAnywhere);
对此的一些说明:
- 参数中的值
type
来自导致调用此函数的调用的memoryType
参数。IOConnectMapMemory64
因此,您的驱动程序可以有某种编号约定;在最简单的情况下,您可以将其与外部方法中的选择器类似地对待。
memory
实际上是一个输出参数,当函数返回kIOReturnSuccess
. 该函数具有复制语义,即调用者希望获得内存描述符的所有权,即当不再需要引用计数时,它最终会将引用计数减 1。返回的内存描述符不必IOBufferMemoryDescriptor
像我在示例中使用的那样,它也可以是 PCI BAR 或其他。
- 调用中的
kIOMapAnywhere
选项IOConnectMapMemory64
很重要,通常是您想要的:如果您不指定此选项,则该atAddress
参数将成为输入输出参数,并且调用者应在地址空间中选择驱动程序内存应映射的位置. 通常你不关心它在哪里,如果那里已经映射了一些东西,那么指定一个明确的位置确实是危险的。
- 如果用户空间不能写入映射内存,则相应
options
地设置参数CopyClientMemoryForType
:*options = kIOUserClientMemoryReadOnly;
要销毁映射,用户空间进程必须调用IOConnectUnmapMemory64()
.