如何在 DriverKit 系统扩展中分配内存并将其映射到另一个进程?
How to allocate memory in a DriverKit system extension and map it to another process?
我已经在我的应用程序中分配了内存并将其指针和大小传递给 IOConnectCallStructMethod
。使用 IOMemoryDescriptor::CreateMapping
然后我将这个内存映射到 DriverKit 系统扩展进程,并且可以写入这个映射的内存位置并从我的应用程序读取数据。
我现在想对系统扩展中分配的内存做类似的事情,然后将其映射到使用系统扩展的应用程序。我想在系统扩展中创建一组内存缓冲区,然后从应用程序写入它,然后使用 IOConnectCallScalarMethod
向系统扩展发出信号,表明应该将给定缓冲区发送到 USB 设备,使用 IOUSBHostPipe::AsyncIO
. When the CompleteAsyncIO
回调是发送完成的结果,我会通知应用程序现在可以将数据复制到发送的第一个缓冲区。此机制可能可以使用 IOConnectCallAsyncStructMethod
和在系统扩展中创建的 OSAction
对象来完成。我不明白的是如何将系统扩展中分配的内存映射到应用程序。
这就是 IOUserClient::CopyClientMemoryForType
in DriverKit is for, which gets invoked when your user process calls IOConnectMapMemory64
from IOKit.framework
. The kext equivalent, incidentally, is IOUserClient::clientMemoryForType
并且本质上完全相同。
要使其正常工作,您需要覆盖用户客户端子程序中的 CopyClientMemoryForType
虚函数class。
在.iig
中的class定义中:
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;
}
在用户 space 中,您将调用:
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
调用。因此,您的驱动程序可以有某种编号约定;在最简单的情况下,您可以像 select 或外部方法一样对待它。
memory
实际上是一个输出参数,这是你期望 return 当你的函数 return 映射到用户 space 的内存描述符的地方]s kIOReturnSuccess
。该函数具有复制语义,即调用者希望获得内存描述符的所有权,即当不再需要时,它最终会将引用计数减 1。 returned 内存描述符不必是我在示例中使用的 IOBufferMemoryDescriptor
,它也可以是 PCI BAR 或其他任何东西。
IOConnectMapMemory64
调用中的 kIOMapAnywhere
选项很重要,通常是您想要的:如果您不指定它,atAddress
参数将成为输入输出参数,并且调用者应该 select 地址 space 中驱动程序内存应该被映射的位置。通常你不关心它在哪里,如果已经有东西映射到那里,那么指定一个明确的位置确实是危险的。
- 如果用户 space 不能写入映射内存,请相应地将
options
参数设置为 CopyClientMemoryForType
:*options = kIOUserClientMemoryReadOnly;
要销毁映射,用户 space 进程必须调用 IOConnectUnmapMemory64()
。
我已经在我的应用程序中分配了内存并将其指针和大小传递给 IOConnectCallStructMethod
。使用 IOMemoryDescriptor::CreateMapping
然后我将这个内存映射到 DriverKit 系统扩展进程,并且可以写入这个映射的内存位置并从我的应用程序读取数据。
我现在想对系统扩展中分配的内存做类似的事情,然后将其映射到使用系统扩展的应用程序。我想在系统扩展中创建一组内存缓冲区,然后从应用程序写入它,然后使用 IOConnectCallScalarMethod
向系统扩展发出信号,表明应该将给定缓冲区发送到 USB 设备,使用 IOUSBHostPipe::AsyncIO
. When the CompleteAsyncIO
回调是发送完成的结果,我会通知应用程序现在可以将数据复制到发送的第一个缓冲区。此机制可能可以使用 IOConnectCallAsyncStructMethod
和在系统扩展中创建的 OSAction
对象来完成。我不明白的是如何将系统扩展中分配的内存映射到应用程序。
这就是 IOUserClient::CopyClientMemoryForType
in DriverKit is for, which gets invoked when your user process calls IOConnectMapMemory64
from IOKit.framework
. The kext equivalent, incidentally, is IOUserClient::clientMemoryForType
并且本质上完全相同。
要使其正常工作,您需要覆盖用户客户端子程序中的 CopyClientMemoryForType
虚函数class。
在.iig
中的class定义中:
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;
}
在用户 space 中,您将调用:
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
调用。因此,您的驱动程序可以有某种编号约定;在最简单的情况下,您可以像 select 或外部方法一样对待它。memory
实际上是一个输出参数,这是你期望 return 当你的函数 return 映射到用户 space 的内存描述符的地方]skIOReturnSuccess
。该函数具有复制语义,即调用者希望获得内存描述符的所有权,即当不再需要时,它最终会将引用计数减 1。 returned 内存描述符不必是我在示例中使用的IOBufferMemoryDescriptor
,它也可以是 PCI BAR 或其他任何东西。IOConnectMapMemory64
调用中的kIOMapAnywhere
选项很重要,通常是您想要的:如果您不指定它,atAddress
参数将成为输入输出参数,并且调用者应该 select 地址 space 中驱动程序内存应该被映射的位置。通常你不关心它在哪里,如果已经有东西映射到那里,那么指定一个明确的位置确实是危险的。- 如果用户 space 不能写入映射内存,请相应地将
options
参数设置为CopyClientMemoryForType
:*options = kIOUserClientMemoryReadOnly;
要销毁映射,用户 space 进程必须调用 IOConnectUnmapMemory64()
。