3. Linux图形栈全景

理解 DRM/KMS 之前,必须先建立一张“全景地图”。Linux 图形栈是一条从用户态应用到显示控制器寄存器的纵向分层链路,每一层只解决特定问题,并通过明确定义的接口把工作交给下一层。很多初学者把“显卡驱动”“显示驱动”“窗口系统”混为一谈,导致后续分析 KMS 初始化时找不到坐标。本节把整条链路拆开,说明各层职责、数据流向以及 DRM/KMS 与 Framebuffer 的真实关系。

一、核心原理:分层与职责边界

Linux 图形栈大致可分为用户态与内核态两大部分。用户态负责“画什么、怎么组织窗口”,内核态负责“把像素送到屏幕、管理显存与显示时序”。

  • 应用程序:使用 OpenGL、Vulkan、GTK/Qt 等工具包绘制内容,它只关心自己的窗口或表面,不关心屏幕有几个、如何扫描输出。
  • 窗口系统 / 显示服务器:如 X11(Xorg)、Wayland(Weston、KWin、Mutter)。它管理窗口层级、输入事件、合成,并决定最终哪块缓冲区被送到显示控制器。
  • 图形 API 与用户态驱动:Mesa 提供 OpenGL/Vulkan 实现,把应用命令翻译成 GPU 可执行的命令流,并调用内核 DRM 接口提交。
  • DRM/KMS 子系统:内核中的 Direct Rendering Manager 与 Kernel Mode Setting。DRM 管理 GPU 与显存对象(GEM/TTM),KMS 管理显示管线:CRTC、Encoder、Connector、Plane。
  • GPU 驱动与显示驱动:如 i915、amdgpu、msm、rockchip-drm。它们把 DRM/KMS 的抽象对象映射到具体硬件寄存器。
  • Framebuffer:一块内存缓冲区,保存屏幕像素。它既可以是 DRM 管理的 GEM 对象,也可以是传统 fbdev 的 /dev/fb0。
关键结论:DRM/KMS 不是“另一个 Framebuffer”,而是 Framebuffer 的管理者与显示管线的控制者。Framebuffer 是数据,KMS 是让数据出现在屏幕上的机制。

二、数据流与调用链

以 Wayland 合成器为例,一次典型显示刷新大致经历如下路径:应用渲染到 EGLSurface → Mesa 将命令提交给 GPU → 合成器把多个窗口合成为一张最终图像 → 通过 DRM 的 atomic 接口提交到 KMS → KMS 配置 CRTC 扫描该 Framebuffer → Encoder 转换为 HDMI/DP 信号 → Connector 输出到显示器。

应用 (OpenGL/Vulkan)
   ↓ EGL / Vulkan WSI
Mesa 用户态驱动
   ↓ DRM ioctl (GEM 创建、execbuffer)
内核 DRM 核心
   ↓
GPU 驱动 (i915/amdgpu/...)
   ↓
KMS: Plane → CRTC → Encoder → Connector
   ↓
显示接口 (HDMI/DP/eDP/MIPI-DSI)
   ↓
物理屏幕

这条链路中,KMS 的四个对象是理解初始化的关键:Plane 提供图像层,CRTC 负责扫描时序与像素输出,Encoder 把像素流转换为接口信号,Connector 表示物理输出端口及其连接状态。DRM 初始化时,驱动会枚举这些对象并建立它们之间的可能组合。

三、DRM/KMS 与 Framebuffer 的关系

传统 fbdev 只提供一个线性帧缓冲,应用直接写 /dev/fb0。它简单,但无法处理多图层、页面翻转、多显示器、GPU 渲染同步。DRM/KMS 则把 Framebuffer 抽象为 GEM 对象,并允许通过 ioctl 动态创建、映射和提交。KMS 的 atomic 模式进一步把“一次显示配置”打包成不可分割的事务,保证 Plane、CRTC、Connector 的状态一致切换。

层次 典型组件 核心职责 对外接口
应用层 浏览器、游戏、Qt/GTK 产生绘制命令与窗口内容 OpenGL/Vulkan/工具包 API
显示服务器 Xorg、Wayland 合成器 窗口管理、合成、输入分发 X11 协议、Wayland 协议
用户态图形驱动 Mesa、NVIDIA 用户态 命令翻译、内存管理、WSI DRM ioctl、GBM
内核 DRM/KMS drm.ko、KMS 核心 显存对象、显示管线、模式设置 /dev/dri/card0、sysfs
硬件驱动 i915、amdgpu、msm 寄存器操作、时钟、电源 内核内部函数
显示硬件 GPU、显示控制器、PHY 扫描输出、信号编码 HDMI/DP/eDP/MIPI-DSI

四、典型场景:一次页面翻转

假设合成器要显示下一帧。它先通过 GBM 从 DRM 分配两个 Framebuffer(双缓冲),渲染完成后调用 drmModeAtomicCommit,把新的 Framebuffer 绑定到 Primary Plane,并请求页面翻转。内核在垂直消隐期切换扫描地址,旧缓冲被释放回合成器。整个过程用户态不直接碰寄存器,也不直接写 /dev/fb0。

# 查看当前 DRM 设备与连接器状态
ls /dev/dri/
cat /sys/class/drm/card0-HDMI-A-1/status
modetest -M rockchip -c   # 列出 Connector/CRTC/Plane

如果此时用 modetest 看到 CRTC 已绑定 Framebuffer,说明 KMS 已完成模式设置;若 status 为 disconnected,则 Connector 没有检测到显示器,后续初始化会停在探测阶段。

五、常见误区与复盘

误区一:认为“有 /dev/fb0 就不需要 DRM”。实际上现代 GPU 驱动通常同时提供 fbdev 模拟层,但真正的显示控制仍走 KMS。
误区二:把 GPU 驱动等同于显示驱动。GPU 驱动负责渲染与显存,显示驱动负责 CRTC/Encoder/Connector 时序,两者在 DRM 中协作但职责不同。
误区三:认为 Wayland 取代了 DRM。Wayland 是用户态协议,最终仍通过 DRM/KMS 提交扫描缓冲。

复盘时可以用一句话定位问题:应用画不出 → 查 Mesa/GPU;窗口不显示 → 查合成器;屏幕无信号 → 查 KMS 的 Connector/Encoder;花屏或撕裂 → 查 Framebuffer 格式与页面翻转。建立这条纵向链路后,后续分析 DRM 设备初始化、KMS 对象创建和驱动 probe 顺序时,就能准确判断每一步发生在哪一层。

Linux 图形栈全景:应用 → 显示硬件 用户态 User Space 应用程序 OpenGL / Vulkan / Qt 窗口系统 / 显示服务器 Xorg / Wayland 合成器 用户态图形驱动 Mesa / GBM / EGL 图形 API 与合成路径 应用渲染 → 合成器合成 → 生成最终 Framebuffer 内容 内核态 Kernel Space DRM 核心 GEM / TTM 显存对象 /dev/dri/card0 KMS 显示管线 Plane → CRTC → Encoder → Connector 硬件驱动 i915 / amdgpu / msm rockchip-drm Framebuffer:像素数据缓冲区(GEM 对象 / fbdev 模拟) KMS 负责把 Framebuffer 扫描输出到物理屏幕 显示硬件:GPU / 显示控制器 / PHY → HDMI · DP · eDP · MIPI-DSI → 屏幕 扫描输出 · 信号编码 · 时序生成