☰
Linux DRM显示驱动探秘:从KMS到跨DRM录制实践
2026/10/8 11:55:27 网站建设 项目流程

做了几年 Linux 图形栈的活儿,最常被同事问的问题就是:天天听你说 DRM DRM,它到底是啥?为什么新的显示驱动改了一版又一版,最后都得落在那几个/dev/dri节点上?这个问题确实值得掰开揉碎聊一聊。DRM 是 Linux 内核里的 Direct Rendering Manager,从 2.4 内核时代一路孵化到现在,已经成为几乎所有 GPU 显示驱动的主通道。不管你做的是 i915、amdgpu、nouveau,还是嵌入式里那堆 msm、vc4、imx-drm,只要想让自己的设备跑起一个能用 X/Wayland 的驱动,就绕不开 DRM 这几个字母。这篇文章我按实际调驱动项目的方式把它拆开讲清楚,顺手聊聊大家讨论比较多的跨 DRM 录制这类玩法,适合第一次接触内核显示驱动、或者正在为无头机器录不了屏、画面黑屏发愁的朋友。

1. DRM 的出身:从“显存直刷”到“显示资源管理”

1.1 老 fbdev 只能当画布,管不了显卡的复杂度

很多人第一次接触驱动,看的是fbdev:它简单,一个/dev/fb0节点,把内存里的像素数据往里一写,屏幕就出画面。如果你做的是单片机、低端嵌入式,这个模型到今天都够用。但放到 PC 和现代 SoC 上,fbdev 就捉襟见肘了。

原因很简单:现代 GPU 输出不是“一块显存对应一块屏”这么直白。一块显卡往往带多个物理输出口,每个口可能接不同的显示器,分辨率、刷新率、色彩空间全都不一样;显示链路里还夹着编码器、桥接芯片、DP MST 分流器,甚至一个 CRTC 可以驱动多个 Connector。这些资源的管理、热插拔检测、EDID 解析、切换模式时的重建,都远远超出“画布”的能力边界。用 fbdev 去做,结果就是每家驱动各写各的私有接口,用户态根本没法统一配合。

1.2 从 DRI 到内核主线的那次整合

DRM 真正被推到内核主线,是 2.6 前后的事。更早的时候,XFree86/X.Org 想走 Direct Rendering 让 OpenGL 直接访问 GPU 2D/3D 硬件,搞了 DRI(Direct Rendering Infrastructure)项目。后来大家发现,光靠用户态和驱动私聊不行,内核里必须有一个统一的显存对象模型和权限控制层,否则多进程同时访问 GPU,显存怎么分配、怎么同步、怎么回收,全是灾难。

于是 DRM 被设计成这样一个中间层:它住进内核,接管 GPU 的设备文件、显存对象、中断、fence 同步、显示模式管理。再往后,radeon 和 i915 先后把原来的私有 fb 驱动迁到 DRM 框架里,fbdev 逐渐退化成控制台兜底方案。到 2010 年代后期,DRM 事实上已经成为 Linux 图形栈的中枢。你打开/dev/dri目录看到card0、renderD128,就是这套体系的对外窗口。

1.3 DRM 到底提供哪三类能力

把 DRM 拆到不能再简单的程度,它提供三类基础能力:

  • 显示输出管理(KMS):控制扫描输出、显示模式、颜色管理,对应用户态能感知的“屏幕上显示什么”。
  • 显存对象管理(GEM/GBM):分配、映射、导出 GPU buffer,让渲染结果能被合成、扫描、共享给其他设备。
  • 同步机制(fence/syncobj):给 CPU、GPU、显示扫描之间加同步点,避免画到一半就被扫出去的情况。

这三类能力每一块都能单独写几篇文章。但有个关键点需要先建立认知:DRM 不是一个“画图的库”,它是一个硬件资源管理框架。它不负责生成像素,只负责把生成像素所需的 GPU 能力,以标准接口开放给用户态。

2. DRM 架构:两条腿走路,哪条都不能瘸

2.1 KMS:像舞台调度一样去管显示器

KMS(Kernel Mode Setting)是 DRM 最常被感知到的一半。它管的是显示路径上的四个核心对象:

  • CRTC:扫描控制器。它按固定时钟从内存读帧,合成多个平面后送出信号。
  • Plane:显示平面。主平面、光标平面、视频覆盖平面都在这一类,每个平面挂一个 framebuffer。
  • Encoder:编码器。把 CRTC 出来的信号转换成具体接口形式,比如 TMDS、DisplayPort、MIPI DSI。
  • Connector:物理输出口。代表一个 HDMI 口、DP 口或者面板接口,通过探测拿到 EDID,确定显示器支持哪些模式。

