4. mmap系统调用详解

在Framebuffer编程中,mmap是将设备内存映射到用户空间的桥梁。理解其每个参数的含义、返回值及错误处理,是稳定控制帧率、避免内存泄漏和段错误的前提。

4.1 mmap函数原型

mmap定义在 <sys/mman.h> 中,原型如下:

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

返回映射的首地址(成功)或 MAP_FAILED(失败)。

4.2 参数含义详解

参数 类型 含义 Framebuffer典型值
addr void * 期望的映射起始地址,通常传 NULL 由内核选择 NULL
length size_t 映射的字节长度,必须为正 finfo.smem_len 或 vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8
prot int 映射区域的内存保护标志 PROT_READ | PROT_WRITE
flags int 映射类型和属性 MAP_SHARED
fd int 已打开的设备文件描述符 open("/dev/fb0", O_RDWR) 返回的 fd
offset off_t 文件偏移量,必须是页大小的整数倍 0

4.3 参数细节与避坑

addr — 地址提示

  • 传 NULL 让内核自动选择,兼容性最好。
  • 若指定非 NULL 地址,内核仅将其作为提示,不保证使用该地址。
  • 在嵌入式系统中,某些平台要求地址对齐到页边界(4KB)。

length — 映射大小

  • 必须与Framebuffer显存大小匹配。过小会导致访问越界,过大浪费虚拟地址空间。
  • 推荐使用 ioctl(FBIOGET_VSCREENINFO) 获取 smem_len 作为长度。

prot — 保护标志

标志 含义 Framebuffer场景
PROT_READ 可读 必须包含
PROT_WRITE 可写 必须包含(需要绘制像素)
PROT_EXEC 可执行 不需要,切勿设置
PROT_NONE 无权限 不用于Framebuffer

flags — 映射类型

  • MAP_SHARED:对映射区的写操作会同步回设备文件,其他进程可见。Framebuffer必须使用此标志。
  • MAP_PRIVATE:写时复制,不会写回设备,不能用于Framebuffer。
  • 可附加 MAP_LOCKED 防止页面被换出(实时性要求高时使用)。
⚠️ 常见错误: 使用 MAP_PRIVATE 映射Framebuffer,写入像素后屏幕无变化,因为数据只停留在进程私有副本中。

fd — 文件描述符

  • 必须是以 O_RDWR 方式打开的Framebuffer设备。
  • 映射完成后,可以 close(fd),映射仍然有效。但建议保留fd以便后续 ioctl 操作。

offset — 偏移量

  • 通常传 0,从显存起始地址映射。
  • 若需要映射双缓冲区的后半个缓冲,可设置为 smem_len / 2(需页对齐)。
  • 必须是系统页大小(sysconf(_SC_PAGE_SIZE))的整数倍,否则 mmap 返回 EINVAL。

4.4 返回值与错误处理

成功时返回映射区域的起始地址。失败时返回 MAP_FAILED(即 (void *)-1),并设置 errno。

errno 含义 Framebuffer常见原因
EACCES 权限不足 fd以只读方式打开,但prot包含 PROT_WRITE
EINVAL 参数无效 offset不是页对齐;length为0;flags组合错误
ENOMEM 内存不足 系统虚拟内存不足,或length超过设备内存大小
ENODEV 设备不支持 fd对应的设备不支持mmap操作
EOVERFLOW 偏移溢出 offset + length 超出设备地址空间
✅ 标准错误处理模板:
void *fb_map = mmap(NULL, smem_len, PROT_READ | PROT_WRITE,
                     MAP_SHARED, fb_fd, 0);
if (fb_map == MAP_FAILED) {
    perror("mmap framebuffer failed");
    close(fb_fd);
    exit(EXIT_FAILURE);
}

4.5 mmap映射流程SVG

mmap映射Framebuffer流程图 mmap映射Framebuffer流程图 open("/dev/fb0", O_RDWR) ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) length = vinfo.smem_len fbp = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) fbp == MAP_FAILED? perror + exit 直接操作fbp绘制 💡 映射完成后,对fbp的读写直接作用于显存,无需经过系统调用,帧率控制的关键在此。 关闭映射使用 munmap(fbp, length),释放虚拟地址空间。

4.6 完整映射代码示例

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

int main() {
    int fb_fd = open("/dev/fb0", O_RDWR);
    if (fb_fd == -1) {
        perror("open framebuffer");
        return 1;
    }

    struct fb_var_screeninfo vinfo;
    if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("ioctl FBIOGET_VSCREENINFO");
        close(fb_fd);
        return 1;
    }

    size_t screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
    printf("Framebuffer size: %zu bytes (%dx%d, %d bpp)\n",
           screensize, vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);

    void *fbp = mmap(NULL, screensize,
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED,
                     fb_fd, 0);
    if (fbp == MAP_FAILED) {
        perror("mmap framebuffer");
        close(fb_fd);
        return 1;
    }

    // 此处可直接通过 fbp 指针绘制像素
    // ...

    // 清理
    munmap(fbp, screensize);
    close(fb_fd);
    return 0;
}
🔑 关键要点:
  • 始终检查 mmap 返回值是否为 MAP_FAILED,不要假设成功。
  • length 使用 smem_len 或自行计算,确保不越界。
  • offset 必须页对齐,否则返回 EINVAL。
  • 映射完成后及时 munmap,避免虚拟地址空间泄漏。