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 → 注册到系统。以下为典型流程:
- 分配 fb_info 结构体:调用
framebuffer_alloc()分配struct fb_info及其私有数据。 - 填充 fb_info 字段:设置
var(可变参数)、fix(固定参数)、fbops(操作函数集)、screen_base(显存基址)、screen_size(显存大小)。 - 注册帧缓冲设备:调用
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 架构图
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 检查占用进程。