C++ 如何在DriverKit系统扩展中分配内存并将其映射到另一个进程?
我已在应用程序中分配内存,并将其指针和大小传递给C++ 如何在DriverKit系统扩展中分配内存并将其映射到另一个进程?,c++,macos-catalina,iokit,driverkit,macos-system-extension,C++,Macos Catalina,Iokit,Driverkit,Macos System Extension,我已在应用程序中分配内存,并将其指针和大小传递给IOConnectCallStructMethod。然后,我使用将该内存映射到DriverKit系统扩展进程,可以写入该映射内存位置并从应用程序读取数据 现在,我想对系统扩展中分配的内存执行类似的操作,然后将其映射到使用系统扩展的应用程序。我想在系统扩展中创建一组内存缓冲区,然后从应用程序向其写入,然后使用IOConnectCallScalarMethod向系统扩展发送信号,指示应使用将给定缓冲区发送到USB设备。当发送完成后出现回调时,我会通知应
IOConnectCallStructMethod
。然后,我使用将该内存映射到DriverKit系统扩展进程,可以写入该映射内存位置并从应用程序读取数据
现在,我想对系统扩展中分配的内存执行类似的操作,然后将其映射到使用系统扩展的应用程序。我想在系统扩展中创建一组内存缓冲区,然后从应用程序向其写入,然后使用IOConnectCallScalarMethod
向系统扩展发送信号,指示应使用将给定缓冲区发送到USB设备。当发送完成后出现回调时,我会通知应用程序现在可以将数据复制到发送的第一个缓冲区。此机制可能可以使用IOConnectCallAsyncStructMethod
和在系统扩展中创建的OSAction
对象来完成。我不明白的是如何将系统扩展中分配的内存映射到应用程序。这就是DriverKit的作用,当用户进程从IOKit.framework
调用时,就会调用它。顺便说一句,kext等价物实际上是完全相同的
要使其工作,您需要覆盖用户客户端子类中的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
调用的IOConnectMapMemory64
参数。因此,您的驱动程序可以具有某种编号约定;在最简单的情况下,可以将其类似于外部方法中的选择器来处理memoryType
实际上是一个输出参数,当函数返回memory
时,您需要在这里返回要映射到用户空间的内存描述符。该函数具有复制语义,即调用方希望获得内存描述符的所有权,即当不再需要引用计数时,它最终会将引用计数减少1。返回的内存描述符不必像我在示例中使用的那样是kioreturnsucture
,它也可以是PCI条或任何东西IOBufferMemoryDescriptor
调用中的IOConnectMapMemory64
选项很重要,通常是您想要的:如果不指定此选项,则kiompanywhere
参数将变为一个in-out参数,调用者需要在地址空间中选择一个应该映射驱动程序内存的位置。通常情况下,您不关心它在哪里,如果已经映射了某个位置,那么指定一个显式位置可能会很危险atAddress
- 如果用户空间不能写入映射内存,请相应地将
选项设置为
:CopyClientMemoryForType
*选项=kiouseClientMemoryReadOnly代码>
要销毁映射,用户空间进程必须调用
IOConnectUnmapMemory64()
我已经回答了您的问题,但请注意,如果您保留外部方法的IOUserClient方法的structureOutputDescriptor
和/或structureInputDescriptor
,则,您可以根据需要继续在驱动程序中使用该描述符。这实际上取决于哪种方法更适合您的用例。结构描述符方法有一个缺点:结构输入和输出只有在长度超过4096字节时才会在dext/kext中作为内存描述符出现。否则,它们将按值复制到OSData*
类型化字段中/从中复制。iUserClientMethodArguments中可访问的所有IOMemoryDescriptor都由应用程序分配,对吗?我是否可以传递一个指向应用程序分配的内存的IOMemoryDescriptor
,例如IOubHostPipe::AsyncIO
?或者我必须分配任何与ioubhostpipe::AsyncIO
一起使用的描述符与ioubhostinterface::CreateIOBuffer
?您可以安全地将内存描述符从IOUserClientMethodArguments
传递到ioubhostpipe::AsyncIO
。在大多数情况下,这将是一个零拷贝操作。缺点是大小阈值不合适,而且不能以任何方式细分或连接IOMemoryDescriptor
。(我认为可以通过将较小的长度传递给AsyncIO
来删除尾随字节。)ioubhostinterface::CreateIOBuffer
应优先用于分配给USB I/O的缓冲区,而不是IOBufferMemoryDescriptor::Create
——也就是说,如果您已经打算在dext中分配缓冲区内存。顺便说一句,在kexts中,您有IOSubMemoryDescriptor
和IOMultiMemoryDescriptor
,在将内存描述符用于I/O之前,您可以使用它们从足够近的任何源拼接内存描述符。(事实上,您可以定义自己的子类,可以应用您喜欢的任何拼接逻辑。)我已经将它们在DriverKit中的缺失作为一个bug报告给Apple,如果这些对你有帮助,我建议你也这样做。