DriveOS分析系列1-内存管理

项目类型版本号
DriveOS分析系列SummaryV0.1

Nvidia异构计算内存管理


声明:本文章从公开资料中分析,部分底层机制实现并不完全正确。


术语

Block: Stream的一个小模块,在进程内管理stream的行为。block以树状结构互联以组成任意Stream。

Consumer: 一个应用组件负责从Stream中获取待它处理的数据。

Element: 一个Packet中的单一Buffer。

Frame: 一个单帧图像。

Packet : 是一个Buffer的集合,包含Stream的Payload数据。Stream可以包含使用多个Packet。

Payload: Packet里具体的数据内容 和 与之相关的 Stream序列中某个序列号的同步原语。

Producer: 负责产生数据并将其插入流。

Stream: 一种机制,该机制将一系列的计算数据从一个应用组件传递到另一个应用组件。


先写总结:

  • 暴露出来的API较多,需要用户掌握较多概念后,严格按照框架要求编程。
  • 非常关注概念的阐述。
  • 有功能安全版本,同时有非功能安全版本。
  • 公开的文档中描述没有更底层的实现描述。
  • 能够较高效的实现跨进程,跨虚拟机OS,跨芯片的数据传输。
  • 初始化准备工作较多,对开发者要求较高。
  • 底层SMMU的数据通路打通依赖暴露给用户的系列API。(强约束)

TL;DR;

一: Nvidia 共享内存底层对象

EGLStream可以将标准的图像格式EGL数据流在OpenGL,CUDA,NvMedia之间流转。聚焦在Kronos API框架下2D+3D渲染图像的高性能传输。

NvSciStream 中 NvsciBuf 则定义了Nvidia之间各个加速器的共享内存传输能力。

换言之。NVidia的世界里,为了充分发挥CUDA的战斗力。

  • 当需要用到GPU的渲染能力时,使用EGLStream来和开放的图形图像标准来做交互。车载比较典型的场景是:摄像头吐出来的图像数据,通过EGLStream喂给应用程序先用OpenCL进行计算,在给GPU来渲染框架,渲染出图像。
  • 当需要用到GPU的通用算力时,使用NVStream来和CUDA之间做零拷贝的传输。车载场景下比较典型的场景是:车载Sensor吐出来的数据,直接喂给cuDNN做物体识别等推理。

那么,为什么有了EGLStream,还要创造NvStream呢?Nvidia 在 Drive OS的文档中给出了理由:

NVIDIA continues to support EGL, but EGL is not suitable for the rigorous requirements of a safety-certified system. Some reasons:

• Resources are allocated using one library. That library has no knowledge that resources will be shared with another library. There is therefore no guarantee that resources will be allocated in a way that meets the requirements of the other library. Mapping them may not be possible.

• EGL understands only two-dimensional image data. It cannot handle tensors or other non-image sensor data.

• The EGL interfaces were not designed with safety in mind and have failure modes not allowed in a safety-certified system.

总结为三个理由:

1, EGLStream没有全局的,系统的分配机制来满足所有应用的共享需求。

2, EGLStream的标准来源于图形图像领域。主要是二维的图像定义。

3, 功能安全设计不达标。

结论:NvSciStream 是为车载异构计算而生共享内存解决方案。

SOC内部承载应用非常丰富,NVSciStream怎么统一这些不同格式的数据呢?

习惯了底层软件编程的人,一般只会给用户一个buffer地址和长度。而Nvidia没有这么粗暴,只给数据指针和长度就像给用户一个抽象类,具体的格式需要用户自己转换。像极了Linux世界,你要运行APP的话,先丢给你一个源代码,自己去编译!

为了统一异构计算的内存共享问题,Nv给出两个底层管理类。 这两个类是构建后面高级类的基础:

  • NvSciBuf
  • NvSciBufModule
  • NvSciBufAttrList

