开发者
资源

进程间通信

由某个主机线程创建的任意设备内存、Event资源或Notify资源,都可以在同一进程内被该进程中的其他线程直接引用。但这些指针或句柄在进程之外是无效的,因此不能被其他进程的线程直接使用。

若要在不同进程之间共享设备内存、Event资源或Notify资源,需要应用程序使用Runtime提供的进程间通信相关API以实现如下典型场景:由一个主进程生成一批输入数据,并将这些数据提供给多个从属进程使用,而无需在每个进程中重新生成或复制数据。不同资源涉及的IPC(Inter-Process Communication)接口不同,可查看下文中的调用示例。

需要注意的是,通过aclrtMalloc接口分配设备内存时,出于性能考虑,可能会从更大的底层内存块中切分出来。在这种情况下,IPC接口会检查共享内存是否页表对齐,若未对齐,API将拦截并报错,以防止跨进程多映射内存导致的信息泄露风险。因此,建议使用aclrtMalloc接口根据内存分配规则申请内存。申请不同类型的内存时,其页表大小会有所不同:普通页内存的页表大小为4K,大页内存的页表大小支持2M或1G。

进程间共享内存

此处以A进程(内存出借方)、B进程(内存借入方)为例,说明两个进程间的内存共享接口调用流程:

以下为A、B进程之间共享内存的示例代码,不可以直接拷贝编译运行,仅供参考。完整样例代码请参见Link

  1. 在A进程中分配内存,生成共享key:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    uint keyLen = 65;
    char[keyLen] key; 
    void *ptrA = nullptr;
    
    aclrtSetDevice(0);  // 进程A使用Device 0
    aclrtMalloc(&ptrA, size); 
    
    aclrtIpcMemGetExportKey(ptrA, size, key, keyLen, ACL_RT_IPC_MEM_EXPORT_FLAG_DISABLE_PID_VALIDATION);  
    
    // 跨进程通信交换key(以写文件方式交互)
    writeFile("file/ipc_mem", key, keyLen);
    
    // 对共享内存进行读写操作
    ......
    
    // 待共享内存使用完成后,借出方关闭IPC共享内存
    aclrtIpcMemClose(key);   
    aclrtFree(ptrA);
    aclrtResetDeviceForce(0);
    
  2. 在B进程中,通过共享key导入共享内存:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    uint keyLen = 65;
    char[keyLen] key;
    void *ptrB;
    
    // 跨进程通信交换key(以写文件方式交互)
    readFile("file/ipc_mem", key, keyLen);
    
    aclrtSetDevice(1);  // 进程B使用Device 1
    
    // A,B进程使用不同的Device,导入共享内存访问要开启两个Device之间的数据交互 
    aclrtIpcMemImportByKey(&ptrB, key, ACL_RT_IPC_MEM_IMPORT_FLAG_ENABLE_PEER_ACCESS);  
    
    // 对ptrB内存读写操作
    ......
    
    // 使用完成后,借入方关闭IPC共享内存
    aclrtIpcMemClose(key);    
    aclrtResetDeviceForce(1);
    

进程间共享Event

进程之间通过共享Event,可以实现进程间的事件同步。此处以A进程创建Event,共享给B进程为例,说明两个进程间任务同步的示例代码,不可以直接拷贝编译运行,仅供参考。

  1. 在A进程中创建Event,生成共享handle:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    aclrtEvent event;
    aclrtStream stream;
    aclrtIpcEventHandle handle;
    
    aclrtSetDevice(0);  // 进程A使用Device 0
    aclrtCreateEventExWithFlag(&event, ACL_EVENT_IPC);  // 创建IPC Event
    aclrtCreateStream(&stream);  // 创建Stream
    
    // 导出进程间共享handle
    aclrtIpcGetEventHandle(event, &handle);
    
    // 跨进程通信交换key(以写文件方式交互)
    writeFile("file/ipc_event", handle, ACL_IPC_EVENT_HANDLE_SIZE);
    
    // 下发record任务
    aclrtEventRecord(event, stream);  
    
    // Event使用完, 销毁共享Event
    aclrtDestroyEvent(event);
    aclrtResetDeviceForce(0);
    
  2. 在B进程中,通过共享handle导入共享Event:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    aclrtStream stream;
    aclrtEvent event;
    aclrtIpcEventHandle handle;
    
    aclrtCreateStream(&stream);  // 创建Stream
    // 跨进程获取共享handle(以写文件方式交互)
    readFile("file/ipc_event", handle, ACL_IPC_EVENT_HANDLE_SIZE);
    
    aclrtSetDevice(1);  //进程B使用Device 1
    
    // 导入handle,返回共享event
    // A,B进程使用不同的Device 
    aclrtIpcOpenEventHandle(handle, &event); 
    
    // 下发wait任务
    aclrtStreamWaitEvent(stream, event); 
    
    // 同步Stream上的任务
    aclrtSynchonizeStream(stream);
    
    // Event使用完,销毁共享Event
    aclrtDestroyEvent(event);
    aclrtResetDeviceForce(1);
    

