4. FBDEV驱动实战
本节我们不再停留在概念层面,而是从零编写一个可加载、可测试的虚拟帧缓冲驱动。所谓“虚拟”,是指它不绑定任何真实显示控制器,而是用内核态内存模拟一块显存,通过 fb_ops 回调把这块内存暴露成标准 /dev/fbX 设备。这样既能完整走通 FBDEV 驱动的注册流程,又不会因硬件差异干扰学习重点。
核心目标:实现
fb_ops 中最关键的 fb_fillrect、fb_copyarea、fb_imageblit 三个回调,完成 register_framebuffer() 注册,并通过 fbset、fbv 或直接写 /dev/fb0 验证显示效果。
4.1 虚拟帧缓冲的整体结构
一个 FBDEV 驱动的最小骨架由三部分组成:帧缓冲信息结构 struct fb_info、显存缓冲区、以及 fb_ops 操作集。内核通过 fb_info 统一管理分辨率、色深、显存地址和回调函数。下图展示了虚拟帧缓冲从模块加载到用户空间访问的完整数据通路。
4.2 关键回调的实现要点
FBDEV 核心层并不直接操作显存,而是把绘制请求转交给驱动提供的 fb_ops。对于虚拟帧缓冲,我们只需实现三个最基础的加速回调,其余可留空或使用内核默认实现。
| 回调函数 | 触发场景 | 虚拟驱动实现策略 |
|---|---|---|
fb_fillrect |
控制台清屏、填充色块 | 按色深逐像素写入 screen_base |
fb_copyarea |
滚动、区域拷贝 | 逐行 memcpy,注意重叠方向 |
fb_imageblit |
显示 Logo、文字位图 | 解析 monochrome 位图并展开为像素 |
fb_set_par |
fbset 修改分辨率 | 虚拟驱动可返回 0 或重新分配显存 |
fb_blank |
DPMS 息屏 | 虚拟驱动可空实现 |
下面给出 fb_fillrect 的核心实现片段,假设色深为 32bpp,info->screen_base 指向显存起始地址:
static void vfb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
u32 __iomem *dst;
u32 color = rect->color;
int y;
for (y = 0; y < rect->height; y++) {
dst = (u32 __iomem *)info->screen_base
+ (rect->dy + y) * info->fix.line_length / 4
+ rect->dx;
memset32(dst, color, rect->width);
}
}
memset32 是内核提供的按 32 位填充函数,比逐字节循环快得多。若色深为 16bpp,应改用 memset16,并相应调整指针类型。
4.3 模块加载与注册流程
驱动以模块形式加载,入口函数完成 fb_info 的分配、参数填充和注册。退出函数则反向释放资源。典型流程如下:
- 调用
framebuffer_alloc()分配fb_info结构。 - 设置
var中的分辨率、色深、虚拟分辨率;设置fix中的显存长度和行长度。 - 使用
vmalloc()或__get_free_pages()分配显存,并赋给screen_base。 - 填充
fb_ops结构体,绑定回调函数。 - 调用
register_framebuffer()注册,成功后出现/dev/fbX。 - 模块卸载时调用
unregister_framebuffer()、释放显存和fb_info。
static int __init vfb_init(void)
{
struct fb_info *info;
int ret;
info = framebuffer_alloc(0, NULL);
if (!info)
return -ENOMEM;
info->var.xres = 640;
info->var.yres = 480;
info->var.bits_per_pixel = 32;
info->var.xres_virtual = 640;
info->var.yres_virtual = 480;
info->fix.smem_len = 640 * 480 * 4;
info->fix.line_length = 640 * 4;
info->screen_base = vmalloc(info->fix.smem_len);
if (!info->screen_base) {
framebuffer_release(info);
return -ENOMEM;
}
info->fbops = &vfb_ops;
ret = register_framebuffer(info);
if (ret) {
vfree(info->screen_base);
framebuffer_release(info);
return ret;
}
return 0;
}
4.4 加载测试与常见误区
编译出 vfb.ko 后,使用 insmod vfb.ko 加载,通过 dmesg 查看注册日志,再用 ls /dev/fb* 确认设备节点。测试时可使用 fbset -i 查看参数,或用 fbv 显示图片,也可以直接向 /dev/fb0 写入数据观察变化。
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod vfb.ko
$ dmesg | tail -5
[ 1234.567] vfb: framebuffer registered, 640x480-32
$ ls /dev/fb*
/dev/fb0
$ sudo fbset -i
mode "640x480-32"
geometry 640 480 640 480 32
endmode
常见误区一:忘记设置
fix.line_length,导致内核按 0 计算偏移,画面错乱。误区二:显存用 kmalloc 分配但未考虑大分辨率,超过 4MB 时可能失败,应优先使用 vmalloc。误区三:fb_fillrect 中未按色深选择 memset32 或 memset16,造成颜色异常。误区四:卸载模块时未调用 unregister_framebuffer(),导致 /dev/fb0 残留。
复盘整个流程,虚拟帧缓冲驱动的价值在于用最小代码量打通 FBDEV 的注册、回调分发和用户接口三条链路。掌握它之后,再迁移到真实硬件驱动时,只需替换显存分配方式和回调中的硬件操作,整体框架完全一致。这也是后续理解 KMS 迁移时,能够清晰对比两种显示模型差异的重要基础。