DRIVE OS将缓存**NVSciBuf**进行分类,他们可以从SOC内部的 CVSRAM 或者 系统内存 中分配缓存。

缓存分类用途属性
RawBuffer通用存储空间,应用程序用来存储数据NvSciBufGeneralAttrKey_RawBuffer
Image存储图像数据,有自己的格式。方便编解码。NvSciBufGeneralAttrKey_Image
ImagePyramid一组图像,每个图像的长宽不一样,按照一定的顺序堆叠NvSciBufGeneralAttrKey_ImagePyramid
NvSciBufArray表示整形或者浮点等基础数据类型的数组NvSciBufGeneralAttrKey_NvSciBufArray
TensorNN模型的输入数据和输出数据,标准化的NN模型输入输出类NvSciBufGeneralAttrKey_Tensor

Nvidia的方案里给每个Buffer都设置了属性:

  1. NvSciBufGeneralAttrKey_Types 确定是以上哪种Buffer类型
  2. NvSciBufGeneralAttrKey_NeedCpuAccess CPU是否可以访问该Buffer类型。
  3. NvSciBufGeneralAttrKey_RequiredPerm 定义访问该Buffer时,授予给Buffer的权限。

NvSciBufModule

NvSciBufModule管理NVSciBuff,为各个逻辑加速器建立逻辑关系,是非常关键的一个类。为了创建一个NvSciBufModule,需要先定义好属性列表,有三类属性列表:

  • 非调和属性列表:不能通过该列表分配可以在加速器之间流转的NVSciBuf, 仅仅给Buffer来定义属性。相当于原始属性列表。
  • 调和属性列表:将非调和的属性调和成功后的属性列表。通过该列表可以分配在加速器之间流转的NVSciBuf。
  • 冲突属性列表: 如果调和各定义的属性列表失败,则返回冲突属性列表。无法创建NVSciBuf。

应用通过**NvSciBufAttrListReconcile**这个API,检查了Buffer属性的一致性,从而确定接下来 NVSciBuffer 在系统哪些加速器之间进行传输。

NvSciBufAttrList

定义Buffer的属性。

单进程内异构器件使用共享内存示例代码:

// 定义属性Attr的具体值
NvSciBufType bufType = NvSciBufType_RawBuffer;                          
uint64_t rawsize = (128 * 1024);  // Allocate 128K Raw-buffer                                        
uint64_t align = (4 * 1024);  //Buffer Alignment of 4K                                             
bool cpuaccess_flag = false;                                             
                                                                           
NvSciBufAttrKeyValuePair rawbuffattrs[] = {                              
     { NvSciBufGeneralAttrKey_Types, &bufType, sizeof(bufType) },        
     { NvSciBufRawBufferAttrKey_Size, &rawsize, sizeof(rawsize) },       
     { NvSciBufRawBufferAttrKey_Align, &align, sizeof(align) },          
     { NvSciBufGeneralAttrKey_NeedCpuAccess, &cpuaccess_flag,    /*该内存允许CPU访问*/        
            sizeof(cpuaccess_flag) },                                    
};  

NvSciError  err;
NvSciBufModule bufmodule;
err = NvSciBufModuleOpen(&bufmodule);
NvSciBufAttrList AttrList1;    
unreconciledList[0] = AttrList1;
// 将AttrList1 和 bufmodule 关联                                                                          
err = NvSciBufAttrListCreate(bufmodule, &AttrList1);                  
if (err != NvSciError_Success) {
   goto fail;
}                                                                                       
 
// 将属性值设置到具体的属性列表。
err = NvSciBufAttrListSetAttrs(AttrList1, rawbuffattrs,               
        sizeof(rawbuffattrs)/sizeof(NvSciBufAttrKeyValuePair)); 

NvSciBufAttrList unreconciledList[2] = {NULL};
NvSciBufAttrList reconciledList = NULL;
NvSciBufAttrList ConflictList = NULL;
  
