硬件软件系统方案应用范例智能车大赛技术支持学习园地关于我们恩智浦官网

DRM用户层代码篇 (4) 显示关闭和资源释放

云实验室案例- DRM用户层代码篇 (4)

显示关闭和资源释放

在前面的文章中,我们通过具体的代码,重点描述了显示管道的建立,内存申请和渲染,最终的送显以及为动态显示创建pingpong buffer。

但这些代码本身并不完善,如缺乏错误处理,以及只有资源申请但缺乏资源释放的过程。 资源只申请不释放可能带来一系列严重的问题,这些问题不仅影响程序的性能和稳定性,还可能导致系统资源的枯竭和应用程序的崩溃。

在本文中,将为我们之前建立的代码建立基本的资源释放流程。

基于Pthread和linux 信号的安全退出

在最初的文章中,向大家介绍了linux信号机制以及多线程的相关概念,并展示了一个基本的安全退出流程。该流程包含以下及部分:

   1.在main函数中,阻塞当前进程对信号2的响应

   2.调用pthread_create函数新建一个信号处理线程, 并该线程中,屏蔽掉信号2

   3.在信号处理线程中调用sigwait函数等待信号。在接收到信号后(用户终端输入CTRL+C或kill命令),打印对应的log并通过quitflag标志位,向主线程发送消息。

   4.主线程发现quitflag被置位后,执行resource_free函数,并返回退出。

在本文中,我们将借用之前的这套退出流程,去触发显示相关资源的释放。

显示资源的释放

这部分内容会简单分成两部分,一部分是DRM资源的释放以及文件描述符的关闭;另一部分是缓冲区域的释放。

DRM资源的释放和文件描述符的关闭

在执行DRM相关函数的时候,我们为获取当前显示硬件信息,调用了以下的函数:

drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
res = drmModeGetResources(drm_fd);
conn = drmModeGetConnector(drm_fd, res->connectors[0]);
encoder = drmModeGetEncoder(drm_fd, conn->encoders[0]);

在linux和libdrm的标准库函数中,都有对应的函数去释放结构体,并关闭文件描述符,他们分别对应为:

close(drm_fd);
drmModeFreeResources(res);
drmModeFreeConnector(conn);
drmModeFreeEncoder(encoder);

缓冲区域的释放

在前文中,关于从DRM获取缓冲区域,我们依次调用了以下函数,包括内存申请和内存的映射:

ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
ret = drmModeAddFB(drm_fd, WIDTH, HEIGHT, creq.bpp, creq.bpp,
               creq.pitch, creq.handle, &buffer_id);
ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
__u32 *fb_base = mmap(0, creq.size, PROT_READ | PROT_WRITE, AP_SHARED,
                           drm_fd, mreq.offset);

为实现这些资源的释放,需要调用的函数则包括:

drmModeRmFB(fd, buf->buf_id);
munmap(buf->fb_base, buf->size);
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);

这些函数会通知DRM将创建出来的内存销毁,并取消内存映射。

并且随着代码的不断增加,我们需要对代码进行一定程度上的归纳,这里首先为buffer创建一个结构体,该结构体去负责管理buffer的所有相关参数传递:

struct drm_buffer {
    __u32 width;
    __u32 height;
    __u32 bpp;
    __u32 size;
    __u32 *fb_base;
    __u32 handle;
    __u32 buf_id;
    __u32 self_id;
};

   width:宽

   height:高

   bpp:bits per pixel

   size:buffer大小

   fb_base:映射到用户空间的基地址

   handle:buffer句柄

   buf_id:buffer在DRM内部可识别的buffer id号

   self_id:buffer自己的编号

进而在此基础上,对buffer的创建,绘制以及销毁分别创建三个独立的函数,它们分别为:

pingpong_buffer_alloc(drm_fd, &pingpong_buffers[i]);
pingpong_buffer_draw(&pingpong_buffers[i]);
pingpong_buffer_destroy(drm_fd, &pingpong_buffers[i]);

 

最终添加上基于pthread和信号量的退出机制,并优化代码结构后后,至此完整的代码如下所示:

