DualOS多进程资源管理说明

1. 概述

DualOS多进程是基于Purelinux多进程方案上做兼容的,核心设计思想为进程资源谁创建就由谁来销毁,由一个主进程构建核心Pipeline,子进程负责取流、取Output Buffer或创建子码流等行为。套用到DualOS上,将RTOS创建的App Preload也当成一个进程去处理,并且该进程为主进程负责创建核心Pipeline,在Linux起来所跑的App为子进程,负责取流、取Output buffer或创建子码流等行为。

图1-1 DualOS多进程架构图

基于该核心思想,Linux所跑的App不能去跨进程销毁RTOS App所创建的资源,可以去获取或设置属性,在Linux起来后,RTOS App基本上是不会主动去做任何事情的,如果想要销毁RTOS创建的资源,需要通过Linux这边发送Cli Cmd(基于Rpmsg实现的Linux to RTOS通信手段),RTOS接收到Cli Cmd,根据Cli Cmd定义的子串去做对应的销毁动作,基本架构如图1-1所示。

2. MI资源

核心的规则为多进程中资源谁创建就由谁来销毁,本章节会阐述清楚哪些为MI资源,怎么理解销毁创建。

对于MI SYS会拥有Init、Bind、MMa、InputPortBuffer、OutputPortBuffer等资源,每个与stream pipe相关(需要使用到MI模块)的进程都需要在初始化时呼叫一次MI SYS的init API(MI_SYS_Init)、并且退出时呼叫一次MI SYS的Exit API(MI_SYS_Exit);对于同样Src和Dest的Bind和Unbind动作需要在同一个进程进行;对于同一块MMaBuffer的Alloc和Free需要在同一个进程进行;任意进程都可以Get InputPortBuffer或OutputPortBuffer。

对于MI Video/Audio相关的模块大部分会拥有Device/Channel/Port资源。

  • Device资源支持多进程,跨进程使用该Device下的Channel时,需要先Recreate Device,此时Create Device使用的参数不会生效,只会参考第一次创建Device使用的参数,退出时需要Destroy Device。
  • Channel和Port资源不支持多进程,不允许跨进程做Recreate和Destroy的动作。部分模块能跨进程Dup Chn使用,参考特殊模块注意

表1-1总结了常用MI模块拥有的资源和对应创建销毁API,其中规则中提到需在同一进程进行指的是操作同一份资源,例如以MI SYS的Bind为例,以同样的SrcChnPort和DstChnPort建立的绑定关系为同一份资源:

MI_SYS_ChnPort_t   stSrcChnPort;
MI_SYS_ChnPort_t   stDstChnPort;
stSrcChnPort.eModId    = E_MI_MODULE_ID_ISP;
stSrcChnPort.u32DevId  = 0;
stSrcChnPort.u32ChnId  = 0;
stSrcChnPort.u32PortId = 0;
stDstChnPort.eModId    = E_MI_MODULE_ID_SCL;
stDstChnPort.u32DevId  = 0;
stDstChnPort.u32ChnId  = 0;
stDstChnPort.u32PortId = 0;

表1-1 MI资源总结

MI模块 资源 创建和销毁的API 规则
SYS Init MI_SYS_Init
MI_SYS_Exit
每一个stream pipe进程都需要呼叫一次Init和Exit
Bind MI_SYS_BindChnPort2
MI_SYS_UnbindChnPort2
Bind/Unbind同一份资源需在同一进程进行
MMa MI_SYS_MMA_Alloc
MI_SYS_MMA_Free
Alloc和Free同一块Buffer需在同一进程进行
PrivateMMAHeap MI_SYS_ConfigPrivateMMAHeap Alloc和Free同一块Buffer需在同一进程进行
PortBuffer MI_SYS_ChnInputPortGetBuf
MI_SYS_ChnInputPortPutBuf
MI_SYS_ChnOutputPortGetBuf
MI_SYS_ChnOutputPortPutBuf
任意进程都可以GetBuf,同一块Buffer的Get和Put需在同一进程进行
VIF Group MI_VIF_CreateDevGroup
MI_VIF_DestroyDevGroup
不支持多进程,需在同一进程做创建和销毁
Device MI_VIF_EnableDev
MI_VIF_DisableDev
Port MI_VIF_EnableOutputPort
MI_VIF_DisableOutputPort
ISP
SCL
VENC
JPD
VDEC
LDC
AI
AO
RGN
(部分模块没有Port请忽略)
Device MI_XXX_CreateDevice
MI_XXX_DestroyDevice
支持多进程,跨进程使用时需要做创建和销毁
Channel MI_XXX_CreateChannel
MI_XXX_DestroyChannel
不支持多进程,需在同一进程做创建和销毁
Port MI_XXX_EnableOutputPort
MI_XXX_DisableOutputPort
不支持多进程,需在同一进程做创建和销毁