// 假设已经创建了 AttrList2
unreconciledList[1] = AttrList2; 

// 调和属性列表,reconciledList 和 ConflictList 也关联到 bufmodule
err = NvSciBufAttrListReconcile(
        unreconciledList,        /* array of unreconciled lists */
        2,                       /* size of this array */
        &reconciledList,         /* output reconciled list */
        &ConflictList);         /* conflict description filled in case of reconciliation failure */
if (err != NvSciError_Success) {
    goto fail;
}
  
/* ... */
  
// 属性调和好后,允许申请NvSciBuf对象。
/* Allocate a Buffer using reconciled attribute list and the
* created NvSciBufObj will be associated with the module to
* which reconciledAttrlist belongs to.
*/
err = NvSciBufAttrListObjAlloc(reconciledAttrlist,
&nvscibufobj); 
if (err != NvSciError_Success) {
goto fail;
}

// 通过NvSciBufObj获取已调和属性列表 
/* Get the associated reconciled attrlist of the object. */
err = NvSciBufObjGetAttrList(nvscibufobj,
&objReconciledAttrList);
if (err != NvSciError_Success) {
goto fail;
}

// 通过NvSciBufObj获取CPU的指针数据。
err = NvSciBufObjGetCpuPtr(nvscibufobj, &va_ptr);
if (err != NvSciError_Success) {
goto fail;
}

// 扫尾工作 
NvSciBufAttrListFree(objReconciledAttrList);
NvSciBufObjFree(nvscibufobj);
NvSciSyncAttrListFree(AttrList1);
NvSciSyncAttrListFree(AttrList2);
NvSciSyncAttrListFree(reconciledList); // In case of successful reconciliation.
NvSciSyncAttrListFree(ConflictList); // In case of failed reconciliation.

如果进程间需要共享这些 NvSciBuf, 需要通过NvSciIpcOpenEndpoint打开一个IPC通道,进行进程间的属性调和。

NvSciIpc是跨进程共享内存的一个核心,可以完成 跨进程,跨虚拟机OS,跨芯片 的进程间通信。

NvSciIpcEndpoint ipcEndpoint = 0;
 
 
err = NvSciIpcInit();
if (err != NvSciError_Success) {
    goto fail;
}
err = NvSciIpcOpenEndpoint("ipc_endpoint", &ipcEndpoint);
if (err != NvSciError_Success) {
    goto fail;
}
 
 
/* ... */
 
NvSciIpcCloseEndpoint(ipcEndpoint);
NvSciIpcDeinit();

进程一:

NvSciBufAttrList AttrList1 = NULL;
void* ListDesc = NULL;
size_t ListDescSize = 0U;
 
// 通过ipcEndPoint关联,导出本进程的期望的AttrList1。API将这些属性导出到底层框架做调和。 
err = NvSciBufAttrListIpcExportUnreconciled(
    &AttrList1,               /* array of unreconciled lists to be exported */
    1,                             /* size of the array */
    ipcEndpoint,                   /* valid and opened NvSciIpcEndpoint intended to send the descriptor through */
    &ListDesc,               /* The descriptor buffer to be allocated and filled in */
    &ListDescSize );         /* size of the newly created buffer */
if (err != NvSciError_Success) {
    goto fail;
}
 
