3. Linux帧缓冲架构
帧缓冲(FrameBuffer)是Linux内核为显示设备提供的一种统一抽象层。它屏蔽了底层LCD控制器的硬件差异,向上层用户空间提供/dev/fbX设备文件,使得应用程序可以通过mmap、ioctl等标准接口直接操作显存。本章深入剖析帧缓冲架构的四个核心支柱:fbmem.c(框架核心)、fb_info(设备描述符)、fb_ops(硬件操作集)以及注册/注销(生命周期管理)。
3.1 fbmem.c:帧缓冲框架核心
fbmem.c位于drivers/video/fbdev/core/目录,是帧缓冲子系统的中枢调度器。它不直接操作硬件,而是负责:
- 注册字符设备主设备号29,管理
/dev/fb[0-31]次设备。 - 实现统一的
file_operations:open、release、read、write、mmap、ioctl。 - 维护全局
registered_fb数组,保存所有已注册的fb_info指针。 - 提供
register_framebuffer/unregister_framebuffer内核API。
关键设计: fbmem.c 将VFS层与硬件驱动层解耦。驱动开发者只需填充
fb_info和fb_ops,无需关心字符设备注册细节。
主设备号29: 帧缓冲是内核中少数使用静态主设备号的子系统之一。次设备号0通常对应主显存(如LCD),1对应叠加层或TV输出。
3.2 fb_info 结构体:帧缓冲设备描述符
fb_info是帧缓冲驱动的核心数据结构,定义在include/linux/fb.h。每个LCD设备对应一个fb_info实例,它聚合了设备的所有属性、状态和操作入口。
| 字段 | 类型 | 说明 |
|---|---|---|
| fb_var_screeninfo | var |
可变参数:分辨率、位深、时序(可通过ioctl动态修改) |
| fb_fix_screeninfo | fix |
固定参数:显存物理地址、长度、行宽(驱动初始化时确定) |
| fb_ops | *fbops |
硬件操作函数指针集(见3.3节) |
| screen_base | void * |
显存虚拟地址(内核映射后使用) |
| screen_size | size_t |
显存总大小(字节) |
| device | struct device * |
指向平台设备或PCI设备的通用设备结构 |
| par | void * |
私有数据指针,驱动自定义扩展结构 |
| flags | unsigned long |
状态标志:FBINFO_FLAG_DEFAULT / FBINFO_HWACCEL_* |
| node | int |
次设备号(-1表示未注册) |
常见陷阱:
screen_base 必须在 fb_ops->fb_mmap 之前完成 ioremap 或 dma_alloc_coherent 映射。如果 screen_base 为 NULL,用户空间 mmap 将返回 ENODEV。
3.3 fb_ops 操作集:硬件抽象接口
fb_ops是驱动开发者必须实现的函数指针集合。fbmem.c 在收到用户空间请求后,通过fb_info->fbops调用对应的硬件操作。
3.3.1 核心操作函数
| 函数指针 | 原型 | 调用时机 |
|---|---|---|
| fb_open | int (*fb_open)(fb_info*, int) |
用户 open /dev/fbX 时 |
| fb_release | int (*fb_release)(fb_info*, int) |
用户 close /dev/fbX 时 |
| fb_read | ssize_t (*fb_read)(fb_info*, char*, size_t, loff_t*) |
用户 read() 调用 |
| fb_write | ssize_t (*fb_write)(fb_info*, const char*, size_t, loff_t*) |
用户 write() 调用 |
| fb_check_var | int (*fb_check_var)(fb_var_screeninfo*, fb_info*) |
设置分辨率/位深前校验参数合法性 |
| fb_set_par | int (*fb_set_par)(fb_info*) |
应用 var 参数到硬件寄存器 |
| fb_setcolreg | int (*fb_setcolreg)(unsigned, unsigned, unsigned, unsigned, unsigned, fb_info*) |
设置调色板寄存器(伪彩色模式) |
| fb_blank | int (*fb_blank)(int, fb_info*) |
屏幕开关/省电模式 |
| fb_pan_display | int (*fb_pan_display)(fb_var_screeninfo*, fb_info*) |
硬件平移显示(双缓冲滚动) |
| fb_mmap | int (*fb_mmap)(fb_info*, struct vm_area_struct*) |
用户 mmap 映射显存 |
| fb_ioctl | int (*fb_ioctl)(fb_info*, unsigned int, unsigned long) |
自定义 ioctl 命令 |
最小实现要求: 一个可工作的帧缓冲驱动至少需要实现
fb_check_var、fb_set_par、fb_setcolreg(伪彩色)、fb_blank 和 fb_mmap。其余函数可使用内核提供的默认实现。
3.3.2 fb_ops 初始化示例
static struct fb_ops lcd_fb_ops = {
.owner = THIS_MODULE,
.fb_open = lcd_fb_open,
.fb_release = lcd_fb_release,
.fb_read = fb_sys_read, // 使用内核通用实现
.fb_write = fb_sys_write,
.fb_check_var = lcd_fb_check_var,
.fb_set_par = lcd_fb_set_par,
.fb_setcolreg = lcd_fb_setcolreg,
.fb_blank = lcd_fb_blank,
.fb_pan_display = lcd_fb_pan_display,
.fb_fillrect = cfb_fillrect, // 图形加速(可选)
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = lcd_fb_mmap,
};
加速提示: 如果 LCD 控制器支持硬件 2D 加速(如 DMA 矩形填充),应实现
fb_fillrect、fb_copyarea、fb_imageblit,否则使用 cfb_* 软件模拟函数。
3.4 注册与注销流程
帧缓冲驱动的生命周期由register_framebuffer和unregister_framebuffer管理。这两个函数在fbmem.c中实现,驱动通常在probe函数中注册,在remove函数中注销。
3.4.1 注册流程(register_framebuffer)
- 参数校验: 检查
fb_info指针非空,fbops非空。 - 分配次设备号: 遍历
registered_fb[],找到第一个空槽(node = -1)。 - 创建设备文件: 调用
device_create()在/dev下生成fbX。 - 初始化锁: 初始化
fb_info->lock用于并发保护。 - 设置默认参数: 如果
var未初始化,使用fb_set_par应用硬件默认值。 - 通知链回调: 调用
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED),通知上层(如控制台、DRM)新设备加入。 - 返回次设备号: 成功返回
node(≥0),失败返回负错误码。
3.4.2 注销流程(unregister_framebuffer)
- 通知链回调: 调用
fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED),通知上层设备即将移除。 - 删除设备文件: 调用
device_destroy()移除/dev/fbX。 - 释放显存映射: 如果
screen_base由ioremap分配,调用iounmap。 - 清空槽位: 将
registered_fb[node]置为NULL,fb_info->node置为-1。 - 等待引用归零: 确保没有用户空间进程持有
fbX的文件描述符。
注销时序风险: 如果用户空间在注销时仍
mmap 了显存,内核将触发 use-after-free。驱动应在 fb_ops->fb_release 中检查引用计数,或在 unregister_framebuffer 前强制 vm_unmap。
3.4.3 典型驱动 Probe 模板
static int lcd_fb_probe(struct platform_device *pdev)
{
struct fb_info *fbi;
struct lcd_par *par;
int ret;
// 1. 分配 fb_info + 私有数据
fbi = framebuffer_alloc(sizeof(struct lcd_par), &pdev->dev);
if (!fbi)
return -ENOMEM;
par = fbi->par;
// 2. 映射 LCD 控制器寄存器
par->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(par->regs)) {
ret = PTR_ERR(par->regs);
goto err_free_fb;
}
// 3. 分配显存(DMA 一致性内存)
fbi->screen_base = dma_alloc_coherent(&pdev->dev, SZ_4M,
&fbi->fix.smem_start, GFP_KERNEL);
if (!fbi->screen_base) {
ret = -ENOMEM;
goto err_free_fb;
}
fbi->screen_size = SZ_4M;
// 4. 填充固定/可变参数
strscpy(fbi->fix.id, "lcd_fb", sizeof(fbi->fix.id));
fbi->fix.smem_len = SZ_4M;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->var.xres = 1024;
fbi->var.yres = 600;
fbi->var.bits_per_pixel = 32;
// 5. 绑定 fb_ops
fbi->fbops = &lcd_fb_ops;
// 6. 注册帧缓冲设备
ret = register_framebuffer(fbi);
if (ret < 0) {
dev_err(&pdev->dev, "register_framebuffer failed: %d\n", ret);
goto err_free_dma;
}
platform_set_drvdata(pdev, fbi);
return 0;
err_free_dma:
dma_free_coherent(&pdev->dev, SZ_4M, fbi->screen_base, fbi->fix.smem_start);
err_free_fb:
framebuffer_release(fbi);
return ret;
}
static int lcd_fb_remove(struct platform_device *pdev)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
unregister_framebuffer(fbi);
dma_free_coherent(&pdev->dev, fbi->screen_size,
fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
return 0;
}