3. Framebuffer设备操作:打开/关闭设备、ioctl控制命令与mmap映射显存

Framebuffer 设备在 Linux 系统中以字符设备文件的形式呈现,通常位于 /dev/fb0(主设备号 29,次设备号 0)。对 Framebuffer 的操作遵循“打开 → 获取信息 → 映射显存 → 读写像素 → 释放映射 → 关闭设备”的标准流程。本章将深入讲解这三个核心操作:设备文件的打开与关闭、通过 ioctl 系统调用控制显示参数、以及使用 mmap 将物理显存映射到用户空间。

3.1 设备文件的打开与关闭

Framebuffer 设备使用标准的 POSIX 文件 I/O 接口进行打开和关闭。打开设备时,必须指定正确的路径和访问模式;关闭设备则确保资源被释放,避免内存泄漏或设备占用。

操作 函数原型 关键参数 返回值
打开设备 int fd = open(const char *pathname, int flags); pathname: 设备路径,如 "/dev/fb0"
flags: 通常为 O_RDWR
成功返回文件描述符(≥0),失败返回 -1
关闭设备 int close(int fd); fd: 由 open() 返回的文件描述符 成功返回 0,失败返回 -1
实践要点:
  • 打开设备前应检查文件是否存在,使用 access("/dev/fb0", F_OK) 进行预检。
  • 如果只需要读取当前显示内容而不修改,可以使用 O_RDONLY 模式打开。
  • 关闭设备后,之前通过 mmap 映射的内存区域仍然有效,直到调用 munmap 释放。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    int fb_fd;
    
    // 打开 Framebuffer 设备
    fb_fd = open("/dev/fb0", O_RDWR);
    if (fb_fd == -1) {
        perror("Error: cannot open framebuffer device");
        exit(EXIT_FAILURE);
    }
    
    printf("Framebuffer device opened successfully (fd=%d)\n", fb_fd);
    
    // ... 执行其他操作 ...
    
    // 关闭设备
    if (close(fb_fd) == -1) {
        perror("Error: cannot close framebuffer device");
        exit(EXIT_FAILURE);
    }
    
    printf("Framebuffer device closed.\n");
    return 0;
}

3.2 ioctl 控制命令:获取与设置显示参数

ioctl(Input/Output Control)是 Linux 设备驱动中用于执行特殊控制操作的系统调用。对于 Framebuffer 设备,ioctl 是获取和修改显示参数(如分辨率、像素格式、刷新率等)的核心接口。常用的 ioctl 命令定义在头文件 <linux/fb.h> 中。

3.2.1 核心数据结构

Framebuffer 的显示参数通过两个关键结构体描述:

结构体 用途 关键字段
struct fb_fix_screeninfo 固定屏幕信息(不可修改) smem_start: 物理显存起始地址
smem_len: 显存总大小(字节)
line_length: 一行像素的字节数
type: 显示类型(如 FB_TYPE_PACKED_PIXELS)
struct fb_var_screeninfo 可变屏幕信息(可修改) xres: 可见水平分辨率
yres: 可见垂直分辨率
bits_per_pixel: 每像素位数(bpp)
red/green/blue/transp: 颜色通道偏移与长度

3.2.2 常用 ioctl 命令

命令宏 功能 参数类型 方向
FBIOGET_VSCREENINFO 获取当前可变屏幕信息 struct fb_var_screeninfo * 读
FBIOPUT_VSCREENINFO 设置可变屏幕信息(修改分辨率/色深) struct fb_var_screeninfo * 写
FBIOGET_FSCREENINFO 获取固定屏幕信息 struct fb_fix_screeninfo * 读
FBIOBLANK 控制屏幕开关(亮屏/黑屏) int * 写
⚠️ 重要提示: 使用 FBIOPUT_VSCREENINFO 修改分辨率时,必须确保新参数与硬件能力匹配。修改后,显存大小可能发生变化,需要重新进行 mmap 映射。建议在修改前先获取当前参数,修改后再次获取以确认生效。
#include <linux/fb.h>
#include <sys/ioctl.h>

struct fb_var_screeninfo var_info;
struct fb_fix_screeninfo fix_info;

// 获取可变屏幕信息
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var_info) == -1) {
    perror("Error reading variable screen info");
    close(fb_fd);
    exit(EXIT_FAILURE);
}

printf("Current resolution: %dx%d, %d bpp\n",
       var_info.xres, var_info.yres, var_info.bits_per_pixel);

// 获取固定屏幕信息
if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &fix_info) == -1) {
    perror("Error reading fixed screen info");
    close(fb_fd);
    exit(EXIT_FAILURE);
}

printf("Framebuffer memory: %lu bytes, line length: %u bytes\n",
       fix_info.smem_len, fix_info.line_length);

// 示例:修改分辨率为 800x600,32bpp
var_info.xres = 800;
var_info.yres = 600;
var_info.bits_per_pixel = 32;

