內核中與驅動相關的內存操作之十二(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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章