内核中与驱动相关的内存操作之十二(mmap)

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); 



 

发布了131 篇原创文章 · 获赞 17 · 访问量 19万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章