显卡一类的设备有一片很大的显存,驱动程序将这片显存映射到内核的地址空间,方便进行操作。如果用户想要在屏幕上进行绘制操作,将要在用户空间开辟出一片至少同样大小的内存,将要绘制的图像数据填充在这片内存中,然后调用write系统调用,将数据复制到内核空间的显存中,从而进行图像绘制。不难发现,在这个过程中有大量的数据复制,这对于显卡针对性能要求非常高的设备,这种复制带来的性能损耗显然是不可接受的。
要消除这个复制操作就需要应用程序能够直接访问显存,但是现存被映射到内核空间,应用程序没有这个访问权限。字符设备驱动提供了一个mmap接口,可以把内核空间中的那片内存所对应的物理地址再次映射到用户空间,这样一个物理内存就有了两份映射,或者说有两个虚拟地址,一个在内核空间,一个在用户空间。这样就可以通过直接操作用户空间这片映射之后的内存来直接访问物理内存,从而提高了效率。下面已是一个虚拟的帧缓存设备的驱动程序,实现了mmap接口。
#include#include #include #include #include #include #include #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) { 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 "); MODULE_DEscriptION("A simple module");
下面是该程序对应的测试程序。
#include#include #include #include #include #include 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) = ' '; 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