AI、AO模块将Open划分为Device资源,对应API接口MI_XXX_Open/MI_XXX_Close,参考表格中Device资源规则。

RGN模块将Init划分为Device资源,对应MI_RGN_Init/MI_RGN_Deinit,参考表格中Device资源规则;Handle划分为Channel资源,对应MI_RGN_Create/MI_RGN_Destroy,参考格中Channel资源规则。

以下跨进程操作资源的行为是允许的:

  1. 获取和设置资源属性,例如呼叫MI API:MI_XXX_SetDevAttr、MI_XXX_SetChnAttr、MI_XXX_SetOutputPortAttr、MI_XXX_GetDevAttr、MI_XXX_GetChnAttr、MI_XXX_GetOutputPortAttr等。
  2. Device资源的使用,呼叫MI API:MI_XXX_CreateDevice、MI_XXX_DestroyDevice,需要成对呼叫。
  3. Stop和Start Chn,例如呼叫MI API:MI_XXX_StopChannel、MI_XXX_StartChannel。
  4. 获取MI模块的Input/OutputBuffer,例如呼叫MI API:MI_SYS_ChnInputPortGetBuf、MI_SYS_ChnInputPortPutBuf、MI_SYS_ChnOutputPortGetBuf、MI_SYS_ChnOutputPortPutBuf。

3. 退出和销毁

3.1 常用Pipeline举例

以DualOS中常用Pipeline举例,如图1-2所示

图1-2 DualOS多进程常用Pipeline

  • RTOS Preload 属于进程1构建VIF->ISP->SCL->VENC的基本MI Pipeline,确保video数据流正常出流。
  • Linux APP1属于进程2,用于创建VENC子码流,并且Get VENC的output stream给到Rtsp做Preview画面
  • Linux APP2属于进程3,用于Get SCL的Output Buffer(通过MI SYS_ChnOutputPortGetBuf接口),将Buffer给到算法做处理。

在此多进程场景中,RTOS Preload APP会把Video Pipeline基本构建完成,Linux起来后所运行的APP都是在此基础上进行拓展。

3.2 退出和销毁流程

RTOS提供了MI_DEVICE_RegMiSysExitCall这个API,用于Preload APP注册Exit Function。或者Preload APP自行定义Cli Cmd,通过Linux发送Cli Cmd主动退出。

RTOS Preload APP行为 退出流程
注册了Exit Function Linux stream pipeline最后一个进程退出后,RTOS Preload APP自动退出。
在异常退出或进程被Kill掉场景,Linux的资源由MI SYS回收,RTOS的资源由APP通过Exit Function回收。
没有注册对应的Exit Function Rtos Preload APP不会自动退出。
在异常退出或进程被Kill掉场景,RTOS的资源不会主动回收。
定义Cli Cmd,主动调用Exit Function(与上述是否注册Exit Function不冲突) 由Linux APP自行决定退出

退出和销毁流程:

重点在于描述注册了Exit Function的情况,对应到上述举例的Pipeline中:

1、假如Linux APP1进程退出,对RTOS Preload APP 和Linux APP2无影响,RTOS APP能正常出流,Linux APP2也能正常获取SCL OuputBuffer。

2、Linux APP2进程再退出,此时Linux APP2先退出,退出完成后RTOS Preload APP也会自动退出。

注意:Linux APP在使用RTOS资源时,RTOS如果退出了,Linux APP再去使用会出现异常,例如Get不到Buffer,取流失败等。

3.3 异常问题除错

退出和销毁时,如果资源销毁不正确,容易导致系统crash问题,以下提供基本的除错思路:

1、打开RTOS log,更改env参数bootargs_rtos的loglevel参数和logmode参数。

#修改前
bootargs_rtos=rtos_size=0x01000000 limit_dram_size=0x10000000 mma_base=0x24000000 mma_size=0x0B000000 rtosrd_addr=0x2FA00000 rtosrd_size=0x120000 loglevel=3
#修改后
bootargs_rtos=rtos_size=0x01000000 limit_dram_size=0x10000000 mma_base=0x24000000 mma_size=0x0B000000 rtosrd_addr=0x2FA00000 rtosrd_size=0x120000 loglevel=7 logmode=direct

确认是否有[MI ERR]的log,根据log排查资源销毁情况,是否重复调用Unbind/Destroy等接口。

2、加debug信息定位跑到哪里死机,尽可能确认跑到哪一个接口会出现问题。

3、排查是否RTOS端的资源没有完全释放,导致Linux端再跑应用时出错。

