2. 帧缓冲(FBDEV)基础:Linux帧缓冲子系统架构、fb_ops结构体、核心数据结构
帧缓冲(Frame Buffer, FBDEV)是 Linux 内核中最早用于显示输出的子系统,它为用户空间提供了一个简单的内存映射接口,使得应用程序可以直接向显示缓冲区写入像素数据。虽然现代 DRM/KMS 已经取代了 FBDEV 的大部分功能,但理解 FBDEV 的架构和核心数据结构,是掌握 Linux 显示驱动演进的关键一步,也是后续迁移到 KMS 的基础。
2.1 帧缓冲子系统整体架构
FBDEV 子系统采用分层设计,从上到下依次为:用户空间接口层、核心框架层、硬件驱动层。其核心思想是将显示缓冲区抽象为一个字符设备(/dev/fbX),用户态通过 mmap 直接操作显存。
架构要点: FBDEV 的核心是
struct fb_info,它串联了所有关键组件,包括设备属性、操作函数集、可变/固定参数、以及底层硬件回调。
2.2 核心数据结构:struct fb_info
struct fb_info 是帧缓冲驱动的核心枢纽,每个帧缓冲设备对应一个 fb_info 实例。它包含了设备的所有状态信息、操作函数指针、以及屏幕参数。
| 成员 | 类型 | 描述 |
|---|---|---|
fbops |
struct fb_ops * |
硬件操作函数集,驱动必须实现 |
var |
struct fb_var_screeninfo |
可变屏幕参数(分辨率、位深、时序等) |
fix |
struct fb_fix_screeninfo |
固定屏幕参数(物理地址、行长、内存大小等) |
screen_base |
char __iomem * |
帧缓冲内存的虚拟地址(用于 mmap) |
screen_size |
unsigned long |
帧缓冲内存总大小(字节) |
device |
struct device * |
关联的 Linux 设备模型节点 |
par |
void * |
驱动私有数据指针,用于扩展 |
pseudo_palette |
void * |
伪调色板,用于 8/16 位色深模式 |
关键理解:
var 和 fix 是用户空间通过 FBIOGET_VSCREENINFO / FBIOGET_FSCREENINFO ioctl 获取的参数。驱动在 fb_ops.fb_check_var 中验证用户设置的 var 参数是否合法。
2.3 操作函数集:struct fb_ops
struct fb_ops 定义了驱动需要实现的回调函数,是驱动与核心框架交互的接口。每个驱动必须至少实现 fb_check_var 和 fb_set_par,其他函数根据硬件能力选择性实现。
2.3.1 必须实现的回调函数
- fb_check_var:验证用户传入的
fb_var_screeninfo参数是否合法,如分辨率是否支持、位深是否正确、时序是否在硬件范围内。如果参数不合法,驱动应将其调整为最接近的合法值。 - fb_set_par:根据
fb_info.var中的参数配置硬件寄存器,包括设置显示控制器时序、帧缓冲地址、颜色格式等。这是驱动中最关键的硬件配置函数。 - fb_setcolreg:设置颜色寄存器,用于伪彩色模式(8/16 bpp)。对于真彩色模式(24/32 bpp),此函数可以为空或仅更新伪调色板。
2.3.2 可选加速回调函数
FBDEV 提供了三个加速函数,用于在硬件支持的情况下加速图形操作,避免通过 CPU 逐像素操作:
| 函数 | 作用 | 典型硬件支持 |
|---|---|---|
fb_fillrect |
用指定颜色填充矩形区域 | 2D 加速引擎、Blitter |
fb_copyarea |
在帧缓冲内拷贝矩形区域 | 2D 加速引擎、Blitter |
fb_imageblit |
将图像数据渲染到帧缓冲 | 2D 加速引擎、光标硬件 |
迁移注意: 在 DRM/KMS 中,这些加速操作由
drm_fb_helper 或用户空间的 libdrm/Mesa 接管,不再需要驱动实现。FBDEV 的加速函数在迁移时通常会被移除或重写为 DRM 的 drm_plane 操作。
2.4 屏幕参数结构体
FBDEV 使用两个结构体来描述屏幕参数:可变参数和固定参数。用户空间通过 ioctl 读取和设置这些参数。
2.4.1 struct fb_var_screeninfo(可变参数)
该结构体包含用户可修改的显示参数,如分辨率、位深、时序等。驱动在 fb_check_var 中验证其合法性。
struct fb_var_screeninfo {
__u32 xres; /* 可见水平分辨率(像素) */
__u32 yres; /* 可见垂直分辨率(像素) */
__u32 xres_virtual; /* 虚拟水平分辨率(支持滚屏) */
__u32 yres_virtual; /* 虚拟垂直分辨率 */
__u32 xoffset; /* 虚拟到可见的水平偏移 */
__u32 yoffset; /* 虚拟到可见的垂直偏移 */
__u32 bits_per_pixel; /* 每像素位数(bpp) */
__u32 grayscale; /* 灰度标志 */
struct fb_bitfield red; /* 红色通道位域 */
struct fb_bitfield green; /* 绿色通道位域 */
struct fb_bitfield blue; /* 蓝色通道位域 */
struct fb_bitfield transp;/* 透明度通道位域 */
__u32 pixclock; /* 像素时钟(皮秒) */
__u32 left_margin; /* 行同步前沿 */
__u32 right_margin; /* 行同步后沿 */
__u32 upper_margin; /* 帧同步前沿 */
__u32 lower_margin; /* 帧同步后沿 */
__u32 hsync_len; /* 行同步脉冲长度 */
__u32 vsync_len; /* 帧同步脉冲长度 */
__u32 sync; /* 同步极性标志 */
__u32 vmode; /* 显示模式(隔行/逐行) */
__u32 rotate; /* 旋转角度 */
};
2.4.2 struct fb_fix_screeninfo(固定参数)
该结构体包含硬件固定的参数,用户空间只能读取,不能修改。
struct fb_fix_screeninfo {
char id[16]; /* 驱动标识字符串 */
unsigned long smem_start; /* 帧缓冲物理起始地址 */
__u32 smem_len; /* 帧缓冲内存总长度(字节) */
__u32 type; /* 显示类型(如 FB_TYPE_PACKED_PIXELS) */
__u32 type_aux; /* 辅助类型 */
__u32 visual; /* 颜色模式(真彩色/伪彩色/直接彩色) */
__u16 xpanstep; /* 水平滚屏步长 */
__u16 ypanstep; /* 垂直滚屏步长 */
__u16 ywrapstep; /* 垂直回绕步长 */
__u32 line_length; /* 一行像素的字节数(stride) */
unsigned long mmio_start; /* 内存映射 IO 物理起始地址 */
__u32 mmio_len; /* 内存映射 IO 长度 */
__u32 accel; /* 加速器类型 */
__u16 capabilities; /* 能力标志 */
};
关键差异:
smem_start 是物理地址,用户空间通过 mmap 映射到虚拟地址空间。line_length 是每行像素占用的实际字节数,通常大于 xres * (bits_per_pixel / 8),因为硬件可能要求行对齐。
2.5 帧缓冲设备注册流程
一个典型的 FBDEV 驱动在 probe 函数中完成以下步骤:
- 分配 fb_info:调用
framebuffer_alloc()分配struct fb_info实例。 - 设置固定参数:填充
fix结构体,包括物理地址、内存大小、行长等。 - 设置可变参数默认值:填充
var结构体,设置默认分辨率、位深、时序。 - 注册 fb_ops:将驱动实现的
fb_ops赋值给fb_info->fbops。 - 分配帧缓冲内存:通过
dma_alloc_coherent()或ioremap()分配/映射显存。 - 注册设备:调用
register_framebuffer()将设备注册到系统。
2.6 用户空间接口
FBDEV 通过字符设备 /dev/fbX 向用户空间提供以下操作:
| 操作 | 系统调用 | 描述 |
|---|---|---|
| 打开设备 | open() |
打开 /dev/fb0 等设备节点 |
| 获取固定信息 | ioctl(FBIOGET_FSCREENINFO) |
读取 fb_fix_screeninfo |
| 获取可变信息 | ioctl(FBIOGET_VSCREENINFO) |
读取 fb_var_screeninfo |
| 设置可变信息 | ioctl(FBIOPUT_VSCREENINFO) |
设置新的显示参数 |
| 映射帧缓冲 | mmap() |
将显存映射到用户空间 |
| 刷写显示 | ioctl(FBIOPAN_DISPLAY) |
平移显示起始位置(滚屏) |
调试技巧: 使用
fbset 工具可以查看和修改当前帧缓冲参数:fbset -i 显示当前配置,fbset -xres 1024 -yres 768 -depth 32 修改分辨率。使用 cat /dev/urandom > /dev/fb0 可以快速测试帧缓冲是否工作(会显示噪点)。
2.7 FBDEV 的局限性
虽然 FBDEV 架构简单,但在现代显示需求下存在明显不足:
- 单缓冲限制:FBDEV 原生只支持单缓冲,虽然可以通过虚拟分辨率实现软件双缓冲,但效率低下且无法避免撕裂。
- 无硬件合成:不支持硬件图层(plane)合成,所有图层必须在软件中合成后再写入帧缓冲。
- 无垂直同步控制:缺乏对 VBlank 的精确控制,无法实现无撕裂的页面翻转。
- 无连接器管理:不支持热插拔检测、EDID 读取、多显示器配置等现代显示管理功能。
- 无 Modset 接口:不支持原子模式设置(Atomic Modesetting),配置切换可能产生闪烁。
迁移动机: 上述局限性正是 DRM/KMS 要解决的核心问题。在后续章节中,我们将看到 DRM 如何通过
drm_crtc、drm_plane、drm_connector 等对象优雅地解决这些问题。