云实验室案例- 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!”