/* send the descriptor to the process2 */
//当前进程收到调和后的属性列表
/* wait for process 1 to reconcile and export reconciled list */
err = NvSciBufAttrListIpcImportReconciled(
    module,                    /* NvSciBuf module using which this attrlist to be imported */
    ipcEndpoint,                   /* valid and opened NvSciIpcEndpoint on which the descriptor is received */
    ListDesc,               /* The descriptor buffer to be imported */
    ListDescSize,          /* size of the descriptor buffer */
    &AttrList1,            /* array of unreconciled lists to be used for validating the reconciled list */
    1,                     /* Number or unreconciled lists */
    &reconciledAttrList,               /* Imported reconciled list */ <---重点
 if (err != NvSciError_Success) {
    goto fail;
}

进程二:

void* ListDesc = NULL;
size_t ListDescSize = 0U;
NvSciBufAttrList unreconciledList[2] = {NULL};
NvSciBufAttrList reconciledList = NULL;
NvSciBufAttrList newConflictList = NULL;
NvSciBufAttrList AttrList2 = NULL;
NvSciSyncAttrList importedUnreconciledAttrList = NULL;
 
// 等待进程一的属性调和请求。
/* create the local AttrList */
/* receive the descriptor from the other process */
err = NvSciBufAttrListIpcImportUnreconciled(module, ipcEndpoint,
    ListDesc, ListDescSize,
    &importedUnreconciledAttrList);
if (err != NvSciError_Success) {
    goto fail;
}
 
/* gather all the lists into an array and reconcile */
unreconciledList[0] = AttrList2;
unreconciledList[1] = importedUnreconciledAttrList;
 
//应用属性调和
err = NvSciBufAttrListReconcile(unreconciledList, 2, &reconciledList,
        &newConflictList);
if (err != NvSciError_Success) {
    goto fail;
}
 
err = NvSciBufAttrListIpcExportReconciled(
    &AttrList1,               /* array of unreconciled lists to be exported */
    ipcEndpoint,                   /* valid and opened NvSciIpcEndpoint intended to send the descriptor through */
    &ListDesc,               /* The descriptor buffer to be allocated and filled in */
    &ListDescSize );         /* size of the newly created buffer */
if (err != NvSciError_Success) {
    goto fail;
}

可以看出,进程间的属性调和是一个IPC过程。一方Export属性,另一方Import属性,然后在各自进程空间内进行属性一致性检查。

接下来开始进入Buffer对象的使用阶段。

进程一:通过ipcEndpoint进行关联,发送具体的NvSciBuf对象。

/* process1 */
void* objAndList;
size_t objAndListSize;
 
 
err = NvSciBufIpcExportAttrListAndObj(
    bufObj,                        /* bufObj to be exported (the reconciled list is inside it) */
    NvSciBufAccessPerm_ReadOnly,   /* permissions we want the receiver to have */
    ipcEndpoint,                    /* IpcEndpoint via which the object is to be exported */
    &objAndList,                    /* descriptor of the object and list to be communicated */
    &objAndListSize);               /* size of the descriptor */
 
 
 
 /* send via Ipc */

进程二:通过ipcEndpoint进行关联,接收具体的NvSciBuf对象。

/* process2 */
void* objAndList;
size_t objAndListSize;
 
 
err = NvSciBufIpcImportAttrListAndObj(
    module,                        /* NvSciBufModule use to create original unreconciled lists in the waiter */
    ipcEndpoint,                   /* ipcEndpoint from which the descriptor was received */
    objAndList,                    /* the desciptor of the buf obj and associated reconciled attribute list received from the signaler */
    objAndListSize,                /* size of the descriptor */
    &AttrList1,               /* the array of original unreconciled lists prepared in this process */
    1,                             /* size of the array */
    NvSciBufAccessPerm_ReadOnly,  /* permissions expected by this process */
    10000U,                        /* timeout in microseconds. Some primitives might require time to transport all needed resources */
    &bufObj);                     /* buf object generated from the descriptor */
 
 
/* use the buf object */
 
 
NvSciBufObjFree(bufObj);

至此,主干流程完成。

前面还提到几种数据类型:NvMediaImage,NvMediaTensor,NvMediaArray。 Nvidia通过提供一系列接口来和NvSciBuf映射。

1, NVImage和NvSciBuf转换

NvMediaDevice *device ;
NvMediaStatus status;
NvSciError  err;
NvSciBufModule module;
NvSciBufAttrList attrlist, conflictlist;
NvSciBufObj bufObj;
NvMediaImage *image;
NvMediaSurfaceType nvmsurfacetype;
NvMediaSurfAllocAttr surfAllocAttrs[8];
NVM_SURF_FMT_DEFINE_ATTR(surfFormatAttrs);
 
/*NvMedia related initialization */
device = NvMediaDeviceCreate();
 
status = NvMediaImageNvSciBufInit();
 
/*NvSciBuf related initialization*/  
err = NvSciBufModuleOpen(&module);
 
/*Create NvSciBuf attribute list*/
err = NvSciBufAttrListCreate(module, &attrlist);
 
/*Initialize surfFormatAttrs and surfAllocAttrs as required */
....
....
 
/* Get NvMediaSurfaceType */
nvmsurfacetype = NvMediaSurfaceFormatGetType(surfFormatAttrs, NVM_SURF_FMT_ATTR_MAX);
 
/*Ask NvMedia to fill NvSciBufAttrs corresponding to nvmsurfacetype and  surfAllocAttrs*/
status = NvMediaImageFillNvSciBufAttrs(device, nvmsurfacetype, surfAllocAttrs, numsurfallocattrs, 0, attrlist);
 
/*Reconcile the NvSciBufAttrs and then allocate a NvSciBufObj */
err = NvSciBufAttrListReconcileAndObjAlloc(&attrlist, 1, bufobj,  &conflictlist);

// 转换API
/*Create NvMediaImage from NvSciBufObj */
status = NvMediaImageCreateFromNvSciBuf(device, bufobj, &image);
 
/*Free the NvSciBufAttrList which is no longer required */
err = NvSciBufAttrListFree(attrlist);
 
// 使用通用的NvImage
/*Use the image as input or output for any of the Nvmedia */
....
....
 
/*Free the resources after use*/
 
/*Destroy NvMediaImage */
NvMediaImageDestroy(image);
 
/*NvMedia related Deinit*/
NvMediaImageNvSciBufDeinit();
NvMediaDeviceDestroy(device);
 
/*NvSciBuf related deinit*/
NvSciBufObjFree(bufobj);
NvSciBufModuleClose(module);

1, CUDA pointer 和NvSciBuf转换 , CUDA可以利用裸指针做输入,NN推理或训练场景使用。

/*********** Allocate NvSciBuf object ************/                                                                                                                                                                
    // Raw Buffer Attributes for CUDA
    NvSciBufType bufType = NvSciBufType_RawBuffer;
    uint64_t rawsize = SIZE;
    uint64_t align = 0;
    bool cpuaccess_flag = true;
    NvSciBufAttrValAccessPerm perm = NvSciBufAccessPerm_ReadWrite;
 
 
    uint64_t gpuId[] = {};
    cuDeviceGetUuid(&uuid, dev));
    gpuid[0] = (uint64_t)uuid.bytes;
     
    // Fill in values
    NvSciBufAttrKeyValuePair rawbuffattrs[] = {                              
         { NvSciBufGeneralAttrKey_Types, &bufType, sizeof(bufType) },        
         { NvSciBufRawBufferAttrKey_Size, &rawsize, sizeof(rawsize) },       
         { NvSciBufRawBufferAttrKey_Align, &align, sizeof(align) },          
         { NvSciBufGeneralAttrKey_NeedCpuAccess, &cpuaccess_flag,            
                sizeof(cpuaccess_flag) },
         { NvSciBufGeneralAttrKey_RequiredPerm, &perm, sizeof(perm) },
         { NvSciBufGeneralAttrKey_GpuId, &gpuid, sizeof(gpuId) },                                 
    };                                                                       
 
    // Create list by setting attributes
    err = NvSciBufAttrListSetAttrs(attrListBuffer, rawbuffattrs,               
            sizeof(rawbuffattrs)/sizeof(NvSciBufAttrKeyValuePair)); 
                    
    NvSciBufAttrListCreate(NvSciBufModule, &attrListBuffer);
 
    // Reconcile And Allocate
    NvSciBufAttrListReconcile(&attrListBuffer, 1, &attrListReconciledBuffer, &attrListConflictBuffer)
    NvSciBufObjAlloc(attrListReconciledBuffer, &bufferObjRaw);
 
 
    /*************** Query NvSciBuf Object **************/
     NvSciBufAttrKeyValuePair bufattrs[] = {
                {NvSciBufRawBufferAttrKey_Size, NULL, 0},
    };
    NvSciBufAttrListGetAttrs(retList, bufattrs, sizeof(bufattrs)/sizeof(NvSciBufAttrKeyValuePair)));
    ret_size = *(static_cast<const uint64_t*>(bufattrs[0].value));
 
 
    /*************** NvSciBuf Registration With CUDA **************/
    // Fill up CUDA_EXTERNAL_MEMORY_HANDLE_DESC
    cudaExternalMemoryHandleDesc memHandleDesc;
    memset(&memHandleDesc, 0, sizeof(memHandleDesc));
    memHandleDesc.type = cudaExternalMemoryHandleTypeNvSciBuf;
    memHandleDesc.handle.nvSciBufObject = bufferObjRaw; // 拿到buffer object
    memHandleDesc.size = ret_size;
    cudaImportExternalMemory(&extMemBuffer, &memHandleDesc);
 
 
    /************** Mapping to CUDA ******************************/
    cudaExternalMemoryBufferDesc bufferDesc;
    memset(&bufferDesc, 0, sizeof(bufferDesc));
    bufferDesc.offset = offset = 0;
    bufferDesc.size = ret_size;
    cudaExternalMemoryGetMappedBuffer(&dptr, extMemBuffer, &bufferDesc); // 拿到buffer Object里的裸指针。
 
 
    /************** CUDA Kernel ***********************************/
    // Run CUDA Kernel on dptr
 
 
    /*************** Free CUDA mappings *****************************/
    cudaFree();
    cudaDestroyExternalMemory(extMemBuffer);
 
    /***************** Free NvSciBuf **********************************/
    NvSciBufAttrListFree(attrListBuffer);
    NvSciBufAttrListFree(attrListReconciledBuffer)
    NvSciBufAttrListFree(attrListConflictBuffer);
    NvSciBufObjFree(bufferObjRaw);

