DRM(Direct Rendering Manager,直接渲染管理器)是 Linux 内核中的图形子系统,用来让用户态程序安全地使用 GPU、管理显存和命令队列,并通过 KMS 控制显示器模式、扫描输出和页面翻转。
这里的 DRM 不是数字版权管理,而是 Linux 图形栈里的内核基础设施。它把“谁可以提交 GPU 命令”“哪块显存属于谁”“显示器当前输出哪张图像”“多个进程如何共享图形资源”这些问题放进内核统一协调。
核心问题
早期图形系统可以把显卡控制权交给单个显示服务器,由它直接访问硬件。但现代桌面、游戏、浏览器、视频播放器和计算程序都可能同时使用 GPU。如果每个进程都能直接写显卡寄存器或显存,会出现几个问题:
- 安全边界不清:一个进程可能读到另一个进程的图像、纹理或命令缓冲。
- 资源冲突:多个程序同时分配显存、切换显示模式或提交命令时可能互相破坏状态。
- 调度不可控:GPU 是异步执行设备,命令提交后并不马上完成,内核需要知道何时可以复用资源。
- 显示输出需要全局仲裁:屏幕分辨率、刷新率、显示平面和光标不是单个应用自己的私有状态。
- 用户态驱动需要硬件能力:Mesa、浏览器和游戏引擎需要提交渲染命令,但不能绕过内核权限控制。
DRM 的核心价值是:让图形程序可以“直接渲染”到 GPU 资源,同时仍由内核负责权限、资源、同步和显示输出仲裁。
核心对象
| 对象 | 所在层 | 作用 | 典型工程关注点 |
|---|---|---|---|
| DRM driver | 内核态 | 某类 GPU 或 显示控制器 的 设备驱动 | probe、显存管理、中断、命令提交、KMS 对象 |
| DRM device node | /dev/dri/* | 用户态访问 DRM 的文件入口 | 权限、render node、primary node、容器映射 |
| userspace driver | 用户态 | 把图形 API 调用翻译成硬件命令 | 通常由 Mesa 或厂商驱动提供 |
| buffer object | 内核管理的图形缓冲对象 | 存放帧、纹理、顶点或命令数据 | 显存/系统内存位置、共享、同步 |
| [[GEM(Graphics Execution Manager) | GEM]] object | DRM 常见缓冲管理抽象 | 用句柄和 fd 表示可共享图形资源 |
| framebuffer | KMS 输出对象 | 指向可被 [[Scanout | 扫描输出]] 的 帧缓冲 |
| CRTC / encoder / connector | KMS 显示链路对象 | 描述扫描输出、编码器和物理接口 | 分辨率、刷新率、HDMI/DP/eDP 连接 |
| fence / sync object | 同步对象 | 表示 GPU 工作何时完成 | 避免 CPU/GPU 或多队列之间读写冲突 |
核心机制
DRM 可以粗略分成两条路径:渲染路径负责 GPU 命令和图形资源,显示路径负责把最终图像送到屏幕。
1. 打开设备节点
用户态程序通常通过 /dev/dri/renderD* 或 /dev/dri/card* 访问 DRM:
fd = open("/dev/dri/renderD128")fd是文件描述符,后续ioctl都通过它进入内核。renderD*通常只允许渲染和计算,不允许改显示模式,适合普通应用。card*是 primary node,通常有显示控制权限,桌面合成器或显示服务器会使用它。
这一步把 GPU 访问变成普通的 IPC / 系统调用问题:用户态不能直接写硬件,而是把请求交给内核检查。
2. 分配和共享缓冲
程序需要图像、纹理、顶点数据或命令缓冲时,会让 DRM 驱动创建 buffer object。对用户态来说,它通常拿到的是一个句柄或可传递的文件描述符。
buffer = create(width, height, format, usage)
handle = drm_handle(buffer)
fd = export_as_dma_buf(buffer)width、height和format描述图像形状与像素格式。usage描述用途,例如渲染目标、纹理采样、显示扫描输出。handle只在当前 DRM 连接内有效。dma_buf fd可以跨进程共享,让合成器、视频解码器和渲染程序共享同一块图形内存。
这不是简单的“申请一段内存”。GPU 缓冲可能在显存、可映射系统内存或特殊 tiling 布局里,驱动还要跟踪缓存一致性、对齐、页表和访问权限。
3. 提交 GPU 命令
用户态驱动把 OpenGL、Vulkan 或视频处理请求翻译成 GPU 命令缓冲,然后通过 DRM ioctl 提交给内核驱动:
submit(command_buffer, read_buffers, write_buffers, wait_fences)command_buffer是硬件可理解的命令序列,不是通用 CPU 指令。read_buffers是本次 GPU 工作会读取的图形资源。write_buffers是本次 GPU 工作会写入的图形资源。wait_fences是前置同步条件,表示必须等哪些工作完成后才能开始。
内核驱动会校验资源句柄、地址范围、权限和同步关系,然后把命令放进 GPU 队列。GPU 完成后产生 fence,后续工作或显示输出可以等待这个 fence,避免读到半渲染的图像。
4. 用 KMS 扫描输出
当一张图像要显示到屏幕上时,合成器或显示服务器会把缓冲注册成 framebuffer,再通过 KMS 设置显示链路:
connector -> encoder -> CRTC -> plane -> framebufferconnector表示 HDMI、DisplayPort、eDP 等物理接口。encoder把像素流编码成接口需要的信号。CRTC产生扫描时序,例如分辨率、刷新率和 vblank。plane是可叠加的 显示平面,例如主画面、光标或视频 overlay;源尺寸和目标尺寸不一致时可能使用 Scaler(硬件缩放器)。framebuffer指向实际要被扫描输出的图像缓冲。
页面翻转时,KMS 在合适的垂直消隐点把旧 framebuffer 换成新 framebuffer。这样可以减少画面撕裂,并让合成器知道什么时候可以复用旧缓冲。
工程用途
- Linux 桌面显示:桌面合成器、显示服务器和登录管理器通过 DRM/KMS 接管显示输出。
- 图形应用加速:游戏、浏览器、视频播放器和 UI 工具包通过用户态驱动提交 GPU 工作。
- 头less 渲染和计算:程序可以通过 render node 使用 GPU,而不需要控制屏幕。
- 容器和远程开发:需要把
/dev/dri/renderD*映射进容器,并处理用户组、cgroup 和驱动版本匹配。 - 嵌入式 bring-up:屏幕不亮时经常要同时检查 DRM 驱动、KMS connector、EDID、背光、电源和 设备树。
常见观察点包括:
ls /dev/dri/:确认是否有card*和renderD*。dmesg | grep -i drm:查看 DRM 驱动 probe、模式设置和错误。modetest:检查 connector、CRTC、plane 和可用模式。glxinfo、vulkaninfo或eglinfo:确认用户态图形栈是否选对驱动。
边界与常见坑
- DRM 不是版权 DRM:这里不是 Digital Rights Management,而是 Direct Rendering Manager。
- DRM 不是 Mesa:DRM 在内核态做资源与权限仲裁;Mesa 在用户态实现 OpenGL、Vulkan 等 API 到硬件命令的翻译。
- DRM 不是单纯的显示驱动:它既有显示输出路径,也有渲染、缓冲、同步和 GPU 命令提交路径。
- 有
/dev/dri/card0不等于能 3D 加速:显示控制器可能能出图,但 GPU render node、Mesa 驱动或固件仍可能缺失。 - 容器里挂了设备节点也可能不可用:还要匹配用户组权限、cgroup 设备策略、用户态驱动库和宿主内核驱动。
- KMS 缩写容易混淆:图形栈里的 KMS 是 Kernel Mode Setting,不是 Key Management Service。
- 撕裂、黑屏和花屏常常是同步或格式问题:像素格式、stride、多平面布局、fence 等不匹配时,症状可能不像“权限错误”那么直接。