20251130 - 详细解析Framebuffer应用编程中涉及到的API函数

在 Linux Framebuffer (FB) 应用编程中,主要涉及到三个核心的系统调用,它们共同完成了设备打开、配置获取和内存访问的功能。

以下是对这些关键 API 函数的详细解析:


1. open 函数:打开 Framebuffer 设备

open 函数用于打开 /dev/fbX 设备文件,获取文件描述符,这是后续操作的基础。

A. 头文件 (Header Files)

c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

B. 函数原型 (Function Prototype)

c 复制代码
int open(const char *pathname, int flags, ...);

C. 参数说明 (Parameters)

参数 类型 说明 (FB 场景)
pathname const char * 要打开的设备文件路径,例如 /dev/fb0(通常是主显示设备)。
flags int 打开方式标志。在 FB 编程中通常使用 O_RDWR(读写模式),以便进行读(获取配置)和写(绘图)。
... 变长参数 可选参数 mode_t mode,只有在 flags 中包含 O_CREAT 时才需要,在打开设备文件时通常不使用。

D. 返回值 (Return Value)

返回值 说明
非负整数 成功,返回设备的文件描述符 (fd)。该描述符将在 ioctlmmap 中使用。
-1 失败,并设置全局变量 errno。常见的错误是权限不足或设备文件不存在。

2. ioctl 函数:控制和配置 Framebuffer

ioctl (Input/Output Control) 函数是 Linux 设备驱动程序和用户应用交互的主要方式。在 FB 编程中,它用于获取和设置屏幕的配置信息。

A. 头文件 (Header File)

c 复制代码
#include <sys/ioctl.h>

B. 函数原型 (Function Prototype)

c 复制代码
int ioctl(int fd, unsigned long request, ...);

C. 参数说明 (Parameters)

参数 类型 说明 (FB 场景)
fd int 通过 open("/dev/fb0", ...) 返回的文件描述符。
request unsigned long 控制命令 。这是 ioctl 的核心,它告诉驱动程序执行什么操作。FB 驱动定义了特定的命令宏。
... 变长参数 通常是一个指针,指向用户空间定义的结构体,用于在用户和内核之间传递数据(获取/设置配置)。
常见的 Framebuffer request 宏:
request 宏 描述 对应结构体 作用
FBIOGET_FSCREENINFO 获取固定的屏幕信息(Immutable Screen Info)。 struct fb_fix_screeninfo 包含 VRAM 物理地址、映射长度、设备名称等。
FBIOGET_VSCREENINFO 获取可变的屏幕信息(Variable Screen Info)。 struct fb_var_screeninfo 包含分辨率、色深、时序等可修改的参数。
FBIOPUT_VSCREENINFO 设置可变的屏幕信息。 struct fb_var_screeninfo 根据用户设置的参数(如分辨率、色深)来配置显示。

D. 返回值 (Return Value)

返回值 说明
0 成功。
-1 失败,并设置全局变量 errno。常见的错误是命令无效或参数结构体不匹配。

3. mmap 函数:映射 Framebuffer 内存

mmap 函数将 Framebuffer 的物理显存(VRAM)直接映射到应用程序的虚拟地址空间,允许用户通过指针直接读写像素数据,这是实现高性能绘图的关键。

A. 头文件 (Header File)

c 复制代码
#include <sys/mman.h>

B. 函数原型 (Function Prototype)

c 复制代码
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

C. 参数说明 (Parameters)

