3. Linux帧缓冲架构:fbmem.c核心、fb_info结构体、fb_ops操作集、注册与注销流程

帧缓冲(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输出。
fbmem.c 核心调度架构 用户空间应用 VFS / dev/fb0 fbmem.c file_operations registered_fb[] register/unregister fb_notifier_call_chain fb_info #0 fb_info #1 fb_info #2 说明:fbmem.c 通过 registered_fb[] 数组管理所有 fb_info 实例, 用户空间的 open/read/write/mmap 最终由 fbmem.c 分发到对应 fb_info 的 fb_ops。

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。
fb_info 结构体布局 struct fb_info { fb_var_screeninfo var fb_fix_screeninfo fix fb_ops *fbops screen_base screen_size device * void *par flags / node / state }; // 总大小约 200~300 字节(依赖架构对齐) par 指向驱动私有结构,如 LCD 控制器寄存器基址

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_* 软件模拟函数。
fb_ops 调用流程 用户空间 系统调用层 fbmem.c file_operations fb_ops 函数指针跳转 LCD 控制器 硬件寄存器 说明:用户空间调用 ioctl(FBIOPUT_VSCREENINFO) → fbmem.c 解析 → fb_ops->fb_check_var 校验 → fb_ops->fb_set_par 写入硬件寄存器。整个流程无需驱动关心 VFS 细节。

3.4 注册与注销流程

帧缓冲驱动的生命周期由register_framebuffer和unregister_framebuffer管理。这两个函数在fbmem.c中实现,驱动通常在probe函数中注册,在remove函数中注销。

3.4.1 注册流程(register_framebuffer)

  1. 参数校验: 检查 fb_info 指针非空,fbops 非空。
  2. 分配次设备号: 遍历 registered_fb[],找到第一个空槽(node = -1)。
  3. 创建设备文件: 调用 device_create() 在 /dev 下生成 fbX。
  4. 初始化锁: 初始化 fb_info->lock 用于并发保护。
  5. 设置默认参数: 如果 var 未初始化,使用 fb_set_par 应用硬件默认值。
  6. 通知链回调: 调用 fb_notifier_call_chain(FB_EVENT_FB_REGISTERED),通知上层(如控制台、DRM)新设备加入。
  7. 返回次设备号: 成功返回 node(≥0),失败返回负错误码。

3.4.2 注销流程(unregister_framebuffer)

  1. 通知链回调: 调用 fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED),通知上层设备即将移除。
  2. 删除设备文件: 调用 device_destroy() 移除 /dev/fbX。
  3. 释放显存映射: 如果 screen_base 由 ioremap 分配,调用 iounmap。
  4. 清空槽位: 将 registered_fb[node] 置为 NULL,fb_info->node 置为 -1。
  5. 等待引用归零: 确保没有用户空间进程持有 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;
}
帧缓冲设备注册/注销状态机 未注册 node = -1 注册中 分配资源 已注册 node ≥ 0 注销中 释放资源 register 成功 unregister 完成 ← 上一章 📖 返回目录 下一章 →