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;
}
可见,fb_mmap()函数功能的实现其核心功能函数是:
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);