这四个对象的关系我一般喜欢用舞台来类比:Connector 是观众席,Encoder 是扩音器,CRTC 是舞台控制台,Plane 是台上正在播放的屏幕。你要让观众看到画面,得先确认观众席还存在(Connector detect),调好扩音器格式(Encoder),从舞台控制台指定从哪块屏幕取内容(CRTC + Plane),最后按下演出开始键(SetCrtc/Atomic Commit)。

用户态通过/dev/dri/card0发 ioctl 操作这些对象。drmModeGetResources枚举对象,drmModeGetConnector拿显示器和模式列表,drmModeSetCrtc做最基础的显示切换。复杂一点的场景要用drmModeAtomicCommit,一次把多个属性统一提交,避免中间状态导致花屏。

2.2 GEM:显存对象与跨设备共享的底座

另一半是 GEM。早期 GEM 这个名字确实有点“图形执行管理器”的意思,但今天看它更像一个显存对象模型:内核里一切 buffer 都用一个 GEM object 表示。用户态拿到 handle,通过mmap可以读写,通过PRIME机制可以导出成dma-buf传给其他设备,比如给编码器做零拷贝。

实操里最常用的是 dumb buffer:不需要 GPU 华丽渲染、只想把 CPU 数据刷到屏幕时,用drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB)创建一块普通显存,DRM_IOCTL_MODE_MAP_DUMB拿到 CPU 映射地址,往里填像素,再drmModeSetCrtc让它显示出来。这也是很多 framebuffer 移植到 DRM 下的典型思路。

但一旦涉及 GPU 渲染,就不是 dumb buffer 能扛的了。渲染场景需要 GBM 分配缓冲区,Mesa 或 Vulkan 层把渲染图像写进 buffer,再通过drmPrimeHandleToFD导出给显示、编码、另一个 GPU。这就是跨 DRM 录制里最关键的一环:显存对象一旦能被工厂化地导出导入,录屏工具就根本不需要关心底层是 AMD 还是 Intel。

2.3 用户态怎么和 DRM 打交道:libdrm 与 master 权限

用户态一般不是直接放 ioctl,而是走 libdrm 这套封装。modetest这种命令行工具也是基于 libdrm 写的。打开一个 KMS 节点后,要先drmSetMaster拿到主控权,才能改显示模式;权限控制这一层很关键,否则随便一个进程都能把分辨率改掉,桌面早就乱套了。

这里有个初学者特别容易懵的概念:card0和renderD128有什么区别。简单说,card0是主节点,既能渲染也能做 KMS;renderD128是渲染节点,只负责分配缓冲和提交渲染任务,不能碰模式设置。做录屏和显示合成通常需要主节点,做纯离屏渲染可以只用渲染节点,两个别混。

3. 为什么显示驱动绕不开 DRM:三个层面的现实

3.1 用户态生态全部长在 DRM 之上

你先想一个问题:如果我现在新写一个显示驱动,不走 DRM,而是自己发明一套 ioctl,用户态能跑起来吗?答案是否定的。X.Org 的 modesetting 驱动、Wayland 合成器里的 drm-backend、Mesa 的 DRI3/GBM 后端,全是从 libdrm 的接口拿帧、做 Present。你不接 DRM,等于桌面系统根本没有公共语言和你沟通。

这个“被迫接入”是良性的。DRM 把显示驱动最复杂的部分——模式扫描、热插拔、缓冲翻页、同步——抽象成稳定接口,上游开发者围绕它做工具、做测试、做安全加固。作为下游驱动作者,你反而省掉了维护私有 ABI 的负担。这正是“绕不开”的第一层含义:生态和用户都在 DRM 另一边,你没有第二个入口。

3.2 内核层面的基础设施也是围绕 DRM 转的

很多人只看到显示驱动要接 DRM,其实内核里其他相关子系统也在往 DRM 靠。曾经的 fbdev 如今默认退居控制台;V4L2 的视频采集设备与 DRM 的dma-buf配合做零拷贝;DRM 的writeback连接器甚至允许驱动把 CRTC 输出“回写”成内存帧,这在录屏、远程桌面、硬件回放上特别有用。

另一个现实因素是新驱动必须跟主线走。Linux 内核对新驱动有个隐要求:尽量往通用框架上迁移,复用别人的atomic_check、atomic_commit逻辑。你去看include/drm/drm_atomic_helper.h那一堆 helper,就是在帮你减少造轮子。相比几十年前各写各的 radeon fb 驱动时代,现在写一个新平台显示驱动,工作量大头反而变成了:实现好mode_valid、atomic_check、atomic_flush这几个钩子,然后把硬件怪癖填进去。

3.3 安全和维护成本的倒逼