4、排查资源释放接口调用顺序是否正确。

5、排查接口的参数设置是否正确。

4. 切换参数的做法

切换参数分为两种流程:

1、切换过程中不涉及资源的销毁和创建,这种情况下可以在任意进程完成切换,不会有多进程的限制。

对于流程1,Chn属性参数和Port的切换、Port属性的切换会走此流程,例如ISP模块需要切换3Dnr Level,可以参考以下流程:

MI_ISP_DEV     devId;
MI_ISP_CHANNEL chnId;
MI_ISP_ChnParam_t stChnParam;
MI_ISP_StopChannel(devId, chnId);
MI_ISP_GetChnParam(devId, chnId, stChnParam);
stChnParam.e3DNRLevel = 2;
MI_ISP_SetChnParam(devId, chnId, stChnParam);
MI_ISP_StartChannel(devId, chnId);

例如SCL模块需要切换Output Size,可以参考以下流程:

Port属于进程资源,如果要保证实效性,通过Disable Port => Set PortAttr => Enable Port流程做切换,需搭配cli cmd实现。

MI_SCL_DEV     devId;
MI_SCL_CHANNEL chnId;
MI_SCL_PORT    portId;
MI_SCL_OutputParam_t stSclOutputParam;
MI_SCL_GetOutputPortParam(devId, chnId, portId, &stSclOutputParam);
stSclOutputParam.stSclOutputSize.u16Width  = 1280;
stSclOutputParam.stSclOutputSize.u16Height = 720;
MI_SCL_SetOutputPortParam(devId, chnId, portId, &stSclOutputPara);

2、切换过程会需要先做Dev/Chn/Port等资源的销毁,设置参数属性,再重新创建。

在DualOS场景下,RTOS Preload APP也当成一个进程加入到多进程处理中,Linux就不能够直接跨进程去销毁RTOS所创建的资源,需要RTOS自行销毁资源,并且RTOS的开发界面并不如Linux便捷,在Linux端操作RTOS APP要通过Rpmsg的通信方法去实现。

切换参数的具体做法依赖于RTOS端定义Cli Cmd,Linux发送指定子串到RTOS,RTOS解析后实现对应的资源销毁,销毁完成后,由Linux去再创建和设定参数属性动作。

例如Sensor/VIF/ISP切换HDR的流程,可参考以下流程,rtos端接收Cli Cmd具体接口自行定义:

RTOS端:

主要依赖用户RTOS Preload APP如何设计,在这里自行定义一套解析Cli Cmd子串,呼叫对应模块的Deinit/Destroy/Unbind相关API

static int ST_Deinit(MI_U32 mod, MI_U32 dev, MI_U32 chn)
{
    if(mod == E_MI_MODULE_ID_SNR)
    {
        MI_SNR_Disable
    }
    if(mod == E_MI_MODULE_ID_VIF)
    {
        MI_VIF_DisableOutputPort
        MI_VIF_DisableDev
        MI_VIF_DestroyDevGroup
    }
    ...
    ...
    return 0;
}
static int ST_Unbind(MI_U32 mod, MI_U32 dev, MI_U32 chn)
{
    if(mod == E_MI_MODULE_ID_ISP)
    {
        MI_SYS_ChnPort_t   stSrcChnPort;
        MI_SYS_ChnPort_t   stDstChnPort;
        stSrcChnPort.eModId    = E_MI_MODULE_ID_VIF;
        stSrcChnPort.u32DevId  = 0;
        stSrcChnPort.u32ChnId  = 0;
        stSrcChnPort.u32PortId = 0;
        stDstChnPort.eModId    = E_MI_MODULE_ID_ISP;
        stDstChnPort.u32DevId  = dev;
        stDstChnPort.u32ChnId  = chn;
        stDstChnPort.u32PortId = 0;
        MI_SYS_UnBindChnPort2
    }
    ...
    ...
    return 0;
}

static int _L2RData(CLI_t * pCli, char * p)
{
    MI_U32 mod = 0;
    MI_U32 dev = 0;
    MI_U32 chn = 0;
    int    i   = 0;
    int    cnt = 0;
    char  *cmd = NULL;

    cnt = CliTokenCount(pCli);
    if(cnt == 0 || cnt < 4)
        goto HELP_EXIT;

    CliTokenPopNum(pCli, &mod, 0);
    CliTokenPopNum(pCli, &dev, 0);
    CliTokenPopNum(pCli, &chn, 0);
    cmd = CliTokenPop(pCli);
    if(!(strcmp(cmd, "deinit")))
    {
        ST_Deinit(mod, dev, chn);
    }
    else if(!(strcmp(cmd, "unbind")))
    {
        ST_Unbind(mod, dev, chn);
    }

    return eCLI_PARSE_OK;

HELP_EXIT:
    return eCLI_PARSE_ERROR;
}
SS_RTOS_CLI_CMD(pipe_cmd,
        "get linux 2 rtos data",
        "Usage: pipe_cmd [p1:mod p2:dev p3:chn p4:args...]\n",
        _L2RData);