Nvmedia内部没有具体的架构资料,不过通过上面的分析可以猜测到。通过attribute来验证内存输入输出的一致性(大小,数据类型要求,对齐等)后,创建局部加速器组 可见的对象(内部对地址SMMU的翻译是通过API来约定的)。没有约定的不能进行共享。

二: Stream数据流水处理

NVSciBuf只能满足两个异构计算单元的内存共享问题,但没有解决数据Pipeline的问题。如果要做Pipeline,肯定会涉及到持续的生产,消费,同步,丢弃等问题。为了统一SOC内部数据概念,让异构器件处理数据能够流动起来。NvMedia又引入了几个类。

  • NvSciStream
  • NvSciSync
  • NvSciSyncFence

我们通过一个例子来学习Nvidia实现这些类背后的一些概念。 这是一个位于SOC1上的B进程生产者,有三个消费者的数据流水线:

  • 路径1 — 位于SOC1上的B进程内的消费者,与生产者同进程。
  • 路径2 —位于SOC1上的A进程内的消费者。
  • 路径3 —位于SOC2上的C进程内的消费者。

Nvidia在处理流水线设计时,使用构建块(Building Block)的概念,即每一个逻辑对象都是块。下图中的每一个方块都是Block,以NvSciStreamBlock类来抽象,每一个独立方块都是该类具现的实体。 为什么这么做? 我猜测本身就是遵循组件化的思维,从DriveOS的代码框架、ISAAC的代码框架都可以看出来。这种基于Building Block的构建思维有利于在Component中复用。

