当前位置: 首页 > article >正文

海思mmp学习——tde

本文学习 sample_tde 历程,来提升 mmp 的理解。

TDETwo Dimension Engine)功能函数参考提供 2D 加速相关操作。

一、释放资源

该函数主要用于程序在收到 SIGINT(Ctrl+C)或 SIGTERM(kill 命令) 时执行清理操作,防止资源泄露。

/******************************************************************************
* function : to process abnormal case
******************************************************************************/
void SAMPLE_TDE_HandleSig(HI_S32 signo)
{
    if (SIGINT == signo || SIGTERM == signo)
    {   
        /*释放映射的屏幕缓冲区*/
        if (NULL != g_pu8Screen)
        {
            (HI_VOID)munmap(g_pu8Screen, g_u32Size);
            g_pu8Screen = NULL;
        }
        
        /*释放背景缓冲区*/
        if (NULL != g_pu8BackGroundVir)
        {
            HI_MPI_SYS_MmzFree(g_stBackGround.PhyAddr, g_pu8BackGroundVir);
            g_pu8BackGroundVir = NULL;
        }

        /*关闭帧缓冲设备*/
        if (g_s32Fd != -1)
        {
            close(g_s32Fd);
            g_s32Fd = -1;
        }

        /*关闭 TDE 图像引擎*/
        HI_TDE2_Close();

        /*关闭 HDMI 输出*/
        if (IntType & VO_INTF_HDMI)
        {
             SAMPLE_COMM_VO_HdmiStop();
        }
        /*禁用视频输出*/
        HI_MPI_VO_Disable(VoDev);
        /*释放系统资源*/
        SAMPLE_COMM_SYS_Exit();
        printf("\033[0;31mprogram termination abnormally!\033[0;39m\n");
    }
    exit(-1);
}

分析API

HI_MPI_SYS_MmzFree(g_stBackGround.PhyAddr, g_pu8BackGroundVir);

查阅 HiMPP V4.0 媒体处理软件开发参考.pdf ,2.系统控制章节。

要释放背景缓冲区,需要提供物理和虚拟地址。

HI_MPI_VO_Disable(VoDev);

查阅 HiMPP V4.0 媒体处理软件开发参考.pdf ,4.视频输出章节。

二、读取图像数据

从文件中读取图像数据,并填充 TDE2_SURFACE_S 结构体,以便后续的 TDE(图形加速引擎)处理。

static HI_S32 TDE_CreateSurfaceByFile(const HI_CHAR *pszFileName, TDE2_SURFACE_S *pstSurface, HI_U8 *pu8Virt)
{
    FILE *fp;
    HI_U32 colorfmt, w, h, stride;
    HI_U64 packagelen;
    
    /*参数检查,防止非法访问*/
    if((NULL == pszFileName) || (NULL == pstSurface))
    {
        TDE_PRINT("%s, LINE %d, NULL ptr!\n", __FUNCTION__, __LINE__);
        return -1;
    }

    /*打开文件*/
    fp = fopen(pszFileName, "rb");
    if(NULL == fp)
    {
        TDE_PRINT("error when open pszFileName %s, line:%d\n", pszFileName, __LINE__);
        return -1;
    }

    /*读取图片的基本信息
    * 颜色格式、宽度、高度、步长
    */
    if (4 != fread(&colorfmt, 1, 4, fp))
    {
        TDE_PRINT("error when read pszFileName %s, line:%d\n", pszFileName, __LINE__);
        fclose(fp);
        return -1;
    }
    if (4 != fread(&w, 1, 4, fp))
    {
        TDE_PRINT("error when read pszFileName %s, line:%d\n", pszFileName, __LINE__);
        fclose(fp);
        return -1;
    }
    if (4 != fread(&h, 1, 4, fp))
    {
        TDE_PRINT("error when read pszFileName %s, line:%d\n", pszFileName, __LINE__);
        fclose(fp);
        return -1;
    }
    if (4 != fread(&stride, 1, 4, fp))
    {
        TDE_PRINT("error when read pszFileName %s, line:%d\n", pszFileName, __LINE__);
        fclose(fp);
        return -1;
    }

    /*填充位图结构体*/
    pstSurface->enColorFmt = colorfmt;
    pstSurface->u32Width = w;
    pstSurface->u32Height = h;
    pstSurface->u32Stride = stride;
    pstSurface->u8Alpha0 = 0xff;
    pstSurface->u8Alpha1 = 0xff;
    pstSurface->bAlphaMax255 = HI_TRUE;
    pstSurface->bAlphaExt1555 = HI_TRUE;

    /*计算图像数据大小*/
    packagelen = (HI_U64)stride * (HI_U64)h;
    if ( packagelen > 0x7FFFFFFF)
    {
        TDE_PRINT("stride * h not valid: %d %d, line:%d\n", stride, h, __LINE__);
        fclose(fp);
        return -1;
    }

    //读取图像数据
    fread(pu8Virt, 1, stride*h, fp);

    fclose(fp);

    return 0;
}

