3. Framebuffer子系统入门
在进入 DRM 之前,几乎每一位显示驱动工程师都会先与 Framebuffer(fbdev)子系统打交道。它是 Linux 内核中最古老、最直接的显示抽象层:把一块可以被 CPU 直接访问的显存区域,抽象成用户空间可见的 /dev/fbX 设备。理解 fbdev,不仅是移植旧驱动的必要技能,也是理解 DRM 中 framebuffer、plane、CRTC 等概念的历史参照系。
一、fbdev 的整体架构
fbdev 的核心思想非常朴素:内核负责管理一块线性显存(framebuffer memory),用户空间通过 mmap 把它映射到进程地址空间,然后直接往里面写像素。显示控制器(Display Controller)周期性地扫描这块内存,把内容送到屏幕上。整个子系统由三部分组成:
- 核心层(fbmem.c):提供字符设备注册、file_operations、ioctl 分发、mmap 实现,是所有 fb 设备共用的骨架。
- 驱动层(xxxfb.c):具体硬件驱动,负责填充
fb_info、实现fb_ops回调、申请显存、配置时序。 - 用户空间接口:设备节点
/dev/fb0,配合FBIOGET_VSCREENINFO等 ioctl 和 mmap 使用。
下图展示了 fbdev 从用户空间到硬件的完整数据通路与关键结构关系:
二、fb_info 结构:驱动的核心数据结构
struct fb_info 是 fbdev 驱动的“身份证”,它把硬件描述、显存信息、操作函数集全部聚合在一起。驱动 probe 阶段的主要工作,就是分配并填充这个结构,然后调用 register_framebuffer() 把它挂入内核。
其中最关键的两个子结构是 fb_var_screeninfo 和 fb_fix_screeninfo:前者描述“可变”的显示参数(分辨率、色深、时序),后者描述“固定”的硬件属性(显存物理地址、长度、行 stride)。用户空间通过 ioctl 读取它们,就能知道当前屏幕的能力边界。
struct fb_info {
struct fb_var_screeninfo var; /* 可变参数:分辨率、色深、时序 */
struct fb_fix_screeninfo fix; /* 固定参数:显存地址、长度、stride */
struct fb_ops *fbops; /* 硬件操作回调集合 */
char __iomem *screen_base; /* 显存虚拟基地址 */
unsigned long screen_size; /* 显存大小(字节) */
struct device *device; /* 关联的设备模型 */
int node; /* 分配到的 /dev/fbX 编号 */
/* ... 省略若干字段 ... */
};
三、注册与注销流程
fbdev 驱动的生命周期非常清晰,可以概括为“分配 → 填充 → 注册 → 使用 → 注销 → 释放”六步:
- 分配 fb_info:使用
framebuffer_alloc(),它会同时分配私有数据区。 - 填充关键字段:设置 var、fix、fbops、screen_base、screen_size。
- 注册设备:调用
register_framebuffer(info),内核分配/dev/fbX节点。 - 运行期:用户空间 mmap 显存、ioctl 查询/设置参数,驱动响应 fb_ops 回调。
- 注销设备:调用
unregister_framebuffer(info),释放设备节点。 - 释放内存:调用
framebuffer_release(info),回收 fb_info 及私有数据。
register_framebuffer() 之前完成 screen_base 映射和 fbops 赋值,否则用户空间一旦打开设备就可能触发空指针回调。
四、用户空间接口与典型应用场景
fbdev 对用户空间暴露的接口非常精简,主要就是设备节点加一组 ioctl:
| 接口 / ioctl | 作用 | 典型使用场景 |
|---|---|---|
open("/dev/fb0") |
打开 framebuffer 设备 | 所有 fb 应用的第一步 |
FBIOGET_VSCREENINFO |
获取可变屏幕信息 | 查询分辨率、色深、像素格式 |
FBIOPUT_VSCREENINFO |
设置可变屏幕信息 | 切换分辨率或色深 |
FBIOGET_FSCREENINFO |
获取固定屏幕信息 | 计算显存大小与行 stride |
mmap() |
映射显存到用户空间 | 直接绘制像素、双缓冲 |
FBIOPAN_DISPLAY |
切换显示起始行 | 实现页面翻转(page flipping) |
典型应用场景包括:嵌入式设备的开机 logo 显示、控制台终端(fbcon)、轻量级 GUI 框架(如 Qt Embedded 的 linuxfb 后端)、视频播放器的直接渲染层,以及工业 HMI 中的低延迟画面刷新。这些场景的共同点是:不需要复杂的 GPU 合成,只需要一块能直接写的显存。
五、常见误区与复盘
fix.line_length 时直接写成 xres * bpp / 8,但硬件往往要求行对齐(如 64 字节对齐)。如果 stride 算错,画面会出现斜纹或错位。
framebuffer_release() 再 unregister_framebuffer(),会导致内核访问已释放内存。正确顺序永远是先注销、后释放。
复盘来看,fbdev 的价值在于“简单直接”:它用最小的抽象代价换来了最快的上手速度。对于移植适配工作而言,先把 fbdev 跑通,再迁移到 DRM,是一条风险可控的路径。理解 fb_info 的字段含义、注册注销的时序约束,以及用户空间 ioctl 的语义,是后续 DRM 章节中理解 framebuffer 对象、plane 和 CRTC 的重要基础。