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
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,避免虚拟地址空间泄漏。