如何在 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()