分析API

全局区域,定义了位图 surface 结构体3个变量:

static TDE2_SURFACE_S g_stScreen[2];
static TDE2_SURFACE_S g_stBackGround;
static TDE2_SURFACE_S g_stImgSur[N_IMAGES];

三、图像显示

static HI_VOID circumrotate (HI_U32 u32CurOnShow)
{
    /*TDE 引擎的任务句柄。*/
    TDE_HANDLE s32Handle;
    /*TDE 操作选项,设置颜色键模式等。*/
    TDE2_OPT_S stOpt = {0};
    /*旋转中心点的坐标。*/
    HI_FLOAT eXMid, eYMid;
    /*旋转半径*/
    HI_FLOAT eRadius;
    HI_U32 i;
    /*当前帧的进度,用于计算旋转角度*/
    HI_FLOAT f;
    /*下一个显示的图像缓冲区索引*/
    HI_U32 u32NextOnShow;
    /*源图像的矩形区域*/
    TDE2_RECT_S stSrcRect;
    /*目标屏幕上的矩形区域*/
    TDE2_RECT_S stDstRect;
    /*函数返回值,用于错误处理。*/
    HI_S32 s32Ret = HI_SUCCESS;

    /*切换到下一个屏幕缓冲区,u32CurOnShow 和 u32NextOnShow 是两个缓冲区的索引,通过位取反操作切换。*/
    u32NextOnShow = !u32CurOnShow;
    
    /*设置 TDE 操作选项,主要是颜色键模式和透明度处理。*/
    stOpt.enOutAlphaFrom = TDE2_COLORKEY_MODE_FOREGROUND;
    stOpt.unColorKeyValue.struCkARGB.stRed.u8CompMask = 0xff;
    stOpt.unColorKeyValue.struCkARGB.stGreen.u8CompMask = 0xff;
    stOpt.unColorKeyValue.struCkARGB.stBlue.u8CompMask = 0xff;
    stOpt.enColorKeyMode = TDE2_COLORKEY_MODE_FOREGROUND;
    stOpt.unColorKeyValue.struCkARGB.stAlpha.bCompIgnore = HI_TRUE;

    //计算当前帧的进度 f,用于控制旋转动画的进度。
    f = (float) (g_s32FrameNum % CYCLE_LEN) / CYCLE_LEN;

    /*源图像*/    
    stSrcRect.s32Xpos = 0;
    stSrcRect.s32Ypos = 0;
    stSrcRect.u32Width = g_stBackGround.u32Width;
    stSrcRect.u32Height = g_stBackGround.u32Height;

    //旋转中心点
    eXMid = g_stBackGround.u32Width/2.16f;
    eYMid = g_stBackGround.u32Height/2.304f;

    eRadius = MIN (eXMid, eYMid) / 2.0f;

    /* 1. start job */
    s32Handle = HI_TDE2_BeginJob();
    if(HI_ERR_TDE_INVALID_HANDLE == s32Handle)
    {
        TDE_PRINT("start job failed!\n");
        return ;
    }

    /* 2. bitblt background to screen */
    s32Ret = HI_TDE2_QuickCopy(s32Handle, &g_stBackGround, &stSrcRect,
        &g_stScreen[u32NextOnShow], &stSrcRect);
    if(s32Ret < 0)
    {
        TDE_PRINT("Line:%d failed,ret=0x%x!\n", __LINE__, s32Ret);
        HI_TDE2_CancelJob(s32Handle);
        return ;
    }
    /*绘制旋转图像*/
    for(i = 0; i < N_IMAGES; i++)
    {
        HI_FLOAT ang;
        HI_FLOAT r;

        stSrcRect.s32Xpos = 0;
        stSrcRect.s32Ypos = 0;
        stSrcRect.u32Width = g_stImgSur[i].u32Width;
        stSrcRect.u32Height = g_stImgSur[i].u32Height;

        /* 3. calculate new pisition  */
        /*计算旋转参数*/
        ang = 2.0f * (HI_FLOAT) M_PI * (HI_FLOAT) i / N_IMAGES - f * 2.0f * (HI_FLOAT) M_PI;
        r = eRadius + (eRadius / 3.0f) * sinf (f * 2.0 * M_PI);

        /*计算目标位置*/
        stDstRect.s32Xpos = eXMid + r * cosf (ang) - g_stImgSur[i].u32Width / 2.0f;;
        stDstRect.s32Ypos = eYMid + r * sinf (ang) - g_stImgSur[i].u32Height / 2.0f;
        stDstRect.u32Width = g_stImgSur[i].u32Width;
        stDstRect.u32Height = g_stImgSur[i].u32Height;

        /* 4. bitblt image to screen */
        s32Ret = HI_TDE2_Bitblit(s32Handle, &g_stScreen[u32NextOnShow], &stDstRect,
            &g_stImgSur[i], &stSrcRect, &g_stScreen[u32NextOnShow], &stDstRect, &stOpt);
        if(s32Ret < 0)
        {
        	TDE_PRINT("Line:%d,HI_TDE2_Bitblit failed,ret=0x%x!\n", __LINE__, s32Ret);
        	HI_TDE2_CancelJob(s32Handle);
        	return ;
        }
    }

    /* 5. submit job */
    s32Ret = HI_TDE2_EndJob(s32Handle, HI_FALSE, HI_TRUE, 1000);
    if(s32Ret < 0)
    {
        TDE_PRINT("Line:%d,HI_TDE2_EndJob failed,ret=0x%x!\n", __LINE__, s32Ret);
        HI_TDE2_CancelJob(s32Handle);
        return ;
    }

    g_s32FrameNum++;
    return;
}