// SPDX-License-Identifier: GPL-2.0+
 /* * 
 * Copyright 2024 NXP
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
 
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
 
#include <drm/drm.h>
#include <drm/drm_mode.h>           
#include <xf86drm.h>
#include <xf86drmMode.h>
 
#include <signal.h>
#include <pthread.h>
 
#define WIDTH 1280
#define HEIGHT 800
 
struct drm_buffer {
    __u32 width;
    __u32 height;
    __u32 bpp;
    __u32 size;
    __u32 *fb_base;
    __u32 handle;
    __u32 buf_id;
    __u32 self_id;
};
 
static sigset_t sigset_v;
static bool quitflag;
 
static int signal_thread(void *arg)
{
    int sig;
 
    pthread_sigmask(SIG_BLOCK, &sigset_v, NULL);
 
    while (1) {
       sigwait(&sigset_v, &sig);
       if (sig == SIGINT) {
           printf("Ctrl-C received. Exiting.\n");
       } else {
           printf("Unknown signal. Still exiting\n");
       }
       quitflag = 1;
       break;
    }
    return 0;
}
 
static void resource_free(void)
{
    printf("resource_free now!\n\r");
}
 
void pingpong_buffer_alloc(int fd, struct drm_buffer* buf){
    int ret;
    struct drm_mode_create_dumb creq;
    struct drm_mode_map_dumb mreq;
 
    memset(&creq, 0, sizeof(creq));
    creq.width = buf->width;
    creq.height = buf->height;
    creq.bpp = buf->bpp;
 
    ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
 
    __u32 buffer_id;
 
    ret = drmModeAddFB(fd, WIDTH, HEIGHT, creq.bpp, creq.bpp,
              creq.pitch, creq.handle, &buffer_id);
 
    memset(&mreq, 0, sizeof(mreq));
    mreq.handle = creq.handle;
    ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
 
    buf->handle = creq.handle;
    buf->buf_id = buffer_id;
    buf->size = creq.size;
    buf->fb_base = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED,
                         fd, mreq.offset);
}
 
void pingpong_buffer_draw(struct drm_buffer* buf){
    __u32 color[2] = {0x00FF0000, 0x0000FF00};
    for (int j = 0; j < buf->size / 4; j++)
       buf->fb_base[j] = color[buf->self_id];
}
 
void pingpong_buffer_destroy(int fd, struct drm_buffer* buf){
    struct drm_mode_destroy_dumb destroy;
    memset(&destroy, 0, sizeof(destroy));
 
    drmModeRmFB(fd, buf->buf_id);
 
    munmap(buf->fb_base, buf->size);
 
    destroy.handle = buf->handle;
    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
 
int main()
{
    pthread_t sigtid; 
    sigemptyset(&sigset_v);  
    sigaddset(&sigset_v, SIGINT);
 
    pthread_sigmask(SIG_BLOCK, &sigset_v, NULL);                               
    pthread_create(&sigtid, NULL, (void *)&signal_thread, NULL);
 
    int ret;
    int drm_fd;
    struct drm_buffer pingpong_buffers[2];
 
    drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
 
    ret = drmSetMaster(drm_fd);
 
    drmModeRes *res;
    drmModeConnector *conn;
 
    res = drmModeGetResources(drm_fd);
    conn = drmModeGetConnector(drm_fd, res->connectors[0]);
 
    pingpong_buffers[0].width = pingpong_buffers[1].width = conn->modes[0].hdisplay;
    pingpong_buffers[0].height = pingpong_buffers[1].height = conn->modes[0].vdisplay;
    pingpong_buffers[0].bpp = pingpong_buffers[1].bpp = 32;
    
    pingpong_buffers[0].self_id = 0;
    pingpong_buffers[1].self_id = 1;
 
    drmModeEncoder *encoder;
    encoder = drmModeGetEncoder(drm_fd, conn->encoders[0]);
 
    int g_crtc_id;
 
    for (int j = 0; j < res->count_crtcs; j++) {
       if (encoder->possible_crtcs & (1 << j)) {
           g_crtc_id = res->crtcs[j];
       }
    }
 
    for(int i = 0; i < 2; i++){
       pingpong_buffer_alloc(drm_fd, &pingpong_buffers[i]);
       pingpong_buffer_draw(&pingpong_buffers[i]);
    }
 
    drmModeModeInfo mode;
    memcpy(&mode, &conn->modes[0], sizeof(mode));
 
    __u32 *conn_id = &(res->connectors[0]);
 
    int current_buffer = 0;
    while(!quitflag){
       ret = drmModeSetCrtc(drm_fd, g_crtc_id, pingpong_buffers[current_buffer].buf_id,   
                      0, 0, conn_id, 1, &mode);
       current_buffer ^= 1;
       sleep(1);
    }
 
    for(int i = 0; i < 2; i++)
       pingpong_buffer_destroy(drm_fd, &pingpong_buffers[i]);
 
    drmModeFreeConnector(conn);
    drmModeFreeEncoder(encoder);
    drmModeFreeResources(res);
    close(drm_fd);
    
    printf("main task finished!\n\r");
 
    return 0;
}

打开云实验室,编译执行代码,将会看到来回切换的两张图片,在用户终端输入“CTRL+C”命令,程序退出,并打印“main task finished!”