2. 帧缓冲(Framebuffer)基础
帧缓冲(Framebuffer)是Linux内核显示子系统的基石,它为上层应用提供了一个与硬件无关的、直接操作显存的抽象层。本章将深入剖析Framebuffer的设备模型、核心接口、以及用户态与内核态之间高效的数据传输机制——mmap映射。
2.1 Framebuffer设备模型
在Linux内核中,Framebuffer被抽象为一个字符设备,主设备号为29,次设备号从0开始递增(如/dev/fb0、/dev/fb1)。其核心数据结构是fb_info,它串联了硬件操作函数、固定参数、可变参数以及底层驱动。
struct fb_info 是Framebuffer驱动的中枢,包含以下关键成员:
- fbops:指向
struct fb_ops的操作函数集,包括open、release、ioctl、mmap、fb_check_var、fb_set_par等。 - fix:
struct fb_fix_screeninfo,描述屏幕的固定属性(如显存物理地址、长度、行宽)。 - var:
struct fb_var_screeninfo,描述屏幕的可变属性(如分辨率、位深、像素格式)。 - screen_base:显存在内核空间的虚拟地址。
- screen_size:显存总大小。
- device:关联的硬件设备结构体。
2.2 fbdev接口与用户空间交互
fbdev(Framebuffer Device)是内核提供给用户空间的传统接口。应用程序通过标准的文件操作(open、ioctl、mmap、close)与Framebuffer设备通信。其中,ioctl是获取和设置显示参数的核心通道。
| ioctl 命令 | 操作对象 | 功能描述 |
|---|---|---|
FBIOGET_FSCREENINFO |
struct fb_fix_screeninfo |
获取固定参数(显存地址、行宽、内存类型等) |
FBIOGET_VSCREENINFO |
struct fb_var_screeninfo |
获取可变参数(分辨率、位深、像素格式等) |
FBIOPUT_VSCREENINFO |
struct fb_var_screeninfo |
设置可变参数(触发模式切换) |
FBIOGET_VBLANK |
struct fb_vblank |
获取垂直消隐状态,用于同步刷新 |
FBIOPAN_DISPLAY |
struct fb_var_screeninfo |
实现硬件滚动(panning) |
FBIOPUT_VSCREENINFO 之前,建议先调用 FBIOGET_VSCREENINFO 获取当前参数,然后修改需要的字段,最后再写回。这样可以避免因未初始化字段导致驱动拒绝切换。
2.3 mmap操作与显存映射
mmap是Framebuffer性能的关键。它允许用户空间进程直接将显存映射到进程的虚拟地址空间,从而避免昂贵的read/write系统调用和数据拷贝。映射的核心是内核驱动的fb_mmap函数,它负责将物理显存映射到用户空间。
// 用户空间 mmap 示例
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
int main() {
int fbfd = open("/dev/fb0", O_RDWR);
if (fbfd < 0) { perror("open"); return -1; }
struct fb_fix_screeninfo fix;
struct fb_var_screeninfo var;
// 获取固定和可变参数
ioctl(fbfd, FBIOGET_FSCREENINFO, &fix);
ioctl(fbfd, FBIOGET_VSCREENINFO, &var);
// 计算显存大小:行宽 * 垂直分辨率
size_t screensize = fix.line_length * var.yres_virtual;
// 将显存映射到用户空间
char *fbp = (char *)mmap(NULL, screensize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fbfd, 0);
if (fbp == MAP_FAILED) { perror("mmap"); close(fbfd); return -1; }
// 直接操作显存:将屏幕左上角像素设为红色 (RGB565)
unsigned short *pixel = (unsigned short *)(fbp + 0);
*pixel = 0xF800; // RGB565 红色
munmap(fbp, screensize);
close(fbfd);
return 0;
}
2.4 关键数据结构对比
理解fb_fix_screeninfo和fb_var_screeninfo的区别是掌握Framebuffer的关键。前者描述硬件固有属性,后者描述当前工作模式。
| 属性 | fb_fix_screeninfo (固定) | fb_var_screeninfo (可变) |
|---|---|---|
| 分辨率 | 无 | xres, yres (可见区域); xres_virtual, yres_virtual (虚拟区域) |
| 位深 | 无 | bits_per_pixel |
| 像素格式 | 无 | red, green, blue, transp (偏移和长度) |
| 显存地址 | smem_start (物理地址) | 无 |
| 显存大小 | smem_len | 无 |
| 行宽 | line_length (字节) | 无 |
| 硬件加速 | accel (加速器ID) | 无 |
FBIOPAN_DISPLAY 实现无撕裂的页面切换。
2.5 驱动侧实现要点
在内核驱动中,实现mmap的关键是填充fb_ops中的fb_mmap函数指针。对于大多数简单Framebuffer驱动,可以直接使用内核提供的通用实现fb_deferred_io_mmap或fb_mmap。但对于需要自定义内存映射(如IO内存、DMA缓冲区)的硬件,驱动必须提供自己的实现。
// 驱动侧 fb_mmap 典型实现
static int myfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
// 获取物理显存起始地址和大小
unsigned long start = info->fix.smem_start;
unsigned long size = info->fix.smem_len;
// 设置映射属性:禁止缓存,保证显存访问一致性
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
// 将物理显存映射到用户空间
return vm_iomap_memory(vma, start, size);
}
// 注册 fb_ops
static struct fb_ops myfb_ops = {
.owner = THIS_MODULE,
.fb_open = myfb_open,
.fb_release = myfb_release,
.fb_mmap = myfb_mmap,
.fb_check_var = myfb_check_var,
.fb_set_par = myfb_set_par,
.fb_fillrect = cfb_fillrect, // 通用矩形填充
.fb_copyarea = cfb_copyarea, // 通用区域拷贝
.fb_imageblit = cfb_imageblit, // 通用位块传输
};
- pgprot_writecombine():设置页表属性为写组合(Write Combine),提高显存写入性能,同时避免缓存一致性问题。
- vm_iomap_memory():内核辅助函数,用于将IO内存映射到用户空间。
- cfb_* 函数:内核提供的通用软件渲染函数,当硬件不支持2D加速时使用。
通过本章的学习,你应该掌握了Framebuffer的设备模型、用户空间接口、mmap映射机制以及驱动侧的实现要点。这是理解后续复杂显示架构(如DRM/KMS)的基础。