Linux端:

1、echo cli cmd,unbind ISP和VIF
system("echo pipe_cmd ISP dev chn unbind > /proc/dualos/rtos");

2、呼叫MI ISP API Stop Chn
MI_ISP_StopChannel;

3、echo cli cmd,deinit VIF
system("echo pipe_cmd VIF dev chn deinit > /proc/dualos/rtos");

4、echo cli cmd,deinit SENSOR
system("echo pipe_cmd VIF dev chn deinit > /proc/dualos/rtos");

5、呼叫MI SENSOR Init API
MI_SNR_SetPlaneMode
MI_SNR_SetRes
MI_SNR_Enable

6、呼叫MI VIF Init API
MI_VIF_CreateDevGroup
MI_VIF_EnableDev
MI_VIF_EnableOutputPort

7、呼叫MI ISP SetChnParam API
MI_ISP_GetChnParam
MI_ISP_SetChnParam

8、呼叫MI ISP StartChn API
MI_ISP_StartChannel

9、bind ISP和VIF
MI_SYS_BindChnPort2

5. 特殊模块注意

前面章节2中提到Channel资源是不支持多进程,不能跨进程创建使用,但MI VENC和MI AI模块比较特殊,在跨进程使用时,需要先做Dup Chn的动作,Dup Chn就想当于做了Channel资源的Recreate,Create和Destroy是需要成对呼叫,Dup Chn的API接口为MI_AI_DupChnGroup、MI_VENC_DupChn。

总的来说需要有以下注意的地方:

  • Channel被创建后,在另外的进程内Dup Chn只能被调用一次。

  • 在创建Channel的进程中可以直接使用当前资源,无需Dup。

  • 无论进程中使用的是CreateChn还是Dup Chn,在退出进程时都必须呼叫DestroyChn API去销毁资源。

  • 只允许在同一个进程内调用GetStream接口(MI_VENC_GetStraem、MI_AI_Read),不同进程间不支持同时调用GetStream。

6. 规则限制总结

  1. 进程资源默认谁创建就由谁来销毁。
  2. Linux不能直接通过MI API去销毁RTOS创建的资源,只能通过Cli Cmd,发送到RTOS,通知RTOS去呼叫MI API做销毁的动作。
  3. Device资源支持多进程,跨进程使用同一个Device的时候需做创建和销毁,Channel资源和Port资源不支持多进程,不能跨进程做创建和销毁,细节说明参考章节2。
  4. RTOS端创建的资源,需要调用MI_DEVICE_RegMiSysExitCall接口注册对应的Exit Function并在Exit Function中做对应的销毁,否则在Kill掉Linux进程的情况下,RTOS资源不会主动回收,或者跑其他Linux应用会出现异常问题。
  5. 在Linux端切换参数时如果涉及资源销毁,需搭配Cli Cmd实现,第一次切换将RTOS资源销毁,在Linux上创建,后续再次切换就只需要在Linux端操作,详细做法可参考章节4。
  6. MI模块涉及Dup Chn的动作需额外注意,Dup Chn也属于进程资源的一种,Dup后要做相应的销毁动作,细节要求查看章节5。

7. 对APP行为的影响

相比之前的做法,目前MI内部会将跨进程销毁资源的动作挡掉,呼叫这类行为的API时会执行失败,返回MI ERR。目前做法只是将之前理应遵循的规则,写在code里面,确保规则执行到位。

Linux APP需清楚哪些资源是RTOS创建的,哪些资源是Linux创建,对应的只需要做Linux资源的销毁。

如果要在Linux起来后做参数切换,涉及资源的销毁和创建,需要RTOS APP定义Cli Cmd和Linux配合实现,切换的流程由Linux APP去保证。

之前允许Linux任意去创建/销毁RTOS所使用的资源存在安全性和运行稳定性的风险。

举个例子,如图1-3所示:

在RTOS创建了VIF->ISP->SCL->VENC的Pipeline,在使用RGN Attach到SCL上面画框,在Linux起来后,Linux APP同样使用了RGN,将RGN Attach到VENC上面贴时间戳,Linux这边用完RGN后,将其整个Deinit,就会导致RTOS那边的RGN也跟随着Deinit,影响到RTOS创建Piepline的正常出流效果。诸如此类问题,对MI来说以前的做法做不了防守。

图1-3 异常问题举例