4. 帧缓冲驱动实战1:基于QEMU虚拟平台编写最简单的帧缓冲驱动、实现fb_fillrect/fb_copyarea/fb_imageblit
本章是帧缓冲驱动开发的第一个实战环节。我们将基于QEMU虚拟平台,从零编写一个最简单的帧缓冲驱动,并完整实现 fb_fillrect、fb_copyarea 和 fb_imageblit 三个核心加速回调。通过本章学习,你将掌握帧缓冲驱动的注册流程、数据结构填充以及底层像素操作的实际编码方法。
4.1 帧缓冲驱动核心数据结构
Linux帧缓冲驱动的核心是 struct fb_info 结构体。驱动开发者需要填充该结构体中的 fb_ops、fb_var_screeninfo、fb_fix_screeninfo 以及屏幕缓冲区指针。下表列出了本次实战需要填充的关键字段:
| 结构体 | 关键字段 | 说明 |
|---|---|---|
fb_info |
fbops |
指向 fb_ops 结构体,包含所有操作回调 |
fb_info |
screen_base |
虚拟地址,指向帧缓冲内存 |
fb_info |
screen_size |
帧缓冲内存大小(字节) |
fb_var_screeninfo |
xres, yres |
可见分辨率:800×600 |
fb_var_screeninfo |
bits_per_pixel |
32 bpp(每个像素32位) |
fb_fix_screeninfo |
smem_start |
帧缓冲物理地址(QEMU中为0x4C000000) |
fb_fix_screeninfo |
line_length |
一行像素的字节数:800 × 4 = 3200 |
4.2 驱动注册与注销流程
帧缓冲驱动的生命周期管理遵循标准的平台驱动模型。在QEMU虚拟平台中,我们使用 platform_driver 接口进行注册。以下是驱动注册和注销的完整步骤:
- 分配 fb_info:调用
framebuffer_alloc()分配struct fb_info实例。 - 填充固定信息:设置
fb_fix_screeninfo,包括物理地址、行长度、内存大小。 - 填充可变信息:设置
fb_var_screeninfo,包括分辨率、像素位深、时序参数。 - 设置操作回调:将
fb_ops结构体赋值给fb_info->fbops。 - 分配帧缓冲内存:使用
dma_alloc_coherent()或ioremap()映射物理内存。 - 注册帧缓冲设备:调用
register_framebuffer()将设备注册到系统。 - 注销时:先调用
unregister_framebuffer(),再释放内存和framebuffer_release()。
0x4C000000,大小为 800 × 600 × 4 = 1,920,000 字节。驱动中需要使用 ioremap_wc() 映射该物理地址以获得虚拟地址。
4.3 核心加速回调实现
帧缓冲驱动的三个核心加速回调 fb_fillrect、fb_copyarea、fb_imageblit 负责在帧缓冲内存上执行高效的像素操作。Linux内核提供了通用的辅助函数 sys_fillrect、sys_copyarea、sys_imageblit,它们基于CPU逐像素操作,适用于大多数简单场景。本次实战直接使用这些通用实现。
| 回调函数 | 功能描述 | 内核辅助函数 | 时间复杂度 |
|---|---|---|---|
fb_fillrect |
用指定颜色填充矩形区域 | sys_fillrect |
O(width × height) |
fb_copyarea |
将帧缓冲中一块区域拷贝到另一块区域 | sys_copyarea |
O(width × height) |
fb_imageblit |
将位图数据(单色或彩色)渲染到帧缓冲 | sys_imageblit |
O(width × height) |
4.4 完整驱动代码实现
以下是基于QEMU vexpress-a9平台的帧缓冲驱动完整代码。该驱动实现了 fb_fillrect、fb_copyarea 和 fb_imageblit 三个回调,并使用了内核提供的通用辅助函数。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#define VFB_WIDTH 800
#define VFB_HEIGHT 600
#define VFB_BPP 32
#define VFB_PHYS 0x4C000000
#define VFB_SIZE (VFB_WIDTH * VFB_HEIGHT * (VFB_BPP / 8))
static struct fb_info *vfb_info;
static void __iomem *vfb_base;
/* fb_fillrect 回调实现 */
static void vfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
{
sys_fillrect(info, rect);
}
/* fb_copyarea 回调实现 */
static void vfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
{
sys_copyarea(info, area);
}
/* fb_imageblit 回调实现 */
static void vfb_imageblit(struct fb_info *info, const struct fb_image *image)
{
sys_imageblit(info, image);
}
static struct fb_ops vfb_ops = {
.owner = THIS_MODULE,
.fb_fillrect = vfb_fillrect,
.fb_copyarea = vfb_copyarea,
.fb_imageblit = vfb_imageblit,
.fb_check_var = fb_check_var,
.fb_set_par = fb_set_par,
.fb_blank = fb_blank,
};
static int vfb_probe(struct platform_device *pdev)
{
int ret;
/* 1. 分配 fb_info */
vfb_info = framebuffer_alloc(0, &pdev->dev);
if (!vfb_info)
return -ENOMEM;
/* 2. 映射帧缓冲物理内存 */
vfb_base = ioremap_wc(VFB_PHYS, VFB_SIZE);
if (!vfb_base) {
framebuffer_release(vfb_info);
return -ENOMEM;
}
/* 3. 填充可变信息 */
vfb_info->var.xres = VFB_WIDTH;
vfb_info->var.yres = VFB_HEIGHT;
vfb_info->var.xres_virtual = VFB_WIDTH;
vfb_info->var.yres_virtual = VFB_HEIGHT;
vfb_info->var.bits_per_pixel = VFB_BPP;
vfb_info->var.red.offset = 16;
vfb_info->var.red.length = 8;
vfb_info->var.green.offset = 8;
vfb_info->var.green.length = 8;
vfb_info->var.blue.offset = 0;
vfb_info->var.blue.length = 8;
vfb_info->var.transp.offset = 24;
vfb_info->var.transp.length = 8;
vfb_info->var.activate = FB_ACTIVATE_NOW;
/* 4. 填充固定信息 */
vfb_info->fix.smem_start = VFB_PHYS;
vfb_info->fix.smem_len = VFB_SIZE;
vfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
vfb_info->fix.visual = FB_VISUAL_TRUECOLOR;
vfb_info->fix.line_length = VFB_WIDTH * (VFB_BPP / 8);
strcpy(vfb_info->fix.id, "qemu_vfb");
/* 5. 设置操作回调 */
vfb_info->fbops = &vfb_ops;
vfb_info->screen_base = vfb_base;
vfb_info->screen_size = VFB_SIZE;
vfb_info->pseudo_palette = NULL;
vfb_info->flags = FBINFO_FLAG_DEFAULT;
/* 6. 注册帧缓冲设备 */
ret = register_framebuffer(vfb_info);
if (ret) {
iounmap(vfb_base);
framebuffer_release(vfb_info);
return ret;
}
dev_info(&pdev->dev, "QEMU VFB registered: %dx%d %dbpp\n",
VFB_WIDTH, VFB_HEIGHT, VFB_BPP);
return 0;
}
static int vfb_remove(struct platform_device *pdev)
{
unregister_framebuffer(vfb_info);
iounmap(vfb_base);
framebuffer_release(vfb_info);
return 0;
}
static struct platform_driver vfb_driver = {
.probe = vfb_probe,
.remove = vfb_remove,
.driver = {
.name = "qemu_vfb",
.owner = THIS_MODULE,
},
};
module_platform_driver(vfb_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux FB Driver Course");
MODULE_DESCRIPTION("QEMU Virtual Framebuffer Driver");
4.5 编译与测试验证
完成驱动编写后,需要在QEMU环境中编译并加载驱动进行验证。以下是完整的测试步骤:
- 编译驱动:将源码放入内核源码树的
drivers/video/fbdev/目录,配置内核菜单并编译为模块。 - 加载模块:在QEMU启动的内核中执行
insmod qemu_vfb.ko。 - 验证设备节点:检查
/dev/fb0是否存在,使用fbset查看分辨率信息。 - 测试填充功能:使用
dd if=/dev/zero of=/dev/fb0清屏,观察QEMU显示窗口是否变黑。 - 测试加速回调:运行
fb-test或fbtest工具,验证矩形填充、区域拷贝和位图显示功能。
-device VGA 或 -display sdl;2) 帧缓冲物理地址 0x4C000000 是否与QEMU内存布局一致;3) 驱动中 line_length 计算是否正确(800×4=3200)。
4.6 性能对比与优化方向
使用内核通用辅助函数 sys_fillrect、sys_copyarea、sys_imageblit 虽然实现简单,但性能受限于CPU逐像素操作。下表对比了通用实现与硬件加速实现的性能差异:
| 实现方式 | 800×600全屏填充 | 100×100区域拷贝 | 32×32位图渲染 |
|---|---|---|---|
| 通用CPU实现 | ~15 ms | ~0.3 ms | ~0.1 ms |
| 硬件DMA加速 | ~0.5 ms | ~0.01 ms | ~0.005 ms |
| 优化倍数 | 30× | 30× | 20× |
4.7 本章小结
本章通过QEMU虚拟平台实战,完成了以下关键任务:
- 理解了帧缓冲驱动核心数据结构
fb_info、fb_ops、fb_var_screeninfo和fb_fix_screeninfo的填充方法。 - 掌握了平台驱动模型中帧缓冲设备的注册与注销流程。
- 实现了
fb_fillrect、fb_copyarea和fb_imageblit三个核心加速回调,并使用了内核通用辅助函数。 - 完成了驱动的编译、加载和基本功能验证。
- 对比了CPU通用实现与硬件加速的性能差异,明确了后续优化方向。
fb_fillrect 实现改为使用 memset_io 进行优化,并对比性能差异。思考在什么场景下硬件加速是必须的?