【Linux】认识Framebuffer
以下内容来自【腾讯元宝】
Linux framebuffer 是一个强大且有趣的工具,它能让你直接与屏幕的像素“对话”。下面这份教程将带你系统地了解它。
📖 初识 Framebuffer
可以把 framebuffer(帧缓冲)想象成屏幕内容在内存里的一个“镜像”或“画布”。你向这块内存区域写入什么数据,屏幕上就会立刻显示对应的图像。在 Linux 系统中,这个“画布”通常以设备文件的形式出现,比如 /dev/fb0
(第一个显示设备)或 /dev/fb1
(第二个显示设备)。它的核心价值在于,无需依赖任何图形界面(如 X Window 或 Wayland),就能直接控制屏幕显示,这使得它在嵌入式系统、服务器或图形驱动开发中非常有用。
⚙️ 使用 Framebuffer 的步骤
使用 framebuffer 进行绘图,通常遵循以下标准流程,其核心关系如下图所示:
下面我们详细讲解每一步的具体操作。
1. 打开设备
这是第一步,就像你要作画得先进入画室一样。通过 open
系统调用打开 framebuffer 设备文件。
int fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb == -1) {perror("Error opening framebuffer device");exit(1);
}
2. 获取屏幕信息
在开始绘画前,你需要知道“画布”有多大,用什么颜料。通过 ioctl
获取这些信息。
#include <linux/fb.h> // 需要包含这个头文件struct fb_var_screeninfo vinfo; // 可变信息(如当前分辨率)
struct fb_fix_screeninfo finfo; // 固定信息(如显存物理地址)ioctl(fd_fb, FBIOGET_VSCREENINFO, &vinfo); // 获取可变信息
ioctl(fd_fb, FBIOGET_FSCREENINFO, &finfo); // 获取固定信息// 打印信息示例
printf("Resolution: %dx%d, %d bpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("Virtual resolution: %dx%d\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("Line length: %d bytes\n", finfo.line_length);
关键参数说明:
- xres, yres: 屏幕的物理分辨率。
- xres_virtual, yres_virtual: 虚拟分辨率。虚拟分辨率的高度(yres_virtual)可能大于物理分辨率的高度(yres),这通常是为了实现双缓冲(一页显示,另一页绘制),以减少闪烁。
- bits_per_pixel: 每个像素用多少位表示,常见有 16位(RGB565)、24位(RGB888)、32位(ARGB8888)。
- line_length: 一行像素在内存中占用的总字节数(通常 >= xres * bits_per_pixel / 8)。
3. 内存映射 (mmap)
显存是内核空间的内存,应用程序不能直接访问。mmap
函数可以将这块显存映射到应用程序的用户空间地址,这样你就可以像操作普通内存一样直接读写屏幕内容了。
// 计算需要映射的内存大小
// 注意:通常用虚拟分辨率计算,以适应双缓冲等情况
size_t screensize = vinfo.yres_virtual * finfo.line_length;// 执行映射
char *fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fbp == MAP_FAILED) {perror("Error mapping framebuffer device to memory");exit(1);
}
4. 绘制像素点
现在,你可以直接向映射的内存写入数据来画点了。关键在于根据不同的色彩深度正确计算像素位置和颜色值。
void draw_pixel(int x, int y, unsigned int color) {// 边界检查if (x >= vinfo.xres || y >= vinfo.yres) return;// 计算指定像素在映射内存中的位置// 注意:这里使用 finfo.line_length 来计算偏移量更准确long location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length;// 根据色彩深度写入颜色值switch (vinfo.bits_per_pixel) {case 16: { // RGB565unsigned short *pixel = (unsigned short *)(fbp + location);// 将24位RGB颜色(0xRRGGBB)转换为16位RGB565unsigned int r = (color >> 16) & 0xff;unsigned int g = (color >> 8) & 0xff;unsigned int b = color & 0xff;*pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);break;}case 24: // 可能会遇到,但存储方式可能因硬件而异case 32: { // 通常为ARGB8888或RGBA8888等,这里以简单处理为例unsigned int *pixel = (unsigned int *)(fbp + location);*pixel = color; // 直接写入颜色值break;}default:printf("Unsupported bpp: %d\n", vinfo.bits_per_pixel);}
}
5. 清理资源
绘图结束后,别忘记解除映射并关闭设备。
munmap(fbp, screensize);
close(fd_fb);
🎨 综合实例:绘制渐变图形
掌握了画点函数,就可以绘制更复杂的图形了。下面是一个简单的综合示例,它在屏幕上绘制一个渐变的色条。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>int main() {int fd_fb;struct fb_var_screeninfo vinfo;struct fb_fix_screeninfo finfo;char *fbp;// 1. 打开设备fd_fb = open("/dev/fb0", O_RDWR);if (fd_fb == -1) { perror("open"); exit(1); }// 2. 获取信息if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &vinfo)) { perror("ioctl vinfo"); exit(1); }if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &finfo)) { perror("ioctl finfo"); exit(1); }// 3. 内存映射size_t screensize = vinfo.yres_virtual * finfo.line_length;fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);if (fbp == MAP_FAILED) { perror("mmap"); exit(1); }// 4. 绘制渐变图形for (int y = 0; y < vinfo.yres; y++) {for (int x = 0; x < vinfo.xres; x++) {// 根据位置计算颜色,产生渐变效果unsigned int r = (x * 255) / vinfo.xres;unsigned int g = (y * 255) / vinfo.yres;unsigned int b = 100;unsigned int color = (r << 16) | (g << 8) | b; // 组合成24位RGB颜色// 调用画点函数// 注意:这里需要将上面代码中的 draw_pixel 函数实现放在这里或单独定义long location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length;if (vinfo.bits_per_pixel == 16) {/* ... RGB565 转换和写入 ... */} else if (vinfo.bits_per_pixel == 32) {*((unsigned int*)(fbp + location)) = color;}}}// 留点时间查看效果sleep(5);// 5. 清理资源munmap(fbp, screensize);close(fd_fb);return 0;
}
💡 实用技巧与注意事项
- 编译:编译上述代码时,需要链接数学库(如果使用了数学函数),例如
gcc -o fb_test fb_test.c
。 - 运行权限:运行程序通常需要 root 权限,因为直接访问了设备文件,例如
sudo ./fb_test
。 - 清屏:清屏操作很简单,通常用
memset
将映射的内存区域填充为单一颜色(如黑色)即可。memset(fbp, 0x00, screensize); // 填充为黑色
- 双缓冲:如果发现绘制过程中屏幕闪烁,可能是因为使用了双缓冲。在双缓冲机制中,绘制完成后,通常需要通过
ioctl
设置vinfo.yoffset
来切换当前显示页。
希望这篇教程能帮助你轻松上手 Linux framebuffer 编程!如果你对特定色彩深度的处理或者更高级的图形操作有更多疑问,我们可以继续深入探讨。