Linux DRM显示驱动开发与fbdev兼容集成 - 第1节
1.1 Linux 图形栈的演进历史:从 FBdev 到 DRM/KMS
Linux 图形子系统在近三十年的演进中,经历了从简单粗糙的帧缓冲驱动框架(FBdev)到现代、统一的直接渲染管理器/内核模式设置(DRM/KMS)的深刻变革。理解这一演进过程,是掌握 Linux 图形开发的基础。
1.1.1 早期时代:FBdev 框架
在 Linux 2.2 至 2.6 早期内核版本中,FBdev(Frame Buffer Device) 是唯一的显示输出框架。它提供了一个简单的字符设备 /dev/fb*,用户空间通过 mmap 直接操作帧缓冲内存。
- 单一平面:仅支持单个显示层,无法实现硬件合成。
- 无模式设置抽象:分辨率、刷新率等设置通过
FBIO_SET_VARioctl 直接操作寄存器,缺乏原子性。 - 无 GPU 加速:所有 2D/3D 渲染必须通过用户态驱动或软件模拟。
- 多显示器支持差:扩展桌面需依赖 Xorg 的多个
/dev/fb*实例,无法实现无缝克隆或扩展。
FBdev 的主要问题在于它把显示控制与帧缓冲内存管理耦合在一起,且缺乏对现代 GPU 硬件(如硬件光标、多图层、垂直同步控制)的抽象能力。随着 X.Org 和 Wayland 对合成、原子更新、GPU 共享的需求增长,FBdev 逐渐成为瓶颈。
1.1.2 现代框架:DRM/KMS 的诞生
DRM(Direct Rendering Manager) 最初是为 3D 加速设计的,但 Linux 2.6.29 引入的 KMS(Kernel Mode Setting) 组件彻底改变了显示控制方式。KMS 将模式设置、平面管理、垂直同步控制等关键功能移入内核,并提供统一的 API。
| 对比维度 | FBdev | DRM/KMS |
|---|---|---|
| 设备节点 | /dev/fb0 (字符设备) |
/dev/dri/card0 (DRM 主设备) |
| 模式设置 | ioctl 直接操作寄存器,无原子性 | atomic API,支持事务性提交 |
| 显示平面 | 单平面,无硬件合成 | 多平面 (primary/overlay/cursor),硬件合成 |
| GPU 加速 | 无内核抽象,依赖用户态 | GEM/Dumb Buffer,支持 GPU 共享内存 |
| 多显示器 | 多个 /dev/fb* 实例,难以同步 | 单个 DRM 设备管理多个 CRTC/Encoder/Connector |
| 垂直同步 | 无标准接口 | Page Flip + VBlank 事件 |
1.2 用户空间与内核空间的交互模型
DRM/KMS 采用 客户端-服务器 模型,用户空间通过系统调用与内核 DRM 驱动通信。核心交互路径包括:设备打开与内存分配、模式设置与平面配置、渲染提交与页面翻转。
1.2.1 核心交互流程
- 打开 DRM 设备: 用户空间调用
open("/dev/dri/card0")获取文件描述符。 - 查询资源: 通过
drmModeGetResources()获取 CRTC、Encoder、Connector、Plane 列表。 - 分配帧缓冲: 使用
drmModeAddFB()或 GEM 接口创建 dumb buffer。 - 配置模式: 通过
drmModeSetCrtc()或 atomic ioctl 设置分辨率、刷新率。 - 渲染与提交: 用户空间渲染到 GEM 对象,通过
drmModePageFlip()或 atomic commit 提交显示。 - 等待同步: 通过
drmWaitVBlank()等待垂直消隐期,避免撕裂。
1.2.2 关键数据结构与 ioctl 映射
DRM 内核侧维护一组核心对象,用户空间通过 ioctl 操作这些对象。下表列出最常用的 ioctl 及其对应内核处理函数:
| 用户空间调用 | ioctl 命令 | 内核处理函数 | 功能描述 |
|---|---|---|---|
drmModeAddFB() |
DRM_IOCTL_MODE_ADDFB |
drm_mode_addfb() |
创建帧缓冲对象,关联 GEM handle |
drmModeSetCrtc() |
DRM_IOCTL_MODE_SETCRTC |
drm_mode_setcrtc() |
设置 CRTC 连接模式与帧缓冲 |
drmModeAtomicCommit() |
DRM_IOCTL_MODE_ATOMIC |
drm_mode_atomic_ioctl() |
原子提交多个属性变更 |
drmModePageFlip() |
DRM_IOCTL_MODE_PAGE_FLIP |
drm_mode_page_flip_ioctl() |
请求在下一个 VBlank 切换帧缓冲 |
drmWaitVBlank() |
DRM_IOCTL_WAIT_VBLANK |
drm_wait_vblank_ioctl() |
等待垂直消隐期 |
1.2.3 现代用户空间栈:libdrm 与 Mesa
用户空间程序通常不直接调用 ioctl,而是通过 libdrm 库封装。libdrm 提供类型安全的 C API,隐藏了 ioctl 的细节。对于 3D 渲染,Mesa 使用 DRM 的 GEM 和调度接口实现 OpenGL/Vulkan 驱动。
// 1. 打开 DRM 设备
int fd = open("/dev/dri/card0", O_RDWR);
drmModeRes *res = drmModeGetResources(fd);
// 2. 分配 dumb buffer 作为帧缓冲
struct drm_mode_create_dumb create = {0};
create.width = 1920; create.height = 1080; create.bpp = 32;
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
// 3. 添加帧缓冲
uint32_t fb_id;
drmModeAddFB(fd, create.width, create.height, 24, 32,
create.pitch, create.handle, &fb_id);
// 4. 原子提交:设置 CRTC 连接该帧缓冲
drmModeAtomicReq *req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, crtc_id, crtc->mode_id, mode_blob_id);
drmModeAtomicAddProperty(req, crtc_id, crtc->active, 1);
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
drmModeAtomicFree(req);
1.2.4 事件与异步通知
DRM 支持异步事件机制,用户空间通过 poll() 或 epoll() 等待内核事件。常见事件类型包括:
- VBlank 事件: 每次垂直消隐期触发,用于帧率控制和撕裂避免。
- Page Flip 完成事件: 异步页面翻转完成后通知用户空间,可释放旧帧缓冲。
- 属性变更事件: 连接器热插拔、显示器 EDID 更新等异步通知。
DRM_IOCTL_MODE_ATOMIC 提交,不允许混合使用旧版 setCrtc/setPlane 与 atomic 调用。内核会严格校验提交的原子性,违反将返回 -EINVAL。
1.3 本章小结
Linux 图形子系统从 FBdev 演进到 DRM/KMS,核心变化在于:
- 抽象层次提升: 从直接操作帧缓冲内存,升级为对象化的 CRTC/Encoder/Connector/Plane 模型。
- 原子性保证: atomic API 确保多平面配置同时生效,消除撕裂和闪烁。
- GPU 协同: GEM 和 dma-buf 实现显存共享,支持合成器与 GPU 渲染无缝协作。
- 事件驱动: VBlank 和 Page Flip 事件机制为现代合成器(Wayland)提供低延迟、无撕裂的显示体验。