分析API

TDE2_OPT_S stOpt = {0};

/*设置 TDE 操作选项,主要是颜色键模式和透明度处理。*/
    stOpt.enOutAlphaFrom = TDE2_COLORKEY_MODE_FOREGROUND;
    stOpt.unColorKeyValue.struCkARGB.stRed.u8CompMask = 0xff;
    stOpt.unColorKeyValue.struCkARGB.stGreen.u8CompMask = 0xff;
    stOpt.unColorKeyValue.struCkARGB.stBlue.u8CompMask = 0xff;
    stOpt.enColorKeyMode = TDE2_COLORKEY_MODE_FOREGROUND;
    stOpt.unColorKeyValue.struCkARGB.stAlpha.bCompIgnore = HI_TRUE;

    /*源图像的矩形区域*/
    TDE2_RECT_S stSrcRect;
    /*目标屏幕上的矩形区域*/
    TDE2_RECT_S stDstRect;


    /*源图像*/    
    stSrcRect.s32Xpos = 0;
    stSrcRect.s32Ypos = 0;
    stSrcRect.u32Width = g_stBackGround.u32Width;
    stSrcRect.u32Height = g_stBackGround.u32Height;

    /* 1. start job */
    s32Handle = HI_TDE2_BeginJob();
    if(HI_ERR_TDE_INVALID_HANDLE == s32Handle)
    {
        TDE_PRINT("start job failed!\n");
        return ;
    }

    /* 2. bitblt background to screen */
    s32Ret = HI_TDE2_QuickCopy(s32Handle, &g_stBackGround, &stSrcRect,
        &g_stScreen[u32NextOnShow], &stSrcRect);
    if(s32Ret < 0)
    {
        TDE_PRINT("Line:%d failed,ret=0x%x!\n", __LINE__, s32Ret);
        HI_TDE2_CancelJob(s32Handle);
        return ;
    }

