进程间通信
由某个主机线程创建的任意设备内存、Event资源或Notify资源,都可以在同一进程内被该进程中的其他线程直接引用。但这些指针或句柄在进程之外是无效的,因此不能被其他进程的线程直接使用。
若要在不同进程之间共享设备内存、Event资源或Notify资源,需要应用程序使用Runtime提供的进程间通信相关API以实现如下典型场景:由一个主进程生成一批输入数据,并将这些数据提供给多个从属进程使用,而无需在每个进程中重新生成或复制数据。不同资源涉及的IPC(Inter-Process Communication)接口不同,可查看下文中的调用示例。
需要注意的是,通过aclrtMalloc接口分配设备内存时,出于性能考虑,可能会从更大的底层内存块中切分出来。在这种情况下,IPC接口会检查共享内存是否页表对齐,若未对齐,API将拦截并报错,以防止跨进程多映射内存导致的信息泄露风险。因此,建议使用aclrtMalloc接口根据内存分配规则申请内存。申请不同类型的内存时,其页表大小会有所不同:普通页内存的页表大小为4K,大页内存的页表大小支持2M或1G。
进程间共享内存
此处以A进程(内存出借方)、B进程(内存借入方)为例,说明两个进程间的内存共享接口调用流程:
以下为A、B进程之间共享内存的示例代码,不可以直接拷贝编译运行,仅供参考。完整样例代码请参见Link。
- 在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);
- 在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进程为例,说明两个进程间任务同步的示例代码,不可以直接拷贝编译运行,仅供参考。
- 在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);
- 在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。
- 在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(¬ify); // 导出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);
- 在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(¬ify, 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进程中:
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);
- 在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);