进程间共享Notify

进程之间通过共享Notify,可以实现进程间的通知。此处以A进程创建Notify,共享给B进程为例,说明两个进程间任务同步的示例代码,不可以直接拷贝编译运行,仅供参考。完整样例代码请参见Link

注意:创建端会分配Notify硬件资源,因此只能由创建端硬件进行Wait。为此共享Notify有使用约束,只能在创建端调aclrtNotifyWait进行Wait,不能在共享端Wait。

  1. 在A进程中创建Notify,生成共享key:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    uint keyLen = 65;
    char key[keyLen]; 
    aclrtNotify notify;
    aclrtStream stream;
    
    // 进程A使用Device 0
    aclrtSetDevice(0);  
    aclrtCreateStream(&stream); 
    
    aclrtNotifyCreate(&notify); 
    // 导出key(即Notify共享名称)
    aclrtNotifyGetExportKey(notify, key, keyLen, ACL_RT_NOTIFY_EXPORT_FLAG_DISABLE_PID_VALIDATION); 
    // 跨进程通信交换key(以写文件方式交互)
    writeFile("file/ipc_notify", key, keyLen);
    
    // 下发wait任务
    aclrtNotifyWait(notify, stream);  
    
    // Notify使用完, 销毁共享Notify
    aclrtNotifyDestroy(notify);
    aclrtResetDeviceForce(0);
    
  2. 在B进程中,通过共享key导入共享Notify:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    uint keyLen = 65;
    char key[keyLen];
    aclrtNotify notify;
    aclrtStream stream;
    
    // 跨进程通信交换key(以写文件方式交互)
    readFile("file/ipc_notify", key, keyLen);
    
    //进程B使用Device 1
    aclrtSetDevice(1);  
    aclrtCreateStream(&stream);
    
    // 导入key,返回共享Notify
    // A,B进程使用不同的Device,导入共享Notify要开启两个Device之间的数据交互,建议使用ACL_RT_NOTIFY_IMPORT_FLAG_ENABLE_PEER_ACCESS
    aclrtNotifyImportByKey(&notify, key, ACL_RT_NOTIFY_IMPORT_FLAG_ENABLE_PEER_ACCESS); 
    
    // 下发record任务
    aclrtNotifyRecord (notify, stream); 
    
    // Notify使用完,销毁共享Notify
    aclrtNotifyDestroy(notify);
    aclrtResetDeviceForce(1);
    

通过VMM接口实现进程间共享内存

除IPC Mem共享内存外,Runtime还提供了另外一套内存管理和内存共享接口。VMM(Virtual Memory Management)这套接口提供更灵活的功能,支持虚拟地址申请、物理内存申请和跨进程物理内存共享,还支持虚拟地址与物理内存之间的映射操作。

此处以A、B进程为例,说明一个Device上、两个进程间的物理内存共享的示例代码,不可以直接拷贝编译运行,仅供参考,完整样例代码请参见Link

