3、驱动框架入门:Linux DRM框架与FBDEV框架的对比与选择
做显示驱动开发,绕不开两个框架:老牌的 FBDEV 和后来居上的 DRM。很多刚入行的朋友问我:「到底学哪个?项目里该用哪个?」
我的回答很直接:新项目无脑选 DRM,除非你在维护十年前的 legacy 代码。但这话不能说得太绝对,咱们得把背后的逻辑掰开揉碎了讲清楚。
3.1 FBDEV:那个曾经辉煌的「帧缓冲」时代
FBDEV(Framebuffer Device)是 Linux 内核里最古老的显示驱动框架。它的设计哲学很简单——给你一块内存区域,你把像素数据填进去,它负责显示出来。
我记得 2012 年做第一个嵌入式项目时,用的就是 FBDEV。那时候觉得这东西真方便,一个 open("/dev/fb0"),再 mmap 一下,就能直接操作屏幕了。
FBDEV 的核心模型:
- 用户空间直接操作 framebuffer 内存
- 内核只负责简单的显示时序控制
- 不支持硬件图层叠加、不支持 VSYNC 同步
- 多显示器管理基本靠「猜」
说白了,FBDEV 就是个「傻白甜」——你给它什么,它就显示什么。没有图层管理,没有合成引擎,更没有现代显示框架必备的 atomic 操作。
避坑指南:我曾经在一个项目里用 FBDEV 做双屏异显,结果发现要自己维护两个 framebuffer 的同步。更坑的是,切换分辨率时整个显示系统会闪一下,因为 FBDEV 不支持平滑的模式切换。那段时间真是被折腾得够呛。
3.2 DRM:现代显示驱动的「瑞士军刀」
DRM(Direct Rendering Manager)最初是为 GPU 设计的,后来逐渐演变成了 Linux 上标准的显示驱动框架。它的设计思路和 FBDEV 完全不同——DRM 把显示硬件抽象成一组可管理的资源。
你想想看,现代 SoC 的显示模块有多复杂?多个图层、旋转、缩放、色彩转换、HDR...FBDEV 那套「一块内存打天下」的思路根本玩不转。
DRM 引入了几个核心概念:
- CRTC:显示控制器,负责生成显示时序
- Encoder:编码器,把像素数据转换成特定接口信号(HDMI、DP、MIPI DSI 等)
- Connector:物理连接器,代表实际的显示设备
- Plane:硬件图层,每个 plane 可以独立配置
- Framebuffer:内存中的像素数据缓冲区
这些概念组合起来,构成了一个完整的显示 pipeline。我习惯用一张图来理解它:
DRM 最让我欣赏的一点是它的 atomic 接口。什么意思呢?就是你可以一次性提交多个显示状态的变更,内核保证这些变更要么全部生效,要么全部回滚。不会出现「屏幕一半刷新了,一半还是旧的」这种尴尬情况。
个人经验:我建议你在写 DRM 驱动时,优先使用 drm_atomic_helper 系列函数。这些 helper 帮你处理了大部分状态管理的脏活累活。我自己早期手写过 atomic commit 的逻辑,结果发现各种竞态条件,后来老老实实换回了 helper。
3.3 正面硬刚:DRM vs FBDEV 对比表
咱们直接上表格,一目了然:
| 对比维度 | FBDEV | DRM |
|---|---|---|
| 内核版本 | 2.1 引入(1997年) | 2.6.29 引入(2009年) |
| 硬件抽象 | 仅 framebuffer | CRTC + Encoder + Connector + Plane |
| 图层支持 | 无(需用户空间合成) | 硬件图层(Plane) |
| VSYNC 同步 | 不支持 | 原生支持(page flip) |
| 多显示器 | 多个 /dev/fbX,各自独立 | 统一管理,支持克隆/扩展 |
| 模式切换 | 可能闪烁 | atomic 平滑切换 |
| GPU 加速 | 无 | 原生支持(GEM/Dumb Buffer) |
| 用户空间 API | ioctl + mmap | libdrm (drmMode*) |
| 维护状态 | 维护模式(不推荐新驱动) | 活跃开发(推荐) |
3.4 实际项目中的选择策略
好了,理论说完了,咱们聊聊实际项目里怎么选。我个人总结了一个简单的决策树:
- 新项目,新硬件 → 无脑 DRM。别犹豫,FBDEV 已经不适合现代显示硬件了。
- 维护老项目,硬件不支持 DRM → 继续用 FBDEV。但可以考虑加一个
drm_fb_helper做兼容层。 - 极简场景,比如单色屏、低分辨率 → FBDEV 可能更简单。但说实话,现在连单片机都开始用 DRM 了。
- 需要 GPU 加速、游戏、Wayland → 必须是 DRM。FBDEV 根本跑不动现代图形栈。
我的建议:如果你正在写一个新的显示驱动,直接基于 DRM 的 drm_simple_display_pipe 开始。这个 helper 把 CRTC、Encoder、Connector 打包成了一个简单的管道,非常适合嵌入式场景。我最近三个项目都是这么干的,开发效率至少提升 30%。
3.5 代码示例:FBDEV vs DRM 的「Hello World」
咱们用代码说话。同样是初始化一个简单的显示驱动:
FBDEV 方式:
static int fb_probe(struct platform_device *pdev)
{
struct fb_info *fbi;
struct resource *res;
fbi = framebuffer_alloc(0, &pdev->dev);
if (!fbi)
return -ENOMEM;
// 设置固定参数
fbi->fix.smem_start = res->start;
fbi->fix.smem_len = resource_size(res);
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
// 设置可变参数
fbi->var.xres = 800;
fbi->var.yres = 480;
fbi->var.bits_per_pixel = 32;
// 注册 framebuffer
if (register_framebuffer(fbi) < 0) {
framebuffer_release(fbi);
return -EINVAL;
}
return 0;
}
DRM 方式(使用 simple pipe helper):
static const struct drm_simple_display_pipe_funcs pipe_funcs = {
.check = my_pipe_check,
.update = my_pipe_update,
.enable = my_pipe_enable,
.disable = my_pipe_disable,
};
static int drm_probe(struct platform_device *pdev)
{
struct drm_device *drm;
struct drm_connector *connector;
drm = drm_dev_alloc(&my_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
// 使用 simple pipe 替代手动管理 CRTC/Encoder
ret = drm_simple_display_pipe_init(drm, &pipe, &pipe_funcs,
drm_mode_format_list,
ARRAY_SIZE(drm_mode_format_list),
NULL, connector);
drm_dev_register(drm, 0);
return 0;
}
看到了吗?DRM 的代码量并没有比 FBDEV 多多少,但你能获得的硬件控制能力是天壤之别。
小技巧:调试 DRM 驱动时,多用 modetest 工具。它来自 libdrm 的测试套件,可以枚举所有 CRTC、Encoder、Connector 和 Plane,还能直接测试模式设置。我每次写完驱动,第一件事就是跑 modetest -M mydriver 看看硬件拓扑对不对。
3.6 总结:别纠结,选 DRM
说了这么多,其实就一句话:除非你有非常特殊的理由,否则新驱动请用 DRM。
FBDEV 就像当年的功能手机——打电话发短信没问题,但你要用它刷抖音、玩王者荣耀,那就别想了。DRM 是智能手机,功能强大,生态完善,而且还在不断进化。
我记得有一次给客户做方案评审,对方坚持要用 FBDEV,理由是「简单稳定」。我当场打开内核源码,把 drivers/gpu/drm 和 drivers/video/fbdev 两个目录的代码行数对比给他看——DRM 的维护者数量和提交频率是 FBDEV 的 10 倍以上。哪个更「稳定」,不言而喻。