显卡一类的设备有一片很大的显存,驱动程序将这片显存映射到内核的地址空间,方便进行操作。如果用户想要在屏幕上进行绘制操作,将要在用户空间开辟出一片至少同样大小的内存,将要绘制的图像数据填充在这片内存中,然后调用write系统调用,将数据复制到内核空间的显存中,从而进行图像绘制。不难发现,在这个过程中有大量的数据复制,这对于显卡针对性能要求非常高的设备,这种复制带来的性能损耗显然是不可接受的。
要消除这个复制操作就需要应用程序能够直接访问显存,但是现存被映射到内核空间,应用程序没有这个访问权限。字符设备驱动提供了一个mmap接口,可以把内核空间中的那片内存所对应的物理地址再次映射到用户空间,这样一个物理内存就有了两份映射,或者说有两个虚拟地址,一个在内核空间,一个在用户空间。这样就可以通过直接操作用户空间这片映射之后的内存来直接访问物理内存,从而提高了效率。下面已是一个虚拟的帧缓存设备的驱动程序,实现了mmap接口。
/*************************************************************************
> File Name: vfb.c
> Author: longway.bai
> Mail: 953821672@qq.com
> Created Time: 2022年01月15日 星期六 18时08分40秒
************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define VFB_MAJOR 256
#define VFB_MINOR 1
#define VFB_DEV_CNT 1
#define VFB_DEV_NAME "vfbdev"
struct vfb_dev {
unsigned char *buf;
struct cdev cdev;
}vfbdev;
static int vfb_open(struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev, struct vfb_dev, cdev);
return 0;
}
static int vfb_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int vfb_mmap(struct file *flip, struct vm_area_struct *vma)
{
/*第一个参数vma是用来描述一片映射区域的结构指针,一个进程有很多映射区域,
每一个区域都有这样对应的一个结构,这些结构使用链表组织在一起的,该结构描述了
这篇映射区域虚拟的起始地址、结束地址和访问的权限等信息。
第二个参数addr是用户指定的起始地址,如果用户没有指定,那么由内核来指定该地址
第三个参数是物理内存对应的页的框号,就是将物理地址除以页的大小得到的信息
第四个参数是想要映射的空间大小
第五个参数prot是该内存区域的访问权限。
经过该函数后,一片物理内存区域会被映射到用户空间,而这片物理内存之前又被映射到了
内核空间,这样这样这片物理内存被映射过了两次,在用户空间和内核空间都可以被访问*/
if(remap_pfn_range(vma, vma->vm_start, virt_to_phys(vfbdev.buf)>>PAGE_SHIFT,\
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
return 0;
}
ssize_t vfb_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
int ret;
struct vfb_dev *dev = filp->private_data;
size_t len = (count > PAGE_SIZE) ? PAGE_SIZE : count;//首先判断读取的字节数是否超过分配内存的大小(通常是4096),如果超过了限定最多只能读一页的数据,
ret = copy_to_user(buf, dev->buf, len);//把内核的数据复制到用户空间,ret表示未成功copy的数目
return len - ret; //表示成功copy的数目
}
static struct file_operations vfb_fops = {
.owner = THIS_MODULE,
.open = vfb_open,
.release = vfb_release,
.mmap = vfb_mmap,
.read = vfb_read,
};
static int __init vfb_init(void)
{
int ret;
dev_t dev;
unsigned long addr;
dev = MKDEV(VFB_MAJOR, VFB_MINOR);
ret = register_chrdev_region(dev, VFB_DEV_CNT, VFB_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vfbdev.cdev, &vfb_fops);
vfbdev.cdev.owner = THIS_MODULE;
ret = cdev_add(&vfbdev.cdev, dev, VFB_DEV_CNT);
if (ret)
goto add_err;
addr = __get_free_page(GFP_KERNEL);//动态分配了一页内存,内核空间按页来管理内存,在进行映射时,地址要按照页的大小对齐
if (!addr)
goto get_err;
vfbdev.buf = (unsigned char*)addr;
memset(vfbdev.buf, 0, PAGE_SIZE);
return 0;
get_err:
cdev_del(&vfbdev.cdev);
add_err:
unregister_chrdev_region(dev, VFB_DEV_CNT);
reg_err:
return ret;
}
static void __exit vfb_exit(void)
{
dev_t dev;
dev = MKDEV(VFB_MAJOR, VFB_MINOR);
free_page((unsigned long)vfbdev.buf);//释放之前分配的内存
cdev_del(&vfbdev.cdev);
unregister_chrdev_region(dev, VFB_DEV_CNT);
}
module_init(vfb_init);
module_exit(vfb_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("longway<longway.bai@outlook.com>");
MODULE_DESCRIPTION("A simple module");
下面是该程序对应的测试程序。
/*************************************************************************
> File Name: test_mmap.c
> Author: longway.bai
> Mail: 953821672@qq.com
> Created Time: 2022年01月15日 星期六 23时22分48秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
char *start;
int i;
char buf[32];
fd = open("/dev/vfb0", O_RDWR);
if(fd == -1)
goto fail;
start = mmap(NULL, 32, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(start == MAP_FAILED)
goto fail;
for (i=0; i<26; i++)
*(start+i) = 'a' + i;
*(start+i) = '\0';
if (read(fd, buf, 27) == -1)
goto fail;
puts(buf);
munmap(start, 32);
return 0;
fail:
perror("mmap test");
exit(EXIT_FAILURE);
}
以上,通过用户层直接访问到物理内存,并写入数据,然后从内核空间读出数据,测试结果
# insmod vfb.ko
# mknod /dev/vfb0 c 256 1
# ./test_mmap
abcdefghijklmnopqrstuvwxyz