4. DRM框架入门
在Linux图形栈中,DRM(Direct Rendering Manager,直接渲染管理器)是内核态显示与GPU管理的核心子系统。它向上为X Server、Wayland合成器、OpenGL/Vulkan驱动提供统一接口,向下管理显示控制器、GPU、显存与连接器。理解DRM,是理解KMS(Kernel Mode Setting)与整个显示子系统的起点。
一、DRM是什么:从“直接渲染”到统一显示管理
DRM最初解决的是这样一个问题:早期X Server直接操作显卡寄存器,多个进程同时访问GPU时极易冲突,甚至导致系统崩溃。DRM作为内核中的“仲裁者”,把GPU访问、显存分配、命令提交、显示模式设置等能力收拢到内核,用户空间通过标准设备节点发起请求,由DRM统一调度。
从职责上看,DRM主要覆盖四类能力:
- 显存管理:通过GEM/TTM等机制分配、映射、迁移缓冲区对象。
- 命令提交与渲染调度:管理GPU命令队列、同步对象与执行上下文。
- 显示模式设置(KMS):管理CRTC、Encoder、Connector、Plane与Framebuffer。
- 设备与权限抽象:以
/dev/dri/cardX、renderDX等节点暴露能力。
因此,DRM不是“一个驱动”,而是一套内核框架加一组用户空间API。具体显卡驱动(如i915、amdgpu、msm、rockchip)作为DRM的提供者,注册自己的设备并实现回调。
二、历史演进:从DRI到DRM,再到KMS
DRM的演进大致可分为三个阶段:
| 阶段 | 代表技术 | 核心变化 |
|---|---|---|
| 早期直接渲染 | DRI / DRI2 | 用户态直接访问GPU,内核只做基本仲裁,模式设置仍在X Server |
| DRM内核化 | DRM、GEM、TTM | 显存与命令提交进入内核,统一缓冲区管理 |
| KMS统一显示 | KMS、Atomic Modesetting | 显示模式设置进入内核,支持原子提交与无闪烁切换 |
KMS的引入是关键转折:在此之前,显示分辨率、刷新率、输出切换由X Server控制,内核无法独立完成启动画面到登录界面的平滑过渡。KMS把CRTC、Connector、Encoder等显示资源抽象为内核对象,使内核在启动早期即可设置模式,并支持多显示、热插拔与原子更新。
核心记忆点:DRM负责“渲染与显存”,KMS负责“显示与模式设置”。KMS是DRM框架中的一个子模块,但常被合称为DRM/KMS。
三、核心设计思想:对象化、文件描述符与原子提交
DRM的设计思想可以概括为三点:
- 一切皆对象:CRTC、Plane、Encoder、Connector、Framebuffer、Property都是内核对象,拥有唯一ID与属性集。
- 以文件描述符为入口:用户空间打开
/dev/dri/card0获得fd,所有ioctl都围绕该fd进行,天然支持多进程隔离。 - 原子提交:把一次显示更新涉及的所有对象属性打包成一个原子请求,要么全部生效,要么全部失败,避免中间态闪烁。
下面这张图展示了DRM/KMS的核心对象关系与用户空间接口路径:
四、用户空间接口:libdrm与ioctl
用户空间不直接调用内核函数,而是通过ioctl与DRM设备通信。直接写ioctl比较繁琐,因此有了libdrm库,它封装了常用操作,提供更友好的C API。
# 查看DRM设备节点
ls -l /dev/dri/
# 输出示例:
# crw-rw----+ 1 root video 226, 0 card0
# crw-rw----+ 1 root video 226, 128 renderD128
# 查看当前显示模式(需要modetest工具,来自libdrm-tests)
modetest -M rockchip -c
# 输出示例:
# Connectors:
# id encoder status name size (mm) modes encoders
# 42 41 connected HDMI-A-1 520x290 1 41
# modes:
# index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot
# 0 1920x1080 60.00 1920 2008 2052 2200 1080 1084 1089 1125
# 查看DRM设备能力
cat /sys/class/drm/card0/device/vendor
cat /sys/class/drm/card0/device/device
常见的DRM ioctl命令包括:
DRM_IOCTL_MODE_GETRESOURCES:枚举CRTC、Encoder、Connector、Framebuffer。DRM_IOCTL_MODE_GETCONNECTOR:获取连接器状态与支持模式。DRM_IOCTL_MODE_CREATE_DUMB:创建用于显示的缓冲区。DRM_IOCTL_MODE_ATOMIC:提交原子模式设置请求。DRM_IOCTL_GEM_CLOSE:释放GEM缓冲区对象。
五、设备节点与权限管理
DRM设备节点位于/dev/dri/目录下,常见节点及用途如下:
| 节点 | 用途 | 典型访问者 |
|---|---|---|
/dev/dri/card0 | 主设备节点,支持KMS模式设置与渲染 | X Server、Wayland合成器、显示管理器 |
/dev/dri/renderD128 | 仅渲染节点,不涉及显示模式设置 | OpenGL/Vulkan应用、计算任务 |
/dev/dri/controlD64 | 控制节点,用于管理DRM设备 | 系统管理工具 |
权限管理通常通过udev规则与用户组实现。默认情况下,card0属于video组,renderD128属于render组。普通用户需要加入对应组才能访问。
# 查看当前用户所属组
groups
# 输出示例:user adm cdrom sudo audio video render
# 将用户加入video和render组
sudo usermod -aG video,render $USER
# 查看udev规则
cat /lib/udev/rules.d/50-udev-default.rules | grep -i dri
# 输出示例:
# SUBSYSTEM=="drm", KERNEL=="card[0-9]*", GROUP="video"
# SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="render"
提示:如果应用报错“Permission denied”或“Cannot open /dev/dri/card0”,优先检查用户是否在video组中,以及当前会话是否已重新登录使组权限生效。
六、常见误区与复盘
误区一:认为DRM只负责显示。实际上DRM同时管理渲染、显存与显示,KMS只是其中一部分。
误区二:把card0和renderD128混用。需要设置显示模式时必须用cardX;纯渲染任务应使用renderDX,避免不必要的权限暴露。
误区三:忽略原子提交的“全有或全无”特性。提交前应检查所有属性是否合法,否则整组请求会被拒绝。
复盘来看,掌握DRM的关键在于建立三层认知:用户空间通过libdrm/ioctl访问设备节点,内核DRM把显示资源抽象为对象,KMS通过原子提交完成显示更新。这三层打通后,后续理解KMS组件初始化、Plane与CRTC绑定、Connector热插拔等流程就会顺畅很多。