1.mmap简介:
mmap是标准的用户空间的系统调用API.最为常见的就是用户空间对LCD的操作.mmap允许用户直接对硬件设备实现直接访问操作,因此,其效率是很高的.要正确使用mmap机制,首先重温一下VMA.VMA(Virtual Memory Area),即虚拟内存区,它是用来管理一个进程的地址空间的独特区域的内核数据结构.它包括包括当前进程的代码段、数据段和BSS段的布局.
当我们需要进行内存映射时,就是把目标文件或数据内容映射到相应进程的VMA.
2.用户空间的mmap:
下面是用户空间一个简单的mmap()使用示例.把某一文件直接映射到用户进程里面的VMA,然后对其操作:
1. #include <stdio.h> 2. #include<sys/types.h> 3. #include<sys/stat.h> 4. #include<fcntl.h> 5. #include<unistd.h> 6. #include<sys/mman.h> 7. 8. int main() 9. { 10. int fd; 11. char *start; 12. char buf[100]; 13. 14. /*打开文件*/ 15. fd = open("testfile",O_RDWR); 16. 17. start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); 18. 19. /* 读出数据 */ 20. strcpy(buf,start); 21. printf("buf = %s\n",buf); 22. 23. /* 写入数据 */ 24. strcpy(start,"Buf Is Not Null!"); 25. 26. munmap(start,100); /*解除映射*/ 27. close(fd); 28. 29. return 0; 30. }
上述的系统调用函数mmap()简要说明如下:
函数原型:
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);函数功能:
进程可以像读写内存一样对普通文件或物理内存区域的操作.
参数说明:
addr:参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成.
len:len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起.
prot:参数指定共享内存的访问权限.可取如下几个值的或:PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)、PROT_NONE(不可访问).
flags:flags由以下几个常值指定:MAP_SHARED,MAP_PRIVATE,MAP_FIXED.其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用. 如果指定为MAP_SHARED,则对映射的内存所做的修改同样影响到文件.如果是MAP_PRIVATE,则对映射的内存所做的修改仅对该进程可见,对文件没有影响.
fd:参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信).
offset:offset参数一般设为0,表示从文件头开始映射.
返回值:
函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址.
3.内核驱动的mmap:
当一个用户空间进程调用mmap来映射设备内存到它的地址空间,系统(驱动)需要建立一个新的VMA代表那个映射来响应.一个支持 mmap 的驱动(并且,因此,实现 mmap 方法)需要来帮助那个进程来完成那个VMA的初始化.系统驱动对应的mmap如下:
int (*mmap) (struct file *filp, struct vm_area_struct *vma);
第一个参数就是打开的文件描述符.因此,要实现mmap,我们只需要搞定第二个参数vma就可以了--即建立适合的页表给这个地址范围.内核已经为我们提供了API完成"建立适合的页表":
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long pfn, unsigned long size, pgprot_t prot)
参数说明如下:
vma:当前进程的内存区域.此参数由内核根据上层传递的值完成大部分的初始化,如被映射的内存区域是共享的,可读可写的.
vird_addr:重新映射应当开始的用户虚拟地址.
pfn:页帧号,对应虚拟地址应当被映射的物理地址.这个页帧号简单地是物理地址右移 PAGE_SHIFT位.对大部分使用,VMA结构的 vm_paoff成员正好包含你需要的值.这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size.
size:正在被重新映射的区的大小,以字节为单位.
prot:保护标志,如被映射的区域是否可读可写.
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);其中,在LCD的帧缓冲就是调用这个API来关联,其中第三个参数便是要求被映射的物理地址,如LCD帧缓存的内存起始地址.
参数说明:
vma:同上.
virt_addr:同上.
phys_addr:被映射内存的物理地址.
size:同上.
prot:同上.
4.用户空间mmap和内核驱动空间mmap对应示例:
基本上,LINUX的LCD子系统都是通过mmap实现用户像素数据到LCD屏上的显示的.LCD的用户空间的测试程序如下:
#include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/mman.h> #include <unistd.h> #define RED_COLOR565 0x0F100 #define GREEN_COLOR565 0x007E0 #define BLUE_COLOR565 0x0001F int main(int argc,char **argv) { int fd_fb = 0; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; long int screen_size = 0; short *fbp565 = NULL; char *pchFbName = NULL; if(argc < 2) { printf("Pls Input fbName,Such As /dev/fb0.\n"); return -1; } pchFbName = argv[1]; int x = 0, y = 0; fd_fb = open(pchFbName, O_RDWR); if (!fd_fb) { printf("Error: cannot open framebuffer device.\n"); exit(1); } // Get fixed screen info if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &finfo)) { printf("Error reading fixed information.\n"); exit(2); } // Get variable screen info if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &vinfo)) { printf("Error reading variable information.\n"); exit(3); } // the size of the screen in bytes screen_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; printf("%dx%d, %dbpp, screen_size = %d\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel, screen_size ); // map framebuffer to user memory fbp565 = (short *)mmap(0, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); if ((int)fbp565 == -1) { printf("Error: failed to map framebuffer device to memory.\n"); exit(4); } if(vinfo.bits_per_pixel == 16) { printf("16 bpp framebuffer\n"); // Red Screen printf("Red Screen\n"); for(y = 0; y < vinfo.yres/3; y++) { for(x = 0; x < vinfo.xres ; x++) { *(fbp565 + y * vinfo.xres + x) = RED_COLOR565; } } // Green Screen printf("Green Screen\n"); for(y = vinfo.yres/3; y < (vinfo.yres*2)/3; y++) { for(x = 0; x < vinfo.xres; x++) { *(fbp565 + y * vinfo.xres + x) =GREEN_COLOR565; } } // Blue Screen printf("Blue Screen\n"); for(y = (vinfo.yres*2)/3; y < vinfo.yres; y++) { for(x = 0; x < vinfo.xres; x++) { *(fbp565 + y * vinfo.xres + x) = BLUE_COLOR565; } } } else { printf("warnning: bpp is not 16\n"); } munmap(fbp565, screen_size); close(fd_fb); return 0; }其系统调用将调用到内核驱动层的fb_mmap()函数:
static int fb_mmap(struct file *file, struct vm_area_struct * vma) { int fbidx = iminor(file->f_path.dentry->d_inode); struct fb_info *info = registered_fb[fbidx]; struct fb_ops *fb = info->fbops; unsigned long off; unsigned long start; u32 len; if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) return -EINVAL; off = vma->vm_pgoff << PAGE_SHIFT; if (!fb) return -ENODEV; mutex_lock(&info->mm_lock); if (fb->fb_mmap) { int res; res = fb->fb_mmap(info, vma); mutex_unlock(&info->mm_lock); return res; } /* frame buffer memory */ start = info->fix.smem_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); if (off >= len) { /* memory mapped io */ off -= len; if (info->var.accel_flags) { mutex_unlock(&info->mm_lock); return -EINVAL; } start = info->fix.mmio_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); } mutex_unlock(&info->mm_lock); start &= PAGE_MASK; if ((vma->vm_end - vma->vm_start + off) > len) return -EINVAL; off += start; vma->vm_pgoff = off >> PAGE_SHIFT; /* This is an IO map - tell maydump to skip this VMA */ vma->vm_flags |= VM_IO | VM_RESERVED; fb_pgprotect(file, vma, off); if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0; }
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,vma->vm_end - vma->vm_start, vma->vm_page_prot))
其中,第三个参数(即off)是和具体平台侦缓冲关联的.并要求页对齐.
5.小结:
用户空间要实现mmap,系统(驱动)需要通过内核API重新建立并初始化一个VMA与其对应--这一步工作的完成需要借助下面两个API:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot); int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);