1. 帧缓冲基础:帧缓冲概念、显示子系统架构、帧缓冲设备文件(/dev/fb0)
1.1 帧缓冲核心概念
帧缓冲(Framebuffer)是显示子系统中位于显存中的一块连续内存区域,用于存储屏幕上一帧完整的像素数据。在嵌入式Linux系统中,帧缓冲是内核向用户空间暴露显示能力的最基础接口,它屏蔽了底层显示控制器的硬件差异,使应用程序可以直接通过读写内存来更新屏幕内容。
关键定义:帧缓冲是一个内存映射的像素数据缓冲区,每个像素点对应一个或多个字节的颜色/深度/透明度信息。显示控制器以固定刷新率(如60Hz)从该缓冲区读取数据并转换为显示信号。
| 概念 | 说明 | 典型值/示例 |
|---|---|---|
| 像素格式 | 每个像素的位深度与颜色分量排列方式 | RGB565 (16bpp)、ARGB8888 (32bpp) |
| 分辨率 | 屏幕在水平和垂直方向上的像素数量 | 1024×600、1920×1080 |
| 行跨度 | 一行像素占用的字节数(含对齐填充) | stride = width × bpp / 8 + padding |
| 显存大小 | 帧缓冲占用的总内存空间 | stride × height |
| 刷新率 | 显示控制器每秒从帧缓冲读取并发送数据的次数 | 60 Hz、50 Hz |
1.2 显示子系统整体架构
嵌入式Linux显示子系统采用分层设计,从硬件到用户空间依次为:显示控制器硬件 → 内核帧缓冲驱动 → fbdev核心层 → 设备文件接口 → 用户空间应用程序。每一层都封装了特定的职责,上层无需关心下层实现细节。
架构要点:帧缓冲驱动位于内核空间,直接操作显示控制器寄存器;fbdev核心层提供统一的注册/注销/文件操作接口;用户空间通过
/dev/fb0设备文件以mmap方式直接映射显存,实现零拷贝像素更新。
1.3 帧缓冲设备文件 /dev/fb0
在Linux系统中,帧缓冲设备被抽象为字符设备文件,主设备号为29,次设备号从0开始递增。系统中有几个帧缓冲设备,就会生成几个/dev/fbN文件(N=0,1,2,...)。
| 设备文件 | 主设备号 | 次设备号 | 典型用途 |
|---|---|---|---|
| /dev/fb0 | 29 | 0 | 主显示屏幕(LCD/HDMI) |
| /dev/fb1 | 29 | 1 | 第二显示屏幕或叠加层 |
| /dev/fb2 | 29 | 2 | 硬件光标层或辅助显示 |
1.3.1 设备文件操作接口
应用程序通过标准的文件操作函数与帧缓冲设备交互,核心操作包括:
- open() — 打开设备文件,获取文件描述符
- ioctl() — 获取/设置显示参数(分辨率、位深、刷新率等)
- mmap() — 将帧缓冲显存映射到用户空间地址,实现直接像素读写
- read()/write() — 按字节流读写帧缓冲数据(效率低于mmap)
- close() — 关闭设备文件,释放资源
/* 帧缓冲设备打开与信息获取示例 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
int main(void)
{
int fbfd = open("/dev/fb0", O_RDWR);
if (fbfd == -1) {
perror("open /dev/fb0");
exit(EXIT_FAILURE);
}
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
/* 获取可变参数:分辨率、位深、虚拟分辨率 */
if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
perror("FBIOGET_VSCREENINFO");
close(fbfd);
exit(EXIT_FAILURE);
}
/* 获取固定参数:显存地址、行跨度、显存大小 */
if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
perror("FBIOGET_FSCREENINFO");
close(fbfd);
exit(EXIT_FAILURE);
}
printf("分辨率: %d x %d, 位深: %d bpp\n",
vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("显存大小: %lu bytes, 行跨度: %u bytes\n",
finfo.smem_len, finfo.line_length);
/* 映射显存到用户空间 */
size_t screensize = finfo.smem_len;
char *fbp = (char *)mmap(NULL, screensize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fbfd, 0);
if (fbp == MAP_FAILED) {
perror("mmap");
close(fbfd);
exit(EXIT_FAILURE);
}
/* 在屏幕中心画一个白色像素点 (RGB565格式) */
int x = vinfo.xres / 2;
int y = vinfo.yres / 2;
long location = (x + y * vinfo.xres) * (vinfo.bits_per_pixel / 8);
*(unsigned short *)(fbp + location) = 0xFFFF; /* 白色 */
munmap(fbp, screensize);
close(fbfd);
return 0;
}
1.3.2 关键ioctl命令
| ioctl命令 | 数据结构 | 功能描述 |
|---|---|---|
| FBIOGET_VSCREENINFO | struct fb_var_screeninfo | 获取当前可变显示参数(分辨率、位深、时序等) |
| FBIOPUT_VSCREENINFO | struct fb_var_screeninfo | 设置新的显示参数(需硬件支持) |
| FBIOGET_FSCREENINFO | struct fb_fix_screeninfo | 获取固定参数(显存地址、行跨度、类型等) |
| FBIOBLANK | int | 控制屏幕开关(FB_BLANK_UNBLANK / FB_BLANK_POWERDOWN) |
| FBIOGET_CON2FBMAP | struct fb_con2fbmap | 获取控制台到帧缓冲的映射关系 |
1.4 帧缓冲数据结构
内核使用两个核心结构体来描述帧缓冲设备:fb_var_screeninfo(可变参数)和fb_fix_screeninfo(固定参数)。应用程序通过ioctl获取这些信息后,才能正确操作帧缓冲。
1.5 帧缓冲工作流程
从应用程序发起像素更新到屏幕显示,帧缓冲的完整工作流程包含以下步骤:
- 打开设备:应用程序调用
open("/dev/fb0", O_RDWR)获取文件描述符。 - 查询参数:通过
FBIOGET_VSCREENINFO和FBIOGET_FSCREENINFO获取分辨率、位深、行跨度、显存大小等信息。 - 映射显存:使用
mmap(NULL, smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0)将内核显存映射到用户空间。 - 像素操作:根据像素格式和分辨率计算偏移,直接写入像素数据到映射内存。
- 同步等待:可选调用
FBIOPAN_DISPLAY或等待VSYNC信号,避免撕裂。 - 解除映射:使用
munmap()释放映射,close()关闭设备。
常见陷阱:
- 虚拟分辨率(xres_virtual / yres_virtual)可能大于可见分辨率,用于双缓冲或滚动显示。
- 行跨度(line_length)不一定等于 width × bpp/8,硬件可能要求对齐到4/8/16字节边界。
- mmap的偏移参数必须为页大小(4096字节)的整数倍,通常传0即可。
- 在嵌入式设备上,帧缓冲显存可能是物理连续内存(DMA内存),mmap后不可再使用malloc/free管理。
1.6 帧缓冲与显示控制器的交互
帧缓冲驱动在内核中注册struct fb_info结构体,该结构体包含操作函数集fb_ops,驱动通过register_framebuffer()向fbdev核心层注册设备。显示控制器通过DMA方式直接从帧缓冲内存读取数据,无需CPU干预。
1.7 本章小结
- 帧缓冲是显示子系统的核心抽象,提供内存级像素操作接口。
- 显示子系统采用分层架构:硬件 → 驱动 → fbdev核心 → 设备文件 → 用户空间。
/dev/fb0是主帧缓冲设备文件,通过mmap实现零拷贝显存访问。fb_var_screeninfo和fb_fix_screeninfo是操作帧缓冲的核心数据结构。- 显示控制器通过DMA直接从帧缓冲读取数据,CPU仅负责像素内容更新。