1. 课程导入

在嵌入式 Linux、桌面 Linux 以及车载、工业 HMI 等场景中,“把画面显示出来”看似简单,背后却是一条从应用层到显示控制器硬件的完整软件栈。很多开发者第一次接触显示驱动时,会从 /dev/fb0 入手,用 mmap 直接写像素,觉得显示不过如此。但当需求升级到多图层叠加、硬件光标、页面翻转、分辨率动态切换、多屏异显时,FBDEV 的局限就会迅速暴露,这时就必须理解并迁移到 DRM/KMS 框架。本课程的目标,就是带你从帧缓冲的直觉认知出发,逐步建立 KMS 的完整心智模型,最终能够独立完成驱动适配与迁移。

一、Linux 图形显示栈的整体分层

Linux 图形显示栈可以自上而下划分为若干层,每一层解决不同的问题。理解分层,是理解 DRM 与 FBDEV 关系的前提。

  • 应用与图形库层:Qt、GTK、SDL、Wayland 合成器、X Server 等,它们通过 EGL/OpenGL ES、Vulkan 或直接 ioctl 与内核交互。
  • 用户态图形 API 层:Mesa、libdrm、GBM(Generic Buffer Management)等,负责把图形 API 调用翻译成对 DRM 设备的操作。
  • 内核显示子系统层:DRM/KMS 是当前主流框架,FBDEV 是早期框架,二者在 /dev 下分别暴露为 card0 与 fb0。
  • 显示控制器驱动层:CRTC、Encoder、Connector、Plane 等抽象对象,对应 SoC 内部的 Display Controller、MIPI DSI、HDMI、LVDS 等硬件模块。
  • 硬件层:GPU、Display Controller、PHY、面板与背光。

其中,DRM 子系统内部又细分为 KMS(Kernel Mode Setting,内核模式设置)与 GEM(Graphics Execution Manager,显存管理)两大块。KMS 负责显示管线的配置与输出,GEM 负责显存缓冲区的分配与共享。FBDEV 则是一个更薄、更早的抽象,它只提供一块线性帧缓冲和有限的 ioctl,几乎不涉及图层、页面翻转和原子提交。

Linux 图形显示栈分层与 FBDEV / DRM 关系 应用与图形库层 Qt / GTK / SDL / Wayland 合成器 / X Server / 直接 ioctl 应用 用户态图形 API 层 Mesa / libdrm / GBM / EGL / OpenGL ES / Vulkan 内核显示子系统层 DRM / KMS(主流) /dev/dri/card0 · 原子提交 · 多图层 FBDEV(传统) /dev/fb0 · 线性帧缓冲 · 有限 ioctl 显示控制器驱动层 CRTC · Encoder · Connector · Plane · Panel · Backlight · PHY 硬件层 GPU · Display Controller · MIPI DSI / HDMI / LVDS · 面板与背光 迁移方向

二、DRM 与 FBDEV 的关系:不是替代,而是包含与演进

很多资料把 DRM 和 FBDEV 描述成“新老对立”,这并不准确。更准确的说法是:DRM 在内部提供了 FBDEV 兼容层(drm_fbdev_generic 或 drm_fb_helper),使得一个 DRM 驱动可以同时向用户态暴露 /dev/fb0。这样,尚未适配 DRM 的老应用仍能通过 FBDEV 接口工作,而新应用则可以使用 KMS 的完整能力。

二者的核心差异可以用下表概括:

对比维度 FBDEV DRM / KMS
设备节点/dev/fb0/dev/dri/card0
缓冲区模型单一线性帧缓冲GEM 对象 + 多 Plane
图层能力无,需软件合成硬件多图层叠加
页面翻转不支持支持,配合 vblank 同步
模式设置ioctl 有限,能力弱原子提交,全管线配置
多屏支持弱Connector 级多屏异显
显存共享困难dma-buf 跨设备共享
提示:在阅读老驱动代码时,看到 register_framebuffer 说明是纯 FBDEV 驱动;看到 drm_dev_register 与 drm_fbdev_generic_setup 同时出现,说明是 DRM 驱动并开启了 FBDEV 兼容层。

三、从帧缓冲到 KMS 的迁移方法流程

迁移不是简单地把 fbdev 的 API 换成 drm 的 API,而是要把“一块内存 + 一个显示窗口”的思维,升级为“一组对象 + 一条管线 + 一次原子提交”的思维。建议按以下流程推进:

  1. 梳理现有 FBDEV 驱动:确认 fb_ops 中的 fb_check_var、fb_set_par、fb_pan_display 分别对应哪些硬件寄存器操作。
  2. 抽象出 KMS 对象:把帧缓冲映射为 drm_framebuffer,把显示窗口映射为 drm_plane,把时序控制映射为 drm_crtc,把输出接口映射为 drm_encoder 与 drm_connector。
  3. 实现原子提交:用 atomic_begin / atomic_flush 包裹寄存器写入,保证一次提交要么全部生效,要么全部回滚。
  4. 接入 GEM 与 dma-buf:让缓冲区可以被 GPU、视频解码器、显示控制器共享,避免多余的拷贝。
  5. 保留 FBDEV 兼容:通过 drm_fbdev_generic_setup 暴露 /dev/fb0,保证老应用平滑过渡。
# 查看当前系统显示设备
ls -l /dev/fb* /dev/dri/

# 查看 DRM 设备能力
modetest -M rockchip -c
modetest -M rockchip -p

# 查看 FBDEV 信息
fbset -i
cat /sys/class/graphics/fb0/virtual_size

四、典型场景与常见误区复盘

在真实项目中,迁移往往由具体需求驱动。例如车载仪表需要“仪表盘 + 视频 + 报警图标”三层叠加,FBDEV 只能靠 CPU 合成,帧率低且功耗高;而 KMS 可以直接把三层分别送到不同的 Plane,由显示控制器硬件合成。再如多屏异显场景,FBDEV 很难为两个屏幕分别维护独立的时序与缓冲区,而 KMS 的 Connector 模型天然支持。

常见误区一:认为“用了 DRM 就不能用 /dev/fb0”。实际上 DRM 可以同时提供 FBDEV 兼容节点,二者并不冲突。
常见误区二:把 KMS 当成“更复杂的 FBDEV”。KMS 的核心是对象模型与原子提交,不是简单的 API 替换。
常见误区三:忽略 vblank 与页面翻转的同步,导致画面撕裂。KMS 的 atomic commit 需要配合 drm_crtc_wait_one_vblank 或事件回调使用。

本课程后续将沿着“显示栈分层 → DRM 对象模型 → KMS 原子提交 → GEM 显存管理 → 驱动适配与调试”的主线展开。你会在每一节中看到真实的内核结构体、ioctl 命令、设备树节点和调试日志,最终能够独立完成一个从 FBDEV 到 KMS 的驱动迁移,并具备排查显示异常的能力。