显示驱动不接 DRM,还有一个更深层的风险:你自己管 CRTC、管显存映射、管权限,很容易漏掉安全边界。内核社区反复强调 IOMMU、显存隔离、禁止用户态直接踩任意物理地址,DRM 框架把这些边界都固化了:buffer 必须先被 GEM object 引用,映射必须走 VMA 权限校验,多设备的 buffer 共享必须过dma-buf的 attach/export 流程。你想绕过它,等于把攻击面重新打开。

维护角度也一样。DRM 有大量现成的 debugfs、tracepoint、drm_client框架,你嵌入进去之后,出了黑屏、闪屏问题,可以直接拿trace-cmd抓 atomic state 事件,拿modetest摆现状。这些基础设施是我的日常排障利器。绕开 DRM,等于放弃这些现成工具。

4. 跨 DRM 录制:没有屏幕的地方,也能把画面抓下来

4.1 什么是跨 DRM 录制,解决什么问题

“跨 DRM 录制”这个名字听起来玄,拆开就是两件事:一是跨厂商,一套录制逻辑在 i915、amdgpu、vc4 上都能跑,因为大家走的是同一套 DRM/KMS 接口;二是跨环境,服务器无头、虚拟机里没有物理显示器,但只要模出一套 KMS 设备,画面照样能抓能录。

实际业务场景非常常见:远程桌面网关想把虚拟机的桌面录成流,云游戏平台想把 GPU 渲染出来的帧转给编码器,测试机房想抓启动阶段的生命周期画面,这时候没有 X11、没有 Wayland,甚至 HDMI 口都没插显示器。唯一的公共路径就是 DRM。录制工具通过 libdrm 枚举 Connector,拿到 preferred mode,建立 framebuffer,之后要么回读到 CPU,要么直接导出成dma-buf送给硬件编码器。这个过程做到了位,就叫跨 DRM 录制。

4.2 方法一:用 vkms 造一个虚拟显示器

最省事的入门方案是用内核的vkms驱动。modprobe vkms之后,系统里会多出一个虚拟 KMS 设备,没有物理显示器也能枚举出 Connector 和 Mode。

我一般先跑modetest -M vkms看看节点状态:

modprobe vkms modetest -M vkms -c

输出里能看到一个虚拟的Virtual-1连接器,以及它支持的 1920x1080、1280x720 等模式。之后用 libdrm 打开card0,拿虚拟 Connector 的 preferred mode,创建 dumb buffer 填帧,drmModeSetCrtc提交,vkms就会把 framebuffer 的 CRC 算出来。此时想录屏,就得在 CPU 侧把你填进去的像素一并存下来——因为vkms本身不做真正的像素扫描,它只是模拟机制,验证流程、测试音频无关的显示管线非常顺手。

这套玩法特别适合测试跨 DRM 录制的应用逻辑:你不需要一块真实显卡,就能验证枚举、模式选择、frame 提交、颜色格式换算这些核心代码。

4.3 方法二:EGL + GBM 渲染后做 CPU 回读

如果你手里有真实 GPU,但机器没有物理显示器,可以用渲染节点配合 GBM 做离屏渲染,再把结果回读到内存。流程是:创建 GBM 设备,gbm_surface_create申请一块渲染 buffer,配好 EGL 上下文和 surface,渲染完成后eglSwapBuffers,再gbm_bo_map拿地址,用glReadPixels或者直接memcpy把像素取到 CPU 侧。

这里有个容易踩的坑:gbm_bo_map不是所有驱动都支持零拷贝。有些嵌入式驱动 map 回来之后,像素格式是 tiled 的,不能直接当 NV12 或者 BGRA 用。稳妥做法是先查gbm_bo_get_format,必要时在 GPU 侧做一次 blit,把 tiled buffer 转成线性布局,再回读。

回读拿到的是连续内存帧,喂给 FFmpeg 转封装就行。比如用一个rawvideo输入,指定好宽高和像素格式,就能编出 H.264 文件。这种方案 CPU 开销大,但胜在通用,驱动兼容问题少。

4.4 方法三:dma-buf 零拷贝接力硬件编码器

生产环境对性能有要求,这个方法才算正路。思路是不把渲染结果回读到 CPU,而是保持帧数据在 GPU 显存里,通过dma-buf把同一个 buffer 的所有权分享给编码器。

链路大概是:GBM 分配 buffer -> GPU 渲染 ->drmPrimeHandleToFD导出 FD -> 编码器侧把 FD 通过 VAAPI/NVENC/V4L2 M2M 设备导入。整个过程始终是 GPU 到 GPU,没有 PCIe 回读,带宽和延迟都能压下来。FFmpeg 的hwupload、hwmapfilter 就是干这个的:一张显存帧,在这条管道里从 DRM 流到编码器。

