3、帧缓冲设备文件与用户空间接口:/dev/fb*设备节点、mmap映射原理、ioctl控制命令
帧缓冲设备在 Linux 用户空间表现为 /dev/fb* 设备节点。应用程序通过标准的文件操作(open、close、read、write)以及内存映射(mmap)和控制命令(ioctl)来访问显示缓冲区。本章深入剖析这些用户空间接口的实现原理与使用方法。
核心要点:帧缓冲驱动向用户空间暴露了三个关键接口——设备节点用于文件操作,mmap 用于直接访问显存,ioctl 用于查询/设置显示参数。理解这三者的协同工作是掌握 fbdev 编程的基础。
3.1 /dev/fb* 设备节点
系统启动后,帧缓冲驱动会注册一个或多个设备节点,通常命名为 /dev/fb0、/dev/fb1 等。主设备号为 29,次设备号从 0 开始递增。
| 设备节点 | 主设备号 | 次设备号 | 典型用途 |
|---|---|---|---|
/dev/fb0 |
29 | 0 | 主显示屏幕(LCD、HDMI) |
/dev/fb1 |
29 | 1 | 第二显示设备(扩展屏、双屏) |
/dev/fb2 |
29 | 2 | 虚拟帧缓冲或测试设备 |
设备节点的创建由驱动在 probe 阶段通过 register_framebuffer() 函数自动完成。用户空间可以通过 ls -l /dev/fb* 查看当前系统中的帧缓冲设备。
提示:在嵌入式系统中,如果只有一个显示设备,通常只存在
/dev/fb0。某些 SoC 支持多个显示控制器,会注册多个 fb 设备节点。
3.2 mmap 映射原理
帧缓冲的核心优势在于允许用户空间通过 mmap() 系统调用将内核中的显存直接映射到进程的虚拟地址空间。这样应用程序可以像操作普通内存数组一样直接读写显存,无需每次通过 read/write 系统调用,极大提升了绘图性能。
3.2.1 mmap 映射流程
- 打开设备:
fd = open("/dev/fb0", O_RDWR); - 获取固定信息:
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);获取 smem_start(物理显存地址)和 smem_len(显存长度)。 - 获取可变信息:
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);获取分辨率、位深、行宽等参数。 - 执行 mmap:
fbp = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - 直接操作显存:通过
fbp指针读写像素数据。
3.2.2 显存布局与像素操作
映射成功后,显存按线性方式排列。对于 32 位色(ARGB8888),每个像素占用 4 字节,排列顺序为 B、G、R、A(小端模式)。
// 32位色像素操作示例
unsigned int *fbp = (unsigned int *)mmap_ptr;
int x = 100, y = 200;
int screen_width = 1024;
int bytes_per_pixel = 4;
// 计算像素偏移
int offset = (y * screen_width + x) * bytes_per_pixel;
// 写入红色像素 (R=255, G=0, B=0, A=255)
fbp[offset / 4] = 0x00FF0000; // 小端: B=0x00, G=0x00, R=0xFF, A=0x00
注意:不同位深(16位、24位、32位)的像素格式不同。16位通常为 RGB565(5位R、6位G、5位B),32位通常为 ARGB8888。务必通过
FBIOGET_VSCREENINFO 获取当前像素格式后再操作。
3.3 ioctl 控制命令
ioctl 是用户空间与帧缓冲驱动交互的主要控制通道。通过不同的命令码,应用程序可以查询和设置显示参数。
3.3.1 核心 ioctl 命令列表
| 命令码 | 功能 | 数据结构 | 方向 |
|---|---|---|---|
FBIOGET_FSCREENINFO |
获取固定屏幕信息 | struct fb_fix_screeninfo |
读 |
FBIOPUT_FSCREENINFO |
设置固定屏幕信息 | struct fb_fix_screeninfo |
写 |
FBIOGET_VSCREENINFO |
获取可变屏幕信息 | struct fb_var_screeninfo |
读 |
FBIOPUT_VSCREENINFO |
设置可变屏幕信息 | struct fb_var_screeninfo |
写 |
FBIOGETCMAP |
获取颜色查找表 | struct fb_cmap |
读 |
FBIOPUTCMAP |
设置颜色查找表 | struct fb_cmap |
写 |
FBIOBLANK |
控制屏幕开关 | int |
写 |
3.3.2 FBIOPUT_VSCREENINFO 详解
FBIOPUT_VSCREENINFO 是最常用的设置命令,用于修改分辨率、位深、行宽、刷新率等参数。其数据结构 struct fb_var_screeninfo 包含以下关键字段:
struct fb_var_screeninfo {
__u32 xres; // 可见水平分辨率(像素)
__u32 yres; // 可见垂直分辨率(像素)
__u32 xres_virtual; // 虚拟水平分辨率(用于双缓冲)
__u32 yres_virtual; // 虚拟垂直分辨率
__u32 xoffset; // 虚拟到可见的水平偏移
__u32 yoffset; // 虚拟到可见的垂直偏移
__u32 bits_per_pixel; // 每像素位数(16/24/32)
__u32 grayscale; // 灰度标志
struct fb_bitfield red; // 红色通道位域
struct fb_bitfield green; // 绿色通道位域
struct fb_bitfield blue; // 蓝色通道位域
struct fb_bitfield transp; // 透明通道位域
__u32 nonstd; // 非标准像素格式标志
__u32 activate; // 激活模式(立即/下次刷新)
__u32 height; // 屏幕高度(mm)
__u32 width; // 屏幕宽度(mm)
__u32 accel_flags; // 硬件加速标志
__u32 pixclock; // 像素时钟(皮秒)
__u32 left_margin; // 行同步前肩
__u32 right_margin; // 行同步后肩
__u32 upper_margin; // 帧同步前肩
__u32 lower_margin; // 帧同步后肩
__u32 hsync_len; // 行同步脉冲长度
__u32 vsync_len; // 帧同步脉冲长度
__u32 sync; // 同步极性
__u32 vmode; // 视频模式(隔行/逐行)
__u32 rotate; // 旋转角度(0/90/180/270)
__u32 reserved[5]; // 保留字段
};
3.3.3 屏幕旋转实现
rotate 字段用于控制屏幕旋转。当驱动支持旋转时,设置该字段可以实现硬件级别的画面旋转,无需软件逐像素转换。
3.3.4 完整 ioctl 使用示例
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
int main() {
int fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
perror("open fb0");
return -1;
}
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
// 获取当前可变信息
if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo)) {
perror("FBIOGET_VSCREENINFO");
close(fd);
return -1;
}
// 获取固定信息(显存地址、长度等)
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo)) {
perror("FBIOGET_FSCREENINFO");
close(fd);
return -1;
}
printf("分辨率: %dx%d, 位深: %d\n",
vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("显存地址: 0x%lx, 长度: %d bytes\n",
finfo.smem_start, finfo.smem_len);
// 设置旋转 90 度(如果驱动支持)
vinfo.rotate = 1; // 90度旋转
if (ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo)) {
perror("FBIOPUT_VSCREENINFO (rotate)");
// 如果不支持旋转,回退到正常模式
vinfo.rotate = 0;
ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
}
// 执行 mmap 映射
unsigned char *fbp = mmap(NULL, finfo.smem_len,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (fbp == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
// 在 (100, 100) 处画一个红色像素
int x = 100, y = 100;
int offset = (y * vinfo.xres + x) * (vinfo.bits_per_pixel / 8);
fbp[offset + 0] = 0x00; // B
fbp[offset + 1] = 0x00; // G
fbp[offset + 2] = 0xFF; // R
fbp[offset + 3] = 0x00; // A (如果32位)
munmap(fbp, finfo.smem_len);
close(fd);
return 0;
}
3.4 用户空间接口总结
| 接口类型 | 系统调用 | 主要用途 | 性能特点 |
|---|---|---|---|
| 文件操作 | open/close/read/write | 设备打开关闭、简单读写 | 低效,每次读写需系统调用 |
| 内存映射 | mmap/munmap | 直接访问显存、高效绘图 | 极高,无系统调用开销 |
| 控制命令 | ioctl | 查询/设置显示参数、旋转 | 中等,用于配置而非数据 |
最佳实践:在实际嵌入式应用中,通常组合使用 mmap 和 ioctl:先用 ioctl 获取/设置显示参数,再用 mmap 映射显存进行高效绘图。屏幕旋转优先尝试硬件旋转(通过 rotate 字段),如果驱动不支持,再考虑软件旋转方案。