pipeline

生产者拥有: (通过 NvSciErro NvSciStreamProducerCreate(NvSciStreamBlock pool, NvSciStreamBlock* producer) 创建生产者 )

  • Pool : 有静态池和动态池之分。功能安全场景只允许用静态池,非功能安全场景(如视屏播放)用动态池。
    • 对应API c++ NvSciErro NvSciStreamStaticPoolCreate(uint32_t numPacket NvSciStreamBlock* pool)
  • Multicast Block:用于多播。
    • 对应API NvSciError NvSciStreamMulticastCreate(uint32_t numOutputs,NvSciStreamBlock* multicast)
  • 共享内存IPC:利用NvSciBuf的共享内存通信。也是一个StreamBlock实现。
  • MemoryBoundary IPC: 利用拷贝的通信
    • IPC创建方式:NvSciError NvSciStreamIpcSrcCreate( NvSciIpcEndpoint ipcEndpoint, NvSciSyncModule syncModule, NvSciBufModule bufModule, NvSciStreamBlock* ipc) NvSciError NvSciStreamIpcDstCreate( NvSciIpcEndpoint ipcEndpoint, NvSciSyncModule syncModule, NvSciBufModule bufModule, NvSciStreamBlock* ipc)

消费者拥有:(通过 NvSciError NvSciStreamConsumerCreate( NvSciStreamBlock queue, NvSciStreamBlock* consumer) 创建消费者 )

  • QueueBlock: 支持FIFO和MailBox两种。
    • FIFO模式下,生产者必须等待消费者消费完当前帧。如果消费者速度很慢,生产者会降低生产的速度。producer阻塞在消费者的fence上。新数据可能会丢失。 对应API NvSciError NvSciStreamFifoQueueCreate(NvSciStreamBlock* queue)
    • MailBox模式下,生产者会循环利用buffer,将最新的数据代替消费还未消费的上一帧数据,让消费者一直用到最新的数据。对应API NvSciError NvSciStreamMailboxQueueCreate(NvSciStreamBlock* queue)
  • Pool : 有静态池和动态池之分。功能安全场景只允许用静态池,非功能安全场景(如视屏播放)用动态池。
  • 共享内存IPC:利用NvSciBuf的共享内存通信。
  • MemoryBoundary IPC: 利用拷贝的通信