我印象比较深的是在一台无头 AMD 服务器上做桌面录制。环境里没有物理输出,我用vkms提供 KMS 节点,渲染后端是 amdgpu,编码走 VAAPI,最终录出来的是流畅的 4K 流。整条链路看起来是“两个不同 DRM 设备在协作”,实际底层全靠dma-buf的 import/export 机制打通。跨 DRM 录制能做到这个程度,才算是真正吃透了框架。

5. 实操踩坑实录与排查清单

5.1 没有权限,打不开 card0

最常见的报错是drmOpen失败,或者Permission denied。如果你用的是普通桌面环境,X/Wayland 那边的 compositor 已经占着 master,你的工具直接drmSetMaster会被拒绝。

解决要分情况:临时调试就在 root 或video组下跑;要长期跑录制服务,用udev规则把/dev/dri/*的权限拨到指定用户组。还有一个细节:多 GPU 机器上card0不一定是你想操作的那张卡,用drmGetDeviceNameFromFd或者去/sys/class/drm/card*/device/vendor里比对 PCI ID 确定。

5.2 枚举不到 Connector,或 Connector 状态是 disconnected

无头服务器上最常见。物理 HDMI/DP 口没插显示器,KMS 枚举出来的 Connector 状态就是 disconnected,很多工具会直接放弃。这时候要么接一个“假负载”让链路保持连接,要么用vkms虚拟一个连接器。

注意drmModeGetConnector返回的 count 是 0 时,你要重新 poll,因为有些驱动热插拔检测是异步的。我在调试时习惯加一个 3 秒重试循环,如果一直是 0,再看 dmesg 里的drm_connector日志,确认 detect 回调是否真的被调用过。

5.3 atomic commit 返回 EBUSY 或 EPERM

EBUSY通常表示你试图改的 CRTC/Plane 正在被其他进程占用,或者上次翻页还没完成。检查点有三个:是否已经把 plane 的 framebuffer 关联到正确的 CRTC;是否漏了DRM_MODE_ATOMIC_ALLOW_MODESET标志;fence 是否在等待超时。

EPERM则多半是 master 权限问题。有人会在拿到DRM_CLIENT_CAP_ATOMIC之后忘记重新drmSetMaster,导致 atomic 不认。这个细节我犯过不止一次。

5.4 录出来的画面颜色不对、花屏、方向反

跨 DRM 录制里,最折磨人的不是拿不到帧,而是拿到帧后格式不对。硬件 framebuffer 常见格式有 XRGB8888、ARGB8888、NV12、P010,还有一堆 tiled 变体。你如果不管drmModeGetFB2返回的 modifier,直接当普通 BGRA 处理,录出来的画面十有八九是绿的、红的或者上下颠倒。

我的习惯是先modetest -M card0 -f看当前 framebuffer 格式,再决定回读路径里要不要过一次 conversion。需要 GPU 转格式时,用 Vulkan 或 Gallium 的 blit 子集做一次 copy,不要图省事在 CPU 端逐像素转换,否则 4K 60 帧能把 CPU 直接打满。

5.5 画面撕裂和帧率漂移

录制过程中如果出现撕裂,多半是没配合 fence。回读和渲染之间要么加eglCreateSyncKHR,要么用DRM_IOCTL_SYNCOBJ_WAIT等 GPU 完成信号。帧率漂移则是你回读耗时超过了帧间隔,导致编码器排队越来越多。这种情况下别迷信 PTS,以编码器输入队列的实测时间戳为准,必要时丢帧保流畅。

6. 一些只有踩过坑才懂的细节

写到最后,分享几个我在项目里反复验证过的体会。

第一,调试 DRM 显示路径,首先打开DRM_DEBUG_MODESET。内核参数里设drm.debug=0x04,重启后 dmesg 会吐出 atomic state、connector 状态变化、mode 校验的完整过程。想靠眼睛猜哪个 CRTC 没配好,纯属浪费时间。

第二,modetest不是用来看一眼的,它是你的“显示器探针”。每次上手新板子,我都会先跑一遍modetest -M <设备节点> -p -c -f,把该显卡的 pipe、encoder、connector、mode 列表完整存下来。之后写代码就按这份“地图”来,比看 datasheet 快得多。

第三,跨 DRM 录制别追求一步到位。先把管线拆成三段验证:显存分配对不对、渲染结果能不能回读、编码器能不能吃这格式。三段各自通了,再拼起来。我见过太多人第一次就写整条链路,结果要么是格式不兼容,要么是同步点错,最后查起来焦头烂额。

最后想说,DRM 确实不好啃,代码量大、概念重叠、文档藏在内核源码的Documentation/gpu/下。但正因为几乎所有显示驱动都在这个框架里,你花时间把它摸透,后面换平台、换 GPU 厂商、做虚拟化、做录制服务,都能吃很久的红利。耐心把这套对象模型和 ioctl 逻辑盘顺,眼前这些驱动和录屏问题,都会慢慢变得通透起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询