从零构建DRM显示程序libdrm核心API实战指南在Linux图形开发领域DRMDirect Rendering Manager作为内核级的显示管理框架承担着协调GPU、显示设备和应用程序的关键角色。而libdrm则是连接用户空间与内核DRM子系统的桥梁通过封装底层ioctl调用为开发者提供了更友好的编程接口。本文将彻底抛开抽象理论直接带领读者用C语言和libdrm库编写一个能在屏幕上显示纯色画面的完整程序。1. 环境准备与基础概念1.1 开发环境配置首先确保系统已安装必要的开发工具和库文件sudo apt install build-essential libdrm-dev验证libdrm版本本文基于libdrm 2.4.113pkg-config --modversion libdrm1.2 DRM核心对象模型在开始编码前需要理解DRM框架中的几个核心对象CRTC显示控制器负责扫描输出时序和显示模式Connector物理连接器如HDMI、DP接口Encoder将数字信号转换为物理接口信号Framebuffer存储像素数据的缓冲区这些对象通过libdrm提供的API进行管理和操作形成完整的显示流水线。2. 初始化DRM设备2.1 打开DRM设备文件Linux系统中DRM设备通常以/dev/dri/cardX形式存在。我们首先需要获取设备文件描述符#include xf86drm.h #include xf86drmMode.h int open_drm_device(const char *path) { int fd open(path, O_RDWR | O_CLOEXEC); if (fd 0) { perror(Failed to open DRM device); return -1; } uint64_t has_dumb; if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, has_dumb) 0 || !has_dumb) { fprintf(stderr, Device does not support dumb buffers\n); close(fd); return -1; } return fd; }2.2 获取显示资源成功打开设备后我们需要获取当前可用的显示资源drmModeRes *get_drm_resources(int fd) { drmModeRes *res drmModeGetResources(fd); if (!res) { perror(Failed to get DRM resources); return NULL; } if (res-count_crtcs 0 || res-count_connectors 0) { fprintf(stderr, No CRTCs or connectors found\n); drmModeFreeResources(res); return NULL; } return res; }3. 配置显示管线3.1 选择有效的连接器我们需要遍历所有连接器找到第一个已连接且可用的显示设备drmModeConnector *find_usable_connector(int fd, drmModeRes *res) { for (int i 0; i res-count_connectors; i) { drmModeConnector *conn drmModeGetConnector(fd, res-connectors[i]); if (!conn) continue; if (conn-connection DRM_MODE_CONNECTED conn-count_modes 0) { return conn; } drmModeFreeConnector(conn); } return NULL; }3.2 创建帧缓冲区接下来创建用于显示的帧缓冲区struct framebuffer { uint32_t width; uint32_t height; uint32_t pitch; uint32_t handle; uint32_t id; uint8_t *map; }; int create_dumb_buffer(int fd, struct framebuffer *buf) { struct drm_mode_create_dumb create { .width buf-width, .height buf-height, .bpp 32 }; if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, create) 0) { perror(Failed to create dumb buffer); return -1; } buf-pitch create.pitch; buf-handle create.handle; // 将缓冲区映射到用户空间 struct drm_mode_map_dumb map { .handle buf-handle }; if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, map) 0) { perror(Failed to map dumb buffer); return -1; } buf-map mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map.offset); if (buf-map MAP_FAILED) { perror(Failed to mmap dumb buffer); return -1; } // 创建帧缓冲区对象 if (drmModeAddFB(fd, buf-width, buf-height, 24, 32, buf-pitch, buf-handle, buf-id) 0) { perror(Failed to add FB); return -1; } return 0; }4. 显示配置与渲染循环4.1 设置CRTC显示模式现在我们可以将帧缓冲区与CRTC关联开始显示int set_crtc_mode(int fd, drmModeRes *res, drmModeConnector *conn, struct framebuffer *buf) { // 获取第一个可用的CRTC ID uint32_t crtc_id res-crtcs[0]; // 获取连接器的第一个显示模式 drmModeModeInfo *mode conn-modes[0]; // 设置CRTC if (drmModeSetCrtc(fd, crtc_id, buf-id, 0, 0, conn-connector_id, 1, mode) 0) { perror(Failed to set CRTC); return -1; } return 0; }4.2 填充颜色与主循环最后我们填充帧缓冲区并进入显示循环void fill_buffer(struct framebuffer *buf, uint32_t color) { uint32_t *pixels (uint32_t *)buf-map; for (uint32_t y 0; y buf-height; y) { for (uint32_t x 0; x buf-width; x) { pixels[y * (buf-pitch / 4) x] color; } } } int main() { int fd open_drm_device(/dev/dri/card0); if (fd 0) return 1; drmModeRes *res get_drm_resources(fd); if (!res) { close(fd); return 1; } drmModeConnector *conn find_usable_connector(fd, res); if (!conn) { drmModeFreeResources(res); close(fd); return 1; } struct framebuffer buf { .width conn-modes[0].hdisplay, .height conn-modes[0].vdisplay }; if (create_dumb_buffer(fd, buf) 0) { drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 1; } if (set_crtc_mode(fd, res, conn, buf) 0) { munmap(buf.map, buf.pitch * buf.height); drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 1; } // 填充红色 fill_buffer(buf, 0xFFFF0000); printf(Displaying red screen. Press Enter to exit...); getchar(); // 清理资源 munmap(buf.map, buf.pitch * buf.height); drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 0; }5. 进阶优化与错误处理5.1 双缓冲与页面翻转为避免画面撕裂实际应用中应使用双缓冲和原子模式设置int setup_page_flip(int fd, uint32_t crtc_id, uint32_t fb_id) { struct drm_mode_crtc_page_flip flip { .fb_id fb_id, .crtc_id crtc_id, .flags DRM_MODE_PAGE_FLIP_EVENT }; return drmIoctl(fd, DRM_IOCTL_MODE_PAGE_FLIP, flip); }5.2 事件处理通过文件描述符监听DRM事件void handle_drm_events(int fd) { fd_set fds; FD_ZERO(fds); FD_SET(fd, fds); while (1) { int ret select(fd 1, fds, NULL, NULL, NULL); if (ret 0) break; drmEventContext evctx { .version DRM_EVENT_CONTEXT_VERSION, .page_flip_handler page_flip_handler }; drmHandleEvent(fd, evctx); } }5.3 资源释放确保程序退出时正确释放所有资源void cleanup(int fd, struct framebuffer *bufs, int count) { for (int i 0; i count; i) { if (bufs[i].map) { munmap(bufs[i].map, bufs[i].pitch * bufs[i].height); } if (bufs[i].id) { drmModeRmFB(fd, bufs[i].id); } if (bufs[i].handle) { struct drm_mode_destroy_dumb destroy { .handle bufs[i].handle }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, destroy); } } }通过这个完整的示例程序开发者可以直观理解libdrm如何协调DRM子系统完成最基本的显示功能。在实际项目中这些基础操作将被封装为更高级的图形抽象但底层原理依然相同。掌握这些核心API调用流程是深入Linux图形开发的必经之路。
别再只盯着内核了!手把手带你用libdrm写一个最简单的DRM显示程序
发布时间:2026/5/30 5:32:34
从零构建DRM显示程序libdrm核心API实战指南在Linux图形开发领域DRMDirect Rendering Manager作为内核级的显示管理框架承担着协调GPU、显示设备和应用程序的关键角色。而libdrm则是连接用户空间与内核DRM子系统的桥梁通过封装底层ioctl调用为开发者提供了更友好的编程接口。本文将彻底抛开抽象理论直接带领读者用C语言和libdrm库编写一个能在屏幕上显示纯色画面的完整程序。1. 环境准备与基础概念1.1 开发环境配置首先确保系统已安装必要的开发工具和库文件sudo apt install build-essential libdrm-dev验证libdrm版本本文基于libdrm 2.4.113pkg-config --modversion libdrm1.2 DRM核心对象模型在开始编码前需要理解DRM框架中的几个核心对象CRTC显示控制器负责扫描输出时序和显示模式Connector物理连接器如HDMI、DP接口Encoder将数字信号转换为物理接口信号Framebuffer存储像素数据的缓冲区这些对象通过libdrm提供的API进行管理和操作形成完整的显示流水线。2. 初始化DRM设备2.1 打开DRM设备文件Linux系统中DRM设备通常以/dev/dri/cardX形式存在。我们首先需要获取设备文件描述符#include xf86drm.h #include xf86drmMode.h int open_drm_device(const char *path) { int fd open(path, O_RDWR | O_CLOEXEC); if (fd 0) { perror(Failed to open DRM device); return -1; } uint64_t has_dumb; if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, has_dumb) 0 || !has_dumb) { fprintf(stderr, Device does not support dumb buffers\n); close(fd); return -1; } return fd; }2.2 获取显示资源成功打开设备后我们需要获取当前可用的显示资源drmModeRes *get_drm_resources(int fd) { drmModeRes *res drmModeGetResources(fd); if (!res) { perror(Failed to get DRM resources); return NULL; } if (res-count_crtcs 0 || res-count_connectors 0) { fprintf(stderr, No CRTCs or connectors found\n); drmModeFreeResources(res); return NULL; } return res; }3. 配置显示管线3.1 选择有效的连接器我们需要遍历所有连接器找到第一个已连接且可用的显示设备drmModeConnector *find_usable_connector(int fd, drmModeRes *res) { for (int i 0; i res-count_connectors; i) { drmModeConnector *conn drmModeGetConnector(fd, res-connectors[i]); if (!conn) continue; if (conn-connection DRM_MODE_CONNECTED conn-count_modes 0) { return conn; } drmModeFreeConnector(conn); } return NULL; }3.2 创建帧缓冲区接下来创建用于显示的帧缓冲区struct framebuffer { uint32_t width; uint32_t height; uint32_t pitch; uint32_t handle; uint32_t id; uint8_t *map; }; int create_dumb_buffer(int fd, struct framebuffer *buf) { struct drm_mode_create_dumb create { .width buf-width, .height buf-height, .bpp 32 }; if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, create) 0) { perror(Failed to create dumb buffer); return -1; } buf-pitch create.pitch; buf-handle create.handle; // 将缓冲区映射到用户空间 struct drm_mode_map_dumb map { .handle buf-handle }; if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, map) 0) { perror(Failed to map dumb buffer); return -1; } buf-map mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map.offset); if (buf-map MAP_FAILED) { perror(Failed to mmap dumb buffer); return -1; } // 创建帧缓冲区对象 if (drmModeAddFB(fd, buf-width, buf-height, 24, 32, buf-pitch, buf-handle, buf-id) 0) { perror(Failed to add FB); return -1; } return 0; }4. 显示配置与渲染循环4.1 设置CRTC显示模式现在我们可以将帧缓冲区与CRTC关联开始显示int set_crtc_mode(int fd, drmModeRes *res, drmModeConnector *conn, struct framebuffer *buf) { // 获取第一个可用的CRTC ID uint32_t crtc_id res-crtcs[0]; // 获取连接器的第一个显示模式 drmModeModeInfo *mode conn-modes[0]; // 设置CRTC if (drmModeSetCrtc(fd, crtc_id, buf-id, 0, 0, conn-connector_id, 1, mode) 0) { perror(Failed to set CRTC); return -1; } return 0; }4.2 填充颜色与主循环最后我们填充帧缓冲区并进入显示循环void fill_buffer(struct framebuffer *buf, uint32_t color) { uint32_t *pixels (uint32_t *)buf-map; for (uint32_t y 0; y buf-height; y) { for (uint32_t x 0; x buf-width; x) { pixels[y * (buf-pitch / 4) x] color; } } } int main() { int fd open_drm_device(/dev/dri/card0); if (fd 0) return 1; drmModeRes *res get_drm_resources(fd); if (!res) { close(fd); return 1; } drmModeConnector *conn find_usable_connector(fd, res); if (!conn) { drmModeFreeResources(res); close(fd); return 1; } struct framebuffer buf { .width conn-modes[0].hdisplay, .height conn-modes[0].vdisplay }; if (create_dumb_buffer(fd, buf) 0) { drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 1; } if (set_crtc_mode(fd, res, conn, buf) 0) { munmap(buf.map, buf.pitch * buf.height); drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 1; } // 填充红色 fill_buffer(buf, 0xFFFF0000); printf(Displaying red screen. Press Enter to exit...); getchar(); // 清理资源 munmap(buf.map, buf.pitch * buf.height); drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 0; }5. 进阶优化与错误处理5.1 双缓冲与页面翻转为避免画面撕裂实际应用中应使用双缓冲和原子模式设置int setup_page_flip(int fd, uint32_t crtc_id, uint32_t fb_id) { struct drm_mode_crtc_page_flip flip { .fb_id fb_id, .crtc_id crtc_id, .flags DRM_MODE_PAGE_FLIP_EVENT }; return drmIoctl(fd, DRM_IOCTL_MODE_PAGE_FLIP, flip); }5.2 事件处理通过文件描述符监听DRM事件void handle_drm_events(int fd) { fd_set fds; FD_ZERO(fds); FD_SET(fd, fds); while (1) { int ret select(fd 1, fds, NULL, NULL, NULL); if (ret 0) break; drmEventContext evctx { .version DRM_EVENT_CONTEXT_VERSION, .page_flip_handler page_flip_handler }; drmHandleEvent(fd, evctx); } }5.3 资源释放确保程序退出时正确释放所有资源void cleanup(int fd, struct framebuffer *bufs, int count) { for (int i 0; i count; i) { if (bufs[i].map) { munmap(bufs[i].map, bufs[i].pitch * bufs[i].height); } if (bufs[i].id) { drmModeRmFB(fd, bufs[i].id); } if (bufs[i].handle) { struct drm_mode_destroy_dumb destroy { .handle bufs[i].handle }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, destroy); } } }通过这个完整的示例程序开发者可以直观理解libdrm如何协调DRM子系统完成最基本的显示功能。在实际项目中这些基础操作将被封装为更高级的图形抽象但底层原理依然相同。掌握这些核心API调用流程是深入Linux图形开发的必经之路。