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:关联的硬件设备结构体。
Framebuffer 设备模型核心结构 struct fb_info fb_ops (操作函数集) fix (固定参数) var (可变参数) screen_base (虚拟地址) screen_size (显存大小) device (硬件设备) 说明 fb_info 是驱动注册的核心对象,通过 register_framebuffer() 注册到内核。 用户空间通过 /dev/fbX 设备文件与 fb_info 交互。

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;
}
mmap 显存映射流程 用户空间进程 内核空间 (fbdev) 物理显存 (VRAM) 1. open("/dev/fb0") 2. mmap(MAP_SHARED) 3. fb_mmap() 建立页表 4. 返回用户空间虚拟地址 mmap 映射后,用户空间直接读写显存,无需系统调用,性能极高。

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) 无
注意: 虚拟分辨率(xres_virtual / yres_virtual)通常大于或等于可见分辨率。通过设置更大的虚拟分辨率,可以实现双缓冲或多缓冲,结合 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)的基础。