参数 类型 说明 (FB 场景)
addr void * 通常设为 NULL,让内核自动选择虚拟地址。
length size_t 要映射的显存长度 。应通过 FBIOGET_FSCREENINFO 获取的 smem_len 字段来确定。
prot int 保护权限。通常设为 `PROT_READ
flags int 映射标志 。必须使用 MAP_SHARED,因为所有进程的修改都需要反映到实际的显存中,并最终显示在屏幕上。
fd int 通过 open("/dev/fb0", ...) 获得的设备文件描述符。
offset off_t 映射在文件中的起始偏移量。对于 FB 设备,通常设为 0,从显存的起始位置开始映射。

D. 返回值 (Return Value)

返回值 说明
void \* 成功,返回映射到进程虚拟地址空间的起始地址(即显存的起始地址)。
MAP_FAILED 失败(等于 (void *)-1),并设置全局变量 errno

核心步骤总结

一个典型的 Framebuffer 应用编程流程是:

  1. 打开设备: fd = open("/dev/fb0", O_RDWR);
  2. 获取固定信息: ioctl(fd, FBIOGET_FSCREENINFO, &finfo); (获取 smem_len
  3. 获取可变信息: ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); (获取 xres, yres, bits_per_pixel 等)
  4. 映射显存: fbp = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  5. 绘图/操作: 使用 fbp 指针直接读写像素。
  6. 解除映射: munmap(fbp, finfo.smem_len);
  7. 关闭设备: close(fd);

📄 Framebuffer 绘图示例代码 (fb_test.c)

下面这段代码实现了以下功能:

  1. 打开 LCD 设备 (/dev/fb0)。
  2. 获取屏幕参数(分辨率、位深)。
  3. 通过 mmap 将显存映射到用户空间。
  4. 根据屏幕的位深(16bpp 或 32bpp)智能适配颜色格式
  5. 在屏幕上绘制一个全屏红色背景 ,并在中间画一个蓝色的矩形

您可以直接在您的 Linux 开发环境(Ubuntu)中保存为 fb_test.c,然后使用交叉编译器编译。

c 复制代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdint.h>

int main()
{
    int fp = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;
    char *fbp = 0;
    int x = 0, y = 0;
    long int location = 0;

    // 1. 打开 Framebuffer 设备
    fp = open("/dev/fb0", O_RDWR);
    if (fp == -1) {
        perror("Error: cannot open framebuffer device");
        return 1;
    }
    printf("The framebuffer device was opened successfully.\n");

    // 2. 获取不可变屏幕信息 (FINFO) - 为了得到 smem_len (显存总大小) 和 line_length (跨度)
    if (ioctl(fp, FBIOGET_FSCREENINFO, &finfo) == -1) {
        perror("Error reading fixed information");
        return 1;
    }

    // 3. 获取可变屏幕信息 (VINFO) - 为了得到分辨率 (xres, yres) 和 位深 (bpp)
    if (ioctl(fp, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("Error reading variable information");
        return 1;
    }

    // 打印检测到的屏幕信息
    printf("Display info: %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
    printf("Line length (stride): %d bytes\n", finfo.line_length);

    // 4. 计算显存大小
    // 虽然可以用 vinfo.xres * vinfo.yres * bpp / 8 计算,但使用 finfo.smem_len 更安全
    screensize = finfo.smem_len;

    // 5. 内存映射 (mmap)
    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
    if ((long)fbp == -1) {
        perror("Error: failed to map framebuffer device to memory");
        return 1;
    }
    printf("The framebuffer was mapped to memory successfully.\n");

    // 6. 绘图操作
    // 遍历屏幕的每一个像素点
    for (y = 0; y < vinfo.yres; y++) {
        for (x = 0; x < vinfo.xres; x++) {

            // 核心计算:计算坐标 (x,y) 在显存中的字节偏移量
            // finfo.line_length 是每一行占用的字节数(可能包含对齐填充)
            location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) +
                       (y + vinfo.yoffset) * finfo.line_length;

            // 定义颜色 (R, G, B)
            // 默认画红色背景
            int r = 255, g = 0, b = 0;

            // 如果在屏幕中间区域,画蓝色矩形 (200x200)
            if (x >= (vinfo.xres/2 - 100) && x < (vinfo.xres/2 + 100) &&
                y >= (vinfo.yres/2 - 100) && y < (vinfo.yres/2 + 100)) {
                r = 0; g = 0; b = 255;
            }

            // 根据不同的位深 (BPP) 写入不同的像素格式
            if (vinfo.bits_per_pixel == 32) {
                // 32bpp: ARGB8888 或 BGRA8888
                // 指针偏移到当前像素位置
                *(fbp + location) = b;         // Blue
                *(fbp + location + 1) = g;     // Green
                *(fbp + location + 2) = r;     // Red
                *(fbp + location + 3) = 0;     // Alpha (透明度)
            } 
            else if (vinfo.bits_per_pixel == 16) {
                // 16bpp: 通常是 RGB565 (5位红, 6位绿, 5位蓝)
                // i.MX6ULL 的 LCD 经常是这种格式
                unsigned short int t = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
                *((unsigned short int*)(fbp + location)) = t;
            }
            else {
                // 其他情况暂不处理 (如 24bpp)
                // printf("Unsupported bpp: %d\n", vinfo.bits_per_pixel);
            }
        }
    }

    // 7. 解除映射并关闭文件
    munmap(fbp, screensize);
    close(fp);
    
    printf("Drawing finished. Check your screen!\n");
    return 0;
}

💡 核心知识点解析

  1. finfo.line_length (Stride) 的重要性
    • 我们在计算 location 时使用了 y * finfo.line_length,而不是 y * xres * bpp
    • 原因 :有些屏幕为了硬件对齐,每一行的末尾可能会有填充字节 (Padding) 。比如一行只有 1366 像素,但在显存里可能占用了 1400 像素的空间。使用 line_length 是最安全、最标准的做法。
  2. RGB565 (16bpp) vs ARGB8888 (32bpp)
    • i.MX6ULL 的裸 RGB 接口通常连接 16位 或 24位 屏幕。Linux 驱动经常默认配置为 16bpp (RGB565)。
    • RGB565 算法((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
      • 红色 8bit 丢弃低3位,变成 5bit,左移到最高位。
      • 绿色 8bit 丢弃低2位,变成 6bit,放在中间。
      • 蓝色 8bit 丢弃低3位,变成 5bit,放在最低位。
    • 代码中增加了 if (vinfo.bits_per_pixel == ...) 判断,这样无论您的屏幕配置如何,都能显示出正确的颜色,而不会变成花屏或噪点。
相关推荐
Xの哲學2 小时前
Linux AQM 深度剖析: 拥塞控制
linux·服务器·算法·架构·边缘计算
郝学胜-神的一滴2 小时前
Linux 下循环创建多线程:深入解析与实践指南
linux·服务器·c++·程序人生·算法·设计模式
June`3 小时前
深入解析网络层与数据链路层
linux·服务器·网络
老王熬夜敲代码3 小时前
计算机网络--IP概念
linux·网络·笔记
Lynnxiaowen3 小时前
今天我们继续学习devops内容基于Jenkins构建CICD环境
linux·运维·学习·jenkins·devops
用户6135411460163 小时前
Linux 麒麟系统安装 gcc-7.3.0 rpm 包步骤
linux
小尧嵌入式3 小时前
Linux网络介绍网络编程和数据库
linux·运维·服务器·网络·数据库·qt·php
LUCIFER3 小时前
[驱动之路(七)——Pinctrl子系统]学习总结,万字长篇,一文彻底搞懂Pinctrl子系统(含Pin Controller驱动框架解析)
linux·驱动开发
山川而川-R3 小时前
在香橙派5pro上的ubuntu22.04系统烧录镜像
linux·运维·服务器