构建好以上的数据结构,将producer和consumer的数据流转起来只需要:

NvSciError
NvSciStreamBlockConnect(
    NvSciStreamBlock upstream,
    NvSciStreamBlock downstream)

为了驱动数据流往后流动, 在创建好StreamBlock,将这些StreamBlock连接起来后。必须有事件在背后驱动数据流

typedef struct {
    NvSciStreamEventType   type;
    NvSciStreamSyncAttr    syncAttr;
    NvSciStreamSyncDesc    syncDesc;
    NvSciStreamElementAttr packetAttr;
    NvSciStreamPacket      packetCreate;
    NvSciStreamElementDesc packetElement;
    NvSciStreamCookie      packetCookie;
    NvSciError             error;
    uint32_t               index;
    uint32_t               count;
} NvSciStreamEvent

Nvidia 支持两种事件流驱动。在 5.1.6.1L 中,只支持了pool轮训式驱动,在没有数据时会浪费较多CPU。这种实现容易实现,只要在各个线程里主动取上下游的NvSciStreamBlock即可。 接口如下:

NvSciError
NvSciStreamBlockEventQuery(
    NvSciStreamBlock  block,
    int64_t           timeout_usec,
    NvSciStreamEvent *event)

在5.1之后引入了事件通知机制。从描述看,SDK开放使用Nvidia已有的NvSciIpc事件和操作系统已有的通知机制。

compatible with NvSciIpc events and operating system specific event mechanisms. allowing the thread to wait for more than just NvSciStream events at the same time. This mechanism is not supported in the current release.

