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的设计思想可以概括为三点:

  1. 一切皆对象:CRTC、Plane、Encoder、Connector、Framebuffer、Property都是内核对象,拥有唯一ID与属性集。
  2. 以文件描述符为入口:用户空间打开/dev/dri/card0获得fd,所有ioctl都围绕该fd进行,天然支持多进程隔离。
  3. 原子提交:把一次显示更新涉及的所有对象属性打包成一个原子请求,要么全部生效,要么全部失败,避免中间态闪烁。

下面这张图展示了DRM/KMS的核心对象关系与用户空间接口路径:

DRM/KMS 对象关系与用户空间接口 用户空间 X Server / Wayland Mesa / libdrm OpenGL / Vulkan 应用进程 设备节点 /dev/dri/card0 /dev/dri/renderD128 /dev/dri/controlD64 内核 DRM/KMS CRTC 扫描输出 Plane 图层合成 Encoder 信号编码 Connector 物理接口 Framebuffer 像素缓冲 Property 属性系统 + Atomic 原子提交 每个对象拥有可读写属性;一次 ioctl(DRM_IOCTL_MODE_ATOMIC) 提交整组属性变更 成功则统一生效,失败则回滚,避免中间态闪烁与撕裂 open() + ioctl() drm_ioctl 分发

四、用户空间接口: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热插拔等流程就会顺畅很多。