3. DRM架构总览

DRM(Direct Rendering Manager)是Linux内核中负责显卡与显示控制的子系统。它并不是一个单一模块,而是由多个职责清晰、彼此协作的子模块组成。理解DRM架构,关键在于看清“用户空间如何提交请求、内核如何管理显存、显示管线如何把帧送到屏幕”这三条主线。本节从整体组成出发,串联KMS、GEM、DMA-BUF与用户空间接口,建立一张可落地的架构地图。

一、DRM子系统的整体组成

从内核视角看,DRM子系统可以划分为四层:用户空间库与接口层、DRM核心层、驱动层以及硬件层。用户空间通过libdrm或直接ioctl访问/dev/dri/cardX与renderDXXX设备节点;DRM核心提供统一的ioctl分发、对象管理和内存管理框架;具体驱动(如i915、amdgpu、msm、rockchip)实现硬件相关操作;硬件层则包含GPU、显示控制器、CRTC、编码器和连接器。

DRM设备节点分为两类:cardX是主设备节点,支持KMS显示控制;renderDXXX是渲染节点,只用于GPU计算与渲染,不涉及显示权限。这种分离让无显示权限的进程也能安全使用GPU渲染能力。

核心记忆点:DRM = 核心框架 + 驱动实现 + 用户空间接口;KMS管显示,GEM管显存,DMA-BUF管跨设备共享。

二、KMS:显示管线的抽象模型

KMS(Kernel Mode Setting)负责显示模式设置与画面合成。它把显示硬件抽象为一条流水线:Framebuffer → Plane → CRTC → Encoder → Connector。Framebuffer描述一块可扫描输出的像素缓冲;Plane是图层,可叠加多个平面实现多平面合成;CRTC代表显示控制器,负责时序生成与扫描输出;Encoder把像素流转换为HDMI、DP、MIPI DSI等信号;Connector代表物理接口与显示器连接状态。

多平面合成是KMS的重要能力。主平面(Primary Plane)承载桌面或视频底图,叠加平面(Overlay Plane)用于视频、OSD或光标,光标平面(Cursor Plane)独立处理鼠标。硬件合成器可在扫描输出阶段直接叠加多个Plane,避免GPU额外合成开销,从而降低功耗与带宽。

DRM 子系统组成与 KMS 显示管线 用户空间 libdrm / Mesa ioctl / DRM API cardX / renderDXXX DRM 核心层 ioctl 分发 / 对象管理 KMS + GEM 框架 DMA-BUF 导入导出 驱动层 i915 / amdgpu msm / rockchip 硬件寄存器操作 硬件层 GPU / 显示控制器 CRTC / Encoder Connector / 面板 KMS 显示管线:Framebuffer → Plane → CRTC → Encoder → Connector Framebuffer 像素缓冲 Plane 多图层叠加 CRTC 时序与扫描 Encoder 信号编码 Connector HDMI / DP / DSI GEM:显存对象分配与句柄管理 DMA-BUF:跨设备/跨进程缓冲共享

三、GEM与DMA-BUF:显存管理与共享

GEM(Graphics Execution Manager)是DRM的显存管理框架,负责分配、映射和释放图形缓冲对象。用户空间通过DRM_IOCTL_GEM_CREATE创建GEM对象,得到句柄;通过mmap把缓冲映射到用户空间写入像素;通过PRIME机制把GEM对象导出为DMA-BUF文件描述符,供其他设备或进程导入。

DMA-BUF是Linux内核的跨设备缓冲共享框架。它不关心缓冲由谁分配,只提供统一的文件描述符接口和同步机制。典型场景是:摄像头采集的NV12缓冲通过DMA-BUF导出,GPU导入后做色彩转换,KMS再导入作为Plane的Framebuffer直接扫描输出。整条链路零拷贝,避免了内存复制带来的带宽与延迟。

/* 典型 GEM + DMA-BUF 导出流程(伪代码) */
int fd = open("/dev/dri/renderD128", O_RDWR);

struct drm_gem_create create = {
    .size = width * height * 4,
    .flags = 0,
};
ioctl(fd, DRM_IOCTL_GEM_CREATE, &create);   /* 得到 handle */

struct drm_prime_handle prime = {
    .handle = create.handle,
    .flags  = DRM_CLOEXEC | DRM_RDWR,
};
ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime);
/* prime.fd 即可传给其他设备导入 */

四、用户空间接口与典型调用流程

用户空间主要通过libdrm封装的函数访问DRM。显示配置的典型流程是:打开cardX → 获取资源(drmModeGetResources)→ 创建Framebuffer(drmModeAddFB2)→ 设置CRTC(drmModeSetCrtc)→ 页面翻转(drmModePageFlip)。渲染侧则通过renderDXXX提交GPU命令,使用GEM对象作为渲染目标,再通过DMA-BUF交给KMS显示。

模块 核心职责 典型对象/接口 用户空间入口
KMS 显示模式设置与多平面合成 CRTC、Plane、Encoder、Connector drmModeSetCrtc / drmModePageFlip
GEM 显存对象分配与句柄管理 drm_gem_object、handle DRM_IOCTL_GEM_CREATE / mmap
DMA-BUF 跨设备、跨进程缓冲共享 dma_buf、fd、fence PRIME_HANDLE_TO_FD / FD_TO_HANDLE
用户空间接口 统一ioctl与库封装 libdrm、Mesa、DRM API /dev/dri/cardX、renderDXXX

五、常见误区与排障复盘

误区一:把GEM当成“显存分配器”就结束了。实际上GEM还负责生命周期、映射和PRIME导出,理解句柄与fd的转换关系才能排查共享失败。
误区二:认为KMS只能做单图层。现代DRM驱动普遍支持多Plane硬件合成,合理使用Overlay Plane可显著降低GPU负载。
误区三:忽略DMA-BUF的同步。跨设备共享缓冲必须配合fence或隐式同步,否则会出现撕裂或花屏。

排障时可按以下顺序检查:确认设备节点权限与驱动加载;用modetest验证KMS基本输出;用drm_info查看Plane与CRTC能力;检查DMA-BUF导入是否返回有效fd;最后确认页面翻转事件是否正常回收。掌握这条链路,DRM架构就不再是零散概念,而是一张可操作的工程地图。