若A、B进程使用不同的Device,还需配合使用aclrtDeviceEnablePeerAccess接口开启跨Device的数据交互,详细描述请参见跨Device的数据交互
  1. 在A进程中:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 查询内存申请粒度
    const size_t dataSize = 1024 * sizeof(float);
    aclrtPhysicalMemProp prop = {};
    prop.handleType = ACL_MEM_HANDLE_TYPE_NONE;
    prop.allocationType = ACL_MEM_ALLOCATION_TYPE_PINNED;
    prop.location.type = ACL_MEM_LOCATION_TYPE_DEVICE;
    prop.location.id = 0;
    prop.memAttr = ACL_HBM_MEM_NORMAL;
    size_t granularity = 0UL;
    aclrtMemGetAllocationGranularity(&prop, ACL_RT_MEM_ALLOC_GRANULARITY_MINIMUM, &granularity);
    
    // 基于内存申请粒度申请物理内存
    size_t alignedSize = ((dataSize + granularity - 1U) / granularity) * granularity;
    aclrtDrvMemHandle handle = nullptr;
    aclrtMallocPhysical(&handle, alignedSize, &prop, 0);
    
    // 预留虚拟内存
    void *virPtr;
    aclrtReserveMemAddress(&virPtr, alignedSize, 0, nullptr, 0);
    
    // 将虚拟内存映射到物理内存
    aclrtMapMem(virPtr, alignedSize, 0, handle, 0);
    aclrtMemAccessDesc desc = {};
    desc.flags = ACL_RT_MEM_ACCESS_FLAGS_READWRITE;
    desc.location.id = 0;
    desc.location.type = ACL_MEM_LOCATION_TYPE_DEVICE;
    aclrtMemSetAccess(virPtr, alignedSize, &desc, 1);
    
    // 使用virPtr进行复制、读、写等操作
    ......
    
    // 导入共享handle
    uint64_t shareableHandle = 0ULL;
    aclrtMemExportToShareableHandle(handle, ACL_MEM_HANDLE_TYPE_NONE, ACL_RT_VMM_EXPORT_FLAG_DISABLE_PID_VALIDATION , &shareableHandle);
    
    // 将共享handle传递给进程B     
    writeFile("file/vmm_mem", shareableHandle, sizeof(shareableHandle));
    
    // 取消虚拟内存与物理内存之间的映射关系
    aclrtUnmapMem(virPtr);
    // 释放虚拟内存和物理内存
    aclrtReleaseMemAddress(virPtr);
    aclrtFreePhysical(handle);
    
  2. 在B进程中:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    uint64_t shareableHandle = 0ULL;
    // 从文件中获取共享handle
    readFile("file/vmm_mem", &shareableHandle, sizeof(shareableHandle));
    
    aclrtDrvMemHandle handle = nullptr; 
    int32_t deviceId=0;  
    aclrtMemImportFromShareableHandle(shareableHandle, deviceId, &handle);
    // 查询内存申请粒度
    const size_t data_size = 1024 * sizeof(float);
    aclrtPhysicalMemProp prop = {};
    prop.handleType = ACL_MEM_HANDLE_TYPE_NONE;
    prop.allocationType = ACL_MEM_ALLOCATION_TYPE_PINNED;
    prop.location.type = ACL_MEM_LOCATION_TYPE_DEVICE;
    prop.location.id = 0;
    prop.memAttr = ACL_HBM_MEM_NORMAL;
    size_t granularity = 0UL;
    aclrtMemGetAllocationGranularity(&prop, ACL_RT_MEM_ALLOC_GRANULARITY_MINIMUM, &granularity);
    size_t alignedSize = ((dataSize + granularity - 1U) / granularity) * granularity;
    
    // 基于内存申请粒度预留虚拟内存
    void *virPtr = nullptr;
    aclrtReserveMemAddress(&virPtr, alignedSize, 0, nullptr, 0);
    // 将虚拟内存映射到物理内存
    aclrtMapMem(virPtr, alignedSize, 0, handle, 0);
    aclrtMemAccessDesc desc = {};
    desc.flags = ACL_RT_MEM_ACCESS_FLAGS_READWRITE;
    desc.location.id = 0;
    desc.location.type = ACL_MEM_LOCATION_TYPE_DEVICE;
    aclrtMemSetAccess(virPtr,alignedSize, &desc, 1);
    
    // 使用virPtr进行复制、读、写等操作
    ......
    
    // 取消虚拟内存与物理内存之间的映射关系
    aclrtUnmapMem(virPtr);       
    // 释放虚拟内存和物理内存
    aclrtReleaseMemAddress(virPtr);
    aclrtFreePhysical(handle);