2. 帧缓冲设备节点:/dev/fb0 设备文件、主次设备号、设备注册流程

帧缓冲设备在 Linux 用户空间以 字符设备 的形式暴露,设备节点通常为 /dev/fb0、/dev/fb1 等。理解设备节点的创建、主次设备号的分配以及内核中的注册流程,是掌握帧缓冲驱动调试的基础。

2.1 设备节点 /dev/fb0

系统启动后,帧缓冲驱动注册成功,会在 /dev/ 目录下生成对应的设备文件。用户程序通过 open("/dev/fb0", O_RDWR) 即可获得帧缓冲设备的文件描述符,进而使用 mmap、ioctl 等系统调用操作显存。

设备节点 主设备号 次设备号 典型用途
/dev/fb0 29 0 主显示屏(LCD/HDMI)
/dev/fb1 29 1 第二显示屏或图层叠加层
/dev/fb2 29 2 硬件光标或辅助图层
调试提示: 使用 ls -l /dev/fb* 查看设备节点是否存在。若节点缺失,检查驱动是否加载(lsmod | grep fb)或使用 mknod /dev/fb0 c 29 0 手动创建。

2.2 主次设备号

帧缓冲设备的主设备号固定为 29(在 include/linux/major.h 中定义为 FB_MAJOR)。次设备号从 0 开始递增,每个次设备号对应一个独立的帧缓冲实例。

  • 主设备号 (29):标识帧缓冲驱动类型,内核通过主设备号找到对应的 file_operations 结构体。
  • 次设备号 (0, 1, 2...):区分不同的物理或虚拟显示层,驱动内部通过次设备号索引 fb_info 数组。
内核定义: #define FB_MAJOR 29 在 include/linux/major.h 中定义,所有帧缓冲设备共享此主设备号。

2.3 设备注册流程

帧缓冲设备在内核中的注册流程涉及三个核心步骤:分配 fb_info → 填充 fb_info → 注册到系统。以下为典型流程:

  1. 分配 fb_info 结构体:调用 framebuffer_alloc() 分配 struct fb_info 及其私有数据。
  2. 填充 fb_info 字段:设置 var(可变参数)、fix(固定参数)、fbops(操作函数集)、screen_base(显存基址)、screen_size(显存大小)。
  3. 注册帧缓冲设备:调用 register_framebuffer(struct fb_info *fb_info),内核自动完成:
    • 分配次设备号
    • 创建设备节点 /dev/fbN
    • 将 fb_info 加入全局链表 registered_fb
    • 调用 fb_notifier_call_chain() 通知上层
常见错误: 注册前未正确设置 fb_info->screen_base 或 fb_info->screen_size,会导致 mmap 时返回 EINVAL 或访问越界。

2.4 设备注册流程 SVG 架构图

帧缓冲设备注册流程架构图 帧缓冲设备注册流程 驱动入口 (probe) framebuffer_alloc() 填充 fb_info 字段 var / fix / fbops / screen_base / screen_size register_framebuffer() 内核自动完成 分配次设备号 → 创建设备节点 → 加入全局链表 /dev/fb0 可用 关键数据结构 struct fb_info struct fb_ops struct fb_var_screeninfo 注册返回值 0: 成功 -ENOMEM: 内存不足 -EINVAL: 参数无效

2.5 关键数据结构关系

帧缓冲驱动的核心是 struct fb_info,它串联了设备信息、操作函数和显存管理。以下表格列出其关键字段:

字段 类型 说明
var struct fb_var_screeninfo 可变参数:分辨率、位深、像素格式、时序
fix struct fb_fix_screeninfo 固定参数:显存地址、行长度、MMIO 地址
fbops struct fb_ops * 操作函数集:open/release/ioctl/mmap/fb_fillrect 等
screen_base void __iomem * 显存虚拟地址基址,mmap 时映射给用户空间
screen_size unsigned long 显存大小(字节),决定 mmap 可映射范围
device struct device * 内核设备模型指针,用于 sysfs 和电源管理

2.6 注册流程代码示例

以下为帧缓冲驱动 probe 函数中注册设备的典型代码片段:

static int myfb_probe(struct platform_device *pdev)
{
    struct fb_info *fbi;
    struct myfb_par *par;
    int ret;

    // 1. 分配 fb_info 和私有数据
    fbi = framebuffer_alloc(sizeof(struct myfb_par), &pdev->dev);
    if (!fbi)
        return -ENOMEM;

    par = fbi->par;
    par->dev = &pdev->dev;

    // 2. 填充固定参数
    strcpy(fbi->fix.id, "myfb");
    fbi->fix.smem_start = par->fb_phys;
    fbi->fix.smem_len = par->fb_size;
    fbi->fix.type = FB_TYPE_PACKED_PIXELS;
    fbi->fix.visual = FB_VISUAL_TRUECOLOR;
    fbi->fix.line_length = par->xres * (par->bpp / 8);

    // 3. 填充可变参数
    fbi->var.xres = par->xres;
    fbi->var.yres = par->yres;
    fbi->var.bits_per_pixel = par->bpp;
    fbi->var.activate = FB_ACTIVATE_NOW;

    // 4. 设置操作函数集
    fbi->fbops = &myfb_ops;

    // 5. 设置显存基址
    fbi->screen_base = ioremap_wc(par->fb_phys, par->fb_size);
    fbi->screen_size = par->fb_size;

    // 6. 注册帧缓冲设备
    ret = register_framebuffer(fbi);
    if (ret) {
        dev_err(&pdev->dev, "register_framebuffer failed: %d\n", ret);
        iounmap(fbi->screen_base);
        framebuffer_release(fbi);
        return ret;
    }

    platform_set_drvdata(pdev, fbi);
    dev_info(&pdev->dev, "myfb registered as /dev/fb%d\n", fbi->node);

    return 0;
}
调试要点: 注册成功后,fbi->node 字段保存了分配的次设备号。通过 cat /proc/fb 可查看当前系统所有已注册的帧缓冲设备列表。

2.7 注销流程

驱动卸载时,需调用 unregister_framebuffer() 释放设备节点,并调用 iounmap() 和 framebuffer_release() 清理资源:

static int myfb_remove(struct platform_device *pdev)
{
    struct fb_info *fbi = platform_get_drvdata(pdev);

    unregister_framebuffer(fbi);   // 移除设备节点
    iounmap(fbi->screen_base);     // 取消映射
    framebuffer_release(fbi);      // 释放 fb_info

    return 0;
}
注意: 注销前必须确保没有用户空间进程持有 /dev/fbN 的文件描述符,否则 unregister_framebuffer() 会返回 -EBUSY。调试时可用 lsof /dev/fb0 检查占用进程。