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