/* 4. bitblt image to screen */
        s32Ret = HI_TDE2_Bitblit(s32Handle, &g_stScreen[u32NextOnShow], &stDstRect,
            &g_stImgSur[i], &stSrcRect, &g_stScreen[u32NextOnShow], &stDstRect, &stOpt);
        if(s32Ret < 0)
        {
        	TDE_PRINT("Line:%d,HI_TDE2_Bitblit failed,ret=0x%x!\n", __LINE__, s32Ret);
        	HI_TDE2_CancelJob(s32Handle);
        	return ;
        }

    /* 5. submit job */
    s32Ret = HI_TDE2_EndJob(s32Handle, HI_FALSE, HI_TRUE, 1000);
    if(s32Ret < 0)
    {
        TDE_PRINT("Line:%d,HI_TDE2_EndJob failed,ret=0x%x!\n", __LINE__, s32Ret);
        HI_TDE2_CancelJob(s32Handle);
        return ;
    }

四、绘图

1. 操作缓冲区

HI_S32 TDE_DrawGraphicSample()
{
    HI_U32 u32Times;
    HI_U32 u32PhyAddr;
    HI_S32 s32Ret = -1;
    HI_U32 i = 0;
    HI_BOOL bShow;
    HIFB_ALPHA_S stAlpha = {0};

    /* 1. open tde device */
    s32Ret = HI_TDE2_Open();
    if (HI_SUCCESS != s32Ret)
    {
        TDE_PRINT("HI_TDE2_Open failed:0x%x\n", s32Ret);
        return s32Ret;
    }

    /* 2. framebuffer operation */
    g_s32Fd = open("/dev/fb0", O_RDWR);
    if (g_s32Fd < 0)
    {
        TDE_PRINT("open frame buffer device error\n");
        goto FB_OPEN_ERROR;
    }
	/*配置帧缓冲区的 Alpha 属性*/
    stAlpha.bAlphaChannel = HI_FALSE;
    stAlpha.bAlphaEnable = HI_FALSE;
    if (ioctl(g_s32Fd, FBIOPUT_ALPHA_HIFB, &stAlpha) < 0)
    {
        TDE_PRINT("Put alpha info failed!\n");
        goto FB_PROCESS_ERROR0;
    }

    struct hifb_info info;
    /*获取帧缓冲区的固定信息*/    
    if (ioctl(g_s32Fd, FBIOGET_SCREENINFO_HIFB, &info) < 0)
    {
        SAMPLE_PRT("FBIOGET_SCREENINFO_HIFB failed!\n");
        close(g_s32Fd);
        return HI_NULL;
    }

    /*配置屏幕的分辨率和颜色格式*/
    info.vinfo.xres = SCREEN_WIDTH;
    info.vinfo.yres = SCREEN_HEIGHT;
    info.oinfo.sarea.w = SCREEN_WIDTH;
    info.oinfo.sarea.h = SCREEN_HEIGHT;
    info.oinfo.bpp = 16;
    info.activate = 0;
    info.vinfo.fmt = HIFB_FMT_ARGB1555;

    /*应用屏幕的可变信息*/
    if (ioctl(g_s32Fd, FBIOPUT_SCREENINFO_HIFB, &info) < 0)
    {
        TDE_PRINT("Put variable screen info failed!\n");
        goto FB_PROCESS_ERROR0;
    }

    if (ioctl(g_s32Fd, FBIOGET_SCREENINFO_HIFB, &info) < 0)
    {
        TDE_PRINT("Get fix screen info failed!\n");
        goto FB_PROCESS_ERROR0;
    }

    /*获取帧缓冲区的内存地址和大小*/
    g_u32Size   = info.oinfo.fblen;
    u32PhyAddr  = (HI_U32)info.oinfo.fbmem;
    g_pu8Screen = (HI_U8*)(info.oinfo.fbmem);;

    /*清空帧缓冲区*/
    memset_s(g_pu8Screen, g_u32Size, 0x00, g_u32Size);
    /*获取屏幕的步幅*/
    g_stScreen[0].u32Stride = info.oinfo.stride;

2.1 API分析

ioctl 的 cmd 的参数命令:

cmddata 参数:

对于下面这段代码,FBIOGET_VSCREENINFO对应struct fb_var_screeninfo *类型,给其成员进行了赋值,其中有屏幕分辨率和虚拟分辨率,偏移,时钟,颜色等。

if (ioctl(g_s32Fd, FBIOGET_VSCREENINFO, &stVarInfo) < 0)
    {
        TDE_PRINT("Get variable screen info failed!\n");
        goto FB_PROCESS_ERROR0;
    }

    stVarInfo.xres_virtual	 	= SCREEN_WIDTH;
    stVarInfo.yres_virtual		= SCREEN_HEIGHT*2;
    stVarInfo.xres      		= SCREEN_WIDTH;
    stVarInfo.yres      		= SCREEN_HEIGHT;
    stVarInfo.activate  		= FB_ACTIVATE_NOW;
    stVarInfo.bits_per_pixel	= 16;
    stVarInfo.xoffset = 0;
    stVarInfo.yoffset = 0;
    stVarInfo.red   = stR32;
    stVarInfo.green = stG32;
    stVarInfo.blue  = stB32;
    stVarInfo.transp = stA32;

2. 创建图形表面

    /* 3. create surface */
    /*创建屏幕表面*/
    g_stScreen[0].enColorFmt = PIXFMT;
    g_stScreen[0].PhyAddr = u32PhyAddr;
    g_stScreen[0].u32Width = SCREEN_WIDTH;
    g_stScreen[0].u32Height = SCREEN_HEIGHT;

    g_stScreen[0].bAlphaMax255 = HI_TRUE;

    g_stScreen[1] = g_stScreen[0];
    g_stScreen[1].PhyAddr = g_stScreen[0].PhyAddr + (HI_U64)g_stScreen[0].u32Stride * (HI_U64)g_stScreen[0].u32Height;

    /* allocate memory (720*576*2*N_IMAGES bytes) to save Images' infornation */
    if (HI_FAILURE == HI_MPI_SYS_MmzAlloc(&(g_stBackGround.PhyAddr), ((void**)&g_pu8BackGroundVir),
            NULL, NULL, 720*576*2*N_IMAGES))
    {
        TDE_PRINT("allocate memory (720*576*2*N_IMAGES bytes) failed\n");
        goto FB_PROCESS_ERROR1;
    }
    TDE_CreateSurfaceByFile(BACKGROUND_NAME, &g_stBackGround, g_pu8BackGroundVir);

    /*创建子图像表面*/
    g_stImgSur[0].PhyAddr = g_stBackGround.PhyAddr + (HI_U64)g_stBackGround.u32Stride * (HI_U64)g_stBackGround.u32Height;
    for(i = 0; i < N_IMAGES - 1; i++)
    {
        TDE_CreateSurfaceByFile(pszImageNames[i], &g_stImgSur[i],
            g_pu8BackGroundVir + ((HI_U32)g_stImgSur[i].PhyAddr - g_stBackGround.PhyAddr));
        g_stImgSur[i+1].PhyAddr = g_stImgSur[i].PhyAddr + (HI_U64)g_stImgSur[i].u32Stride * (HI_U64)g_stImgSur[i].u32Height;
    }
    TDE_CreateSurfaceByFile(pszImageNames[i], &g_stImgSur[i],
            g_pu8BackGroundVir + ((HI_U32)g_stImgSur[i].PhyAddr - g_stBackGround.PhyAddr));

    /*显示帧缓冲区*/
    bShow = HI_TRUE;
    if (ioctl(g_s32Fd, FBIOPUT_SHOW_HIFB, &bShow) < 0)
    {
        fprintf (stderr, "Couldn't show fb\n");
        goto FB_PROCESS_ERROR2;
    }

    /*初始化帧计数器*/
    g_s32FrameNum = 0;

    /* 3. use tde and framebuffer to realize rotational effect */
    /*实现旋转*/
    for (u32Times = 0; u32Times < 20; u32Times++)
    {
        circumrotate(u32Times%2);

        info.oinfo.sarea.y = (u32Times%2)?0:576;

        /*set frame buffer start position*/
        /*更新帧缓冲区的显示位置*/
        if (ioctl(g_s32Fd, FBIOPAN_DISPLAY_HIFB, &info.oinfo) < 0)
        {
            TDE_PRINT("FBIOPAN_DISPLAY error\n");
            goto FB_PROCESS_ERROR2;
        }
        sleep(1);
    }

五、启动设备、运行示例

HI_S32 sample_circumrotate(HI_VOID)
{
    /*视频输出设备(VO)的配置结构体,用于存储 VO 的初始化参数。*/
    SAMPLE_VO_CONFIG_S vo_config = {0};
    /*显存池(Video Buffer)的配置结构体,用于初始化显存池。*/
    VB_CONFIG_S vb_conf = {0};
    HI_S32 ret = 0;

    /*1 enable Vo device HD first*/
    /*初始化显存池*/
    vb_conf.u32MaxPoolCnt = 16;
    ret = SAMPLE_COMM_SYS_Init(&vb_conf);
    if (ret != HI_SUCCESS) {
        SAMPLE_PRT("SAMPLE_COMM_SYS_Init failed with %d!\n", ret);
        return -1;
    }

    /*获取默认的 VO 配置*/
    ret = SAMPLE_COMM_VO_GetDefConfig(&vo_config);
    if (ret != HI_SUCCESS) {
        SAMPLE_PRT("SAMPLE_COMM_VO_GetDefConfig failed with %d!\n", ret);
        return -1;
    }

    /*启动视频输出设备(VO)*/
    ret = SAMPLE_COMM_VO_StartVO(&vo_config);
    if (ret != HI_SUCCESS) {
        SAMPLE_PRT("SAMPLE_COMM_VO_StartVO failed with %d!\n", ret);
        goto ERR0;
    }
    /*2 run tde sample which draw grahpic on HiFB memory*/
    /*运行 TDE 图形绘制示例*/
    ret = TDE_DrawGraphicSample();
    if (ret != HI_SUCCESS) {
        goto ERR1;
    }
ERR1:
    SAMPLE_COMM_VO_StopVO(&vo_config);
ERR0:
    SAMPLE_COMM_SYS_Exit();

    return ret;
}

六、主函数

#ifdef __HuaweiLite__
#define SAMPLE_HIFB_NAME "sample"
int app_main(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif
{
    HI_S32 ret = 0;
#ifdef __HuaweiLite__
#else
    signal(SIGINT, SAMPLE_TDE_HandleSig);
    signal(SIGTERM, SAMPLE_TDE_HandleSig);
#endif
    if (argc != 2) {
        sample_usage1(argv[0]);
        return HI_FAILURE;
    }

    if ((*argv[1] != '0') || (strlen(argv[1]) != 1)) {
        SAMPLE_PRT("index invaild!only support index 0, please try again.\n");
        sample_usage1(argv[0]);
        return HI_FAILURE;
    }

    if (!strncmp(argv[1], "-h", 2)) {
        sample_usage1(argv[0]);
        return HI_SUCCESS;
    }

    SAMPLE_PRT("\nindex 0 selected.\n");
    ret = sample_circumrotate();
    if (ret == HI_SUCCESS) {
        SAMPLE_PRT("program exit normally!\n");
    } else {
        SAMPLE_PRT("program exit abnormally!\n");
    }
    return ret;
}

七、提供选项

static inline HI_VOID sample_usage2(HI_VOID)
{
    SAMPLE_PRT("\n\n/****************index******************/\n");
    SAMPLE_PRT("please choose the case which you want to run:\n");
    SAMPLE_PRT("\t0: circumrotate \n");
    return;
}

static inline HI_VOID sample_usage1(HI_CHAR* argv)
{
    SAMPLE_PRT("Usage : %s <index>\n", argv);
    sample_usage2();
    return;
}


http://www.kler.cn/a/582446.html

相关文章:

  • Webpack Vite 前端高频面试题
  • uniapp笔记-pages.json全局基本配置
  • 相对与绝对路径的关系
  • 【论文阅读】LightTS:少即是多:基于轻采样的MLP结构的快速多元时间序列预测
  • 实现客户端的网络不影响主线程且随时与服务器通信
  • html-表格标签
  • 蓝桥杯省赛真题C++B组-裁纸刀2022
  • 计算机视觉实战|NeRF 实战教程:基于 nerf_recon_dataset 的三维重建
  • MySQL库和表的操作详解:从创建库到表的管理全面指南
  • 45.HarmonyOS NEXT Layout布局组件系统详解(十二):高级应用案例与性能优化
  • 无标记点动作捕捉系统,无需穿戴设备,摄像头智能采集人体运动姿态
  • Webpack 优化深度解析:从构建性能到输出优化的全面指南
  • TDengine SQL 函数
  • JVM和运行时数据区
  • 国产化信创操作系统的电脑,能运行windows程序吗
  • 关于回归中R2指标的理解
  • docker搭建elk
  • 【学写LibreCAD】 4.1 RS_Undoable文件
  • 【Linux内核系列】:文件系统
  • 一文说清docker及docker compose的应用和部署