3. 实现帧缓冲的mmap操作
任务目标
为帧缓冲驱动实现 mmap 文件操作,使用户空间程序能够将内核中的显存(帧缓冲)直接映射到进程地址空间,从而绕过 read/write 系统调用的数据拷贝开销,实现高性能的像素读写。
核心实现
本案例基于 Linux 内核的 struct fb_info 框架,在自定义帧缓冲驱动中注册 fb_ops 时提供 fb_mmap 回调。该回调通过 remap_pfn_range 将物理连续的帧缓冲内存映射到用户空间。关键点在于:帧缓冲内存必须使用 kmalloc(而非 vmalloc)分配以保证物理连续性,且映射大小不能超过实际分配的内存范围。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#define FB_MMAP_SIZE (800 * 600 * 2) /* 800x600 RGB565 */
static char *fake_screen;
static int my_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long start = vma->vm_start;
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long pfn = virt_to_phys(fake_screen) >> PAGE_SHIFT;
/* 防止映射超过实际分配大小 */
if (size > FB_MMAP_SIZE)
return -EINVAL;
/* 禁止映射为可执行 */
vma->vm_flags &= ~VM_EXEC;
vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
/* 映射物理内存到用户空间 */
if (remap_pfn_range(vma, start, pfn, size, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
static struct fb_ops my_fb_ops = {
.owner = THIS_MODULE,
.fb_read = fb_sys_read,
.fb_write = fb_sys_write,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
.fb_mmap = my_fb_mmap,
};
static int my_fb_probe(struct platform_device *pdev)
{
struct fb_info *fbi;
int ret;
/* 使用 kmalloc 保证物理连续,这是 mmap 的前提 */
fake_screen = kmalloc(FB_MMAP_SIZE, GFP_KERNEL);
if (!fake_screen)
return -ENOMEM;
fbi = framebuffer_alloc(0, &pdev->dev);
if (!fbi) {
kfree(fake_screen);
return -ENOMEM;
}
fbi->screen_base = fake_screen;
fbi->screen_size = FB_MMAP_SIZE;
fbi->fbops = &my_fb_ops;
strcpy(fbi->fix.id, "myfb");
fbi->fix.smem_start = virt_to_phys(fake_screen);
fbi->fix.smem_len = FB_MMAP_SIZE;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = 800 * 2;
/* 设置 800x600 RGB565 格式 */
fbi->var.xres = 800;
fbi->var.yres = 600;
fbi->var.xres_virtual = 800;
fbi->var.yres_virtual = 600;
fbi->var.bits_per_pixel = 16;
fbi->var.red.offset = 11; fbi->var.red.length = 5;
fbi->var.green.offset = 5; fbi->var.green.length = 6;
fbi->var.blue.offset = 0; fbi->var.blue.length = 5;
ret = register_framebuffer(fbi);
if (ret) {
framebuffer_release(fbi);
kfree(fake_screen);
return ret;
}
platform_set_drvdata(pdev, fbi);
return 0;
}
static int my_fb_remove(struct platform_device *pdev)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
unregister_framebuffer(fbi);
kfree(fbi->screen_base);
framebuffer_release(fbi);
return 0;
}
static struct platform_driver my_fb_driver = {
.probe = my_fb_probe,
.remove = my_fb_remove,
.driver = {
.name = "myfb",
},
};
module_platform_driver(my_fb_driver);
MODULE_LICENSE("GPL");
编译与加载
# 编译(内核源码树中)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载驱动
sudo insmod myfb.ko
# 确认设备节点生成
ls /dev/fb*
用户空间验证
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int fd = open("/dev/fb0", O_RDWR);
if (fd < 0) { perror("open"); return 1; }
/* 映射一页大小的显存 */
void *map = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { perror("mmap"); return 1; }
/* 写入像素数据(RGB565 红色) */
unsigned short *pixel = (unsigned short *)map;
pixel[0] = 0xF800; /* 红色 */
msync(map, 4096, MS_SYNC);
/* 读回验证 */
printf("Read back: 0x%04X\n", pixel[0]);
munmap(map, 4096);
close(fd);
return 0;
}
# 编译并运行验证程序
gcc -o fb_mmap_test fb_mmap_test.c
./fb_mmap_test
判定标准
- 驱动加载后
/dev/fb0存在,且dmesg无错误信息。 - 验证程序输出
Read back: 0xF800,说明写入的数据通过 mmap 映射成功写入了内核显存并读回一致。 - 若在真实 LCD 上运行,屏幕左上角像素应显示为红色。
关键说明
- 帧缓冲内存必须用
kmalloc分配(物理连续),vmalloc分配的内存无法通过remap_pfn_range直接映射。 fb_sys_read/fb_sys_write依赖screen_base指向的内存,与 mmap 共享同一块物理内存,因此读写操作保持一致。- 若需支持更大的显存(如 1920x1080x4),需确认物理内存分配是否成功,并相应调整
FB_MMAP_SIZE。