if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &var_info) == -1) {
    perror("Error setting variable screen info");
    close(fb_fd);
    exit(EXIT_FAILURE);
}

3.3 mmap 映射显存:用户空间直接操作帧缓冲区

Framebuffer 的物理显存位于内核空间,用户程序无法直接访问。通过 mmap 系统调用,可以将物理显存映射到用户进程的虚拟地址空间,从而实现对帧缓冲区的直接读写操作。

3.3.1 mmap 函数原型与参数

参数 说明 典型值
addr 映射起始地址(建议设为 NULL,由内核选择) NULL
length 映射长度(字节),通常为 fix_info.smem_len fix_info.smem_len
prot 内存保护标志 PROT_READ | PROT_WRITE
flags 映射类型标志 MAP_SHARED(必须,保证多进程共享)
fd 文件描述符(Framebuffer 设备) fb_fd
offset 文件偏移量,通常为 0 0

3.3.2 映射与解除映射

#include <sys/mman.h>

unsigned char *fb_mem = NULL;
long screensize = 0;

// 计算显存大小(字节)
screensize = fix_info.smem_len;

// 映射显存到用户空间
fb_mem = (unsigned char *)mmap(NULL, screensize,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED, fb_fd, 0);
if (fb_mem == MAP_FAILED) {
    perror("Error: mmap failed");
    close(fb_fd);
    exit(EXIT_FAILURE);
}

printf("Framebuffer mapped at address: %p\n", fb_mem);

// 使用 fb_mem 进行像素操作...

// 解除映射
if (munmap(fb_mem, screensize) == -1) {
    perror("Error: munmap failed");
    close(fb_fd);
    exit(EXIT_FAILURE);
}
⚠️ 常见错误与避坑:
  • 映射长度错误: 使用 fix_info.smem_len 而非 var_info.xres * var_info.yres * (var_info.bits_per_pixel / 8),因为显存可能包含对齐填充字节。
  • 忘记 MAP_SHARED: 如果使用 MAP_PRIVATE,写入操作不会同步到物理显存,屏幕不会更新。
  • 权限不足: 打开设备时使用 O_RDWR,否则 mmap 的 PROT_WRITE 会失败。
  • 修改分辨率后重新映射: 调用 FBIOPUT_VSCREENINFO 后,显存布局可能改变,必须 munmap 旧映射并重新 mmap。

3.4 完整操作流程与 SVG 架构图

下图展示了 Framebuffer 设备从打开到关闭的完整操作流程,以及用户空间与内核空间之间的数据交互关系。

Framebuffer 设备操作完整流程 用户空间 (User Space) 内核空间 (Kernel Space) open("/dev/fb0") ioctl(FBIOGET_*) mmap() 映射显存 读写像素数据 munmap() + close() fb_open() 驱动入口 fb_ioctl() 参数校验 物理显存 (VRAM) LCD 控制器刷新 fb_release() 释放 mmap 建立页表 直接映射物理地址

3.5 综合示例:获取信息并映射显存

以下代码将前面三个操作整合在一起,展示一个完整的 Framebuffer 初始化流程:打开设备、获取固定和可变信息、计算显存大小、映射显存,并在最后释放资源。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

int main() {
    int fb_fd;
    struct fb_fix_screeninfo fix_info;
    struct fb_var_screeninfo var_info;
    unsigned char *fb_mem = NULL;
    long screensize;

    // 1. 打开设备
    fb_fd = open("/dev/fb0", O_RDWR);
    if (fb_fd == -1) {
        perror("open");
        return 1;
    }

    // 2. 获取固定信息
    if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &fix_info)) {
        perror("FBIOGET_FSCREENINFO");
        close(fb_fd);
        return 1;
    }

    // 3. 获取可变信息
    if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var_info)) {
        perror("FBIOGET_VSCREENINFO");
        close(fb_fd);
        return 1;
    }

    printf("Resolution: %dx%d, %d bpp\n",
           var_info.xres, var_info.yres, var_info.bits_per_pixel);
    printf("Framebuffer size: %lu bytes, line length: %u bytes\n",
           fix_info.smem_len, fix_info.line_length);

    // 4. 计算并映射显存
    screensize = fix_info.smem_len;
    fb_mem = mmap(NULL, screensize, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fb_fd, 0);
    if (fb_mem == MAP_FAILED) {
        perror("mmap");
        close(fb_fd);
        return 1;
    }

    // 5. 此处可进行像素操作...

    // 6. 清理
    munmap(fb_mem, screensize);
    close(fb_fd);
    printf("Framebuffer closed successfully.\n");
    return 0;
}
💡 调试技巧:
  • 使用 cat /sys/class/graphics/fb0/virtual_size 查看虚拟分辨率。
  • 使用 fbset 命令查看和修改当前 Framebuffer 参数(需要 root 权限)。
  • 如果 mmap 失败,检查 dmesg 内核日志,确认驱动是否支持 mmap 操作。