老版本已经可以看得到的事件有:

  • NvSciStreamEventType_ConnectUpstream
  • NvSciStreamEventType_ConnectDownStream
  • NvSciStreamEventType_DisconnectUpstream
  • NvSciStreamEventType_DisconnectDownstream
  • NvSciStreamEventType_SyncAttr

值得一提的时,Nvidia非常注重功能安全场景的应用。在功能安全认证系统中,为了避免failure和资源销毁的误操作带来的潜在功能安全隐患,Nvidia打算移除所有Disconnect事件。

In safety-certified systems, failures and teardown are never supposed to occur. Although disconnect events are currently supported in the current safety release, they will likely be removed in later safety releases, and only supported for non-safety platforms.

至此,我们至少把数据的逻辑关系关联起来。

这还远远不够。

在SOC这种负责的异构器件里,存在CPU,GPU和其他加速引擎,他们在同时工作。 共享内存带来了效率提升,也带来同时访问数据的问题。

我们需要类似操作系统的同步原语(synchronization primitives)。Nvidia引入了

  • NvSciSync
    • fence: 代表拥有者的状态机,在多个sync对象中共享。状态机没有满足要求,动作被pending
    • sync对象:可以拥有多个fence
    • 等待fence的对象,称作sync对象上下文中的waiter。
  • NvSciSyncAttrList
    • 表示同步对象可以发送消息的属性需求
    • 表示同步对象可以接收消息的属性需求。

设置一个流的同步属性,同时

typedef struct {
    // Flag indicating fences are not supported
    bool              synchronousOnly;
    // The read requirements for sync objects
    NvSciSyncAttrList waiterSyncAttr;
} NvSciStreamSyncAttr
 
// 设置StreamBlock的同步属性
NvSciError
NvSciStreamBlockSyncRequirements(
    NvSciStreamBlock           block,
    const NvSciStreamSyncAttr* attr)
    
    

在定义好同步对象后。

分配Buffer,并创建Packet(Packet是一系列NvSciBuf的组合)

typedef struct {
    uint32_t               index;
    uint32_t               type;
    NvSciStreamElementMode mode;
    NvSciBufAttrList       bufAttr;
} NvSciStreamElementAttr
// 待补充用途 
NvSciError
NvSciStreamBlockPacketElementCount(
    NvSciStreamBlock block,
    uint32_t         count
)
// 待补充用途  
NvSciError
NvSciStreamBlockPacketAttr(
    NvSciStreamBlock              block,
    const NvSciStreamElementAttr* attr
)

typedef struct {
    NvSciStreamCookie cookie;
    NvSciSyncFence*   prefences;
} NvSciStreamPayload

// 从生产者中获取一个数据包 API
NvSciError
NvSciStreamProducerPacketGet(
    NvSciStreamBlock    producer,
    NvSciStreamPayload* payload)

  
typedef struct {
    uint32_t          index;
    NvSciStreamPacket handle;
    NvSciBufObj       buffer;
} NvSciStreamElementDesc
    
// 发端创建Packet API 
NvSciError
NvSciStreamPoolPacketCreate(
    NvSciStreamBlock   pool,
    NvSciStreamCookie  cookie,
    NvSciStreamPacket* handle
)
// 将数据插入Packet的API
NvSciError
NvSciStreamPoolPacketInsertBuffer(
    NvSciStreamBlock              pool,
    const NvSciStreamElementDesc* desc
)
    
// 生产者发送一个Packet
NvSciError
NvSciStreamProducerPacketPresent(
    NvSciStreamBlock  producer,
    NvSciStreamPacket handle,
    NvSciSyncFence*   postfences  //同步对象
)
    
// 消费者接收一个数据对象
 NvSciError
NvSciStreamConsumerPacketAcquire(
    NvSciStreamBlock    consumer,
    NvSciStreamPayload* payload
) 
    
    

发表评论

电子邮件地址不会被公开。 必填项已用*标注