歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux 內存映射函數 mmap()函數詳解

一、概述

內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功後,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間。那麼對於內核空間<---->用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。

以下是一個把普遍文件映射到用戶空間的內存區域的示意圖。

圖一:

二、基本函數

mmap函數是unix/linux下的系統調用,詳細內容可參考《Unix Netword programming》卷二12.2節。

mmap系統調用並不是完全為了用於共享內存而設計的。它本身提供了不同於一般對普通文件的訪問方式,進程可以像讀寫內存一樣對普通文件的操作。而Posix或系統V的共享內存IPC則純粹用於共享目的,當然mmap()實現共享內存也是其主要應用之一。

mmap系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程可以像訪問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。mmap並不分配空間, 只是將文件映射到調用進程的地址空間裡(但是會占掉你的 virutal memory), 然後你就可以用memcpy等操作寫文件, 而不用write()了.寫完後,內存中的內容並不會立即更新到文件中,而是有一段時間的延遲,你可以調用msync()來顯式同步一下, 這樣你所寫的內容就能立即保存到文件裡了.這點應該和驅動相關。 不過通過mmap來寫文件這種方式沒辦法增加文件的長度, 因為要映射的長度在調用mmap()的時候就決定了.如果想取消內存映射,可以調用munmap()來取消內存映射

<span style="background-color: rgb(255, 255, 255);">void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)</span>

mmap用於把文件映射到內存空間中,簡單說mmap就是把一個文件的內容在內存裡面做一個映像。映射成功後,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間。那麼對於內核空間<---->用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。

start:要映射到的內存區域的起始地址,通常都是用NULL(NULL即為0)。NULL表示由內核來指定該內存地址

length:要映射的內存區域的大小

prot:期望的內存保護標志,不能與文件的打開模式沖突。是以下的某個值,可以通過or運算合理地組合在一起  
PROT_EXEC //頁內容可以被執行  
PROT_READ  //頁內容可以被讀取  
PROT_WRITE //頁可以被寫入  
PROT_NONE  //頁不可訪問

flags:指定映射對象的類型,映射選項和映射頁是否可以共享。它的值可以是一個或者多個以下位的組合體

MAP_FIXED :使用指定的映射起始地址,如果由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。

MAP_SHARED :對映射區域的寫入數據會復制回文件內, 而且允許其他映射該文件的進程共享。

MAP_PRIVATE :建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標志和以上標志是互斥的,只能使用其中一個。

MAP_DENYWRITE :這個標志被忽略。

MAP_EXECUTABLE :同上

MAP_NORESERVE :不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號。

MAP_LOCKED :鎖定映射區的頁面,從而防止頁面被交換出內存。

MAP_GROWSDOWN :用於堆棧,告訴內核VM系統,映射區可以向下擴展。

MAP_ANONYMOUS :匿名映射,映射區不與任何文件關聯。

MAP_ANON :MAP_ANONYMOUS的別稱,不再被使用。

MAP_FILE :兼容標志,被忽略。

MAP_32BIT :將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標志只在x86-64平台上得到支持。

MAP_POPULATE :為文件映射通過預讀的方式准備好頁表。隨後對映射區的訪問不會被頁違例阻塞。

MAP_NONBLOCK :僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於內存中的頁面建立頁表入口。

fd:文件描述符(由open函數返回)

offset:表示被映射對象(即文件)從那裡開始對映,通常都是用0。 該值應該為大小為PAGE_SIZE的整數倍

本文URL地址:http://www.bianceng.cn/OS/Linux/201410/45413.htm

返回說明

成功執行時,mmap()返回被映射區的指針,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值為(void *)-1],munmap返回-1。errno被設為以下的某個值

EACCES:訪問出錯

EAGAIN:文件已被鎖定,或者太多的內存已被鎖定

EBADF:fd不是有效的文件描述詞

EINVAL:一個或者多個參數無效

ENFILE:已達到系統對打開文件的限制

ENODEV:指定文件所在的文件系統不支持內存映射

ENOMEM:內存不足,或者進程已超出最大內存映射數量

EPERM:權能不足,操作不允許

ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標志

SIGSEGV:試著向只讀區寫入

SIGBUS:試著訪問不屬於進程的內存區

int munmap(void *start, size_t length)

start:要取消映射的內存區域的起始地址

length:要取消映射的內存區域的大小。

返回說明

成功執行時munmap()返回0。失敗時munmap返回-1.

int msync(const void *start, size_t length, int flags);

對映射內存的內容的更改並不會立即更新到文件中,而是有一段時間的延遲,你可以調用msync()來顯式同步一下, 這樣你內存的更新就能立即保存到文件裡

start:要進行同步的映射的內存區域的起始地址。

length:要同步的內存區域的大小

flag:flags可以為以下三個值之一:

MS_ASYNC : 請Kernel快將資料寫入。

MS_SYNC : 在msync結束返回前,將資料寫入。

MS_INVALIDATE : 讓核心自行決定是否寫入,僅在特殊狀況下使用

三、用戶空間和驅動程序的內存映射

3.1、基本過程

首先,驅動程序先分配好一段內存,接著用戶進程通過庫函數mmap()來告訴內核要將多大的內存映射到內核空間,內核經過一系列函數調用後調用對應的驅動程序的file_operation中指定的mmap函數,在該函數中調用remap_pfn_range()來建立映射關系。

本文URL地址:http://www.bianceng.cn/OS/Linux/201410/45413.htm

3.2、映射的實現

首先在驅動程序分配一頁大小的內存,然後用戶進程通過mmap()將用戶空間中大小也為一頁的內存映射到內核空間這頁內存上。映射完成後,驅動程序往這段內存寫10個字節數據,用戶進程將這些數據顯示出來。

驅動程序:

#include <linux/miscdevice.h>  
 #include <linux/delay.h>  
 #include <linux/kernel.h>  
 #include <linux/module.h>  
 #include <linux/init.h>  
 #include <linux/mm.h>  
 #include <linux/fs.h>  
 #include <linux/types.h>  
 #include <linux/delay.h>  
 #include <linux/moduleparam.h>  
 #include <linux/slab.h>  
 #include <linux/errno.h>  
 #include <linux/ioctl.h>  
 #include <linux/cdev.h>  
 #include <linux/string.h>  
 #include <linux/list.h>  
 #include <linux/pci.h>  
 #include <linux/gpio.h>  
       
       
 #define DEVICE_NAME "mymap"  
       
       
 static unsigned char array[10]={0,1,2,3,4,5,6,7,8,9};  
 static unsigned char *buffer;  
       
       
 static int my_open(struct inode *inode, struct file *file)  
 {  
     return 0;  
 }  
       
       
 static int my_map(struct file *filp, struct vm_area_struct *vma)  
 {      
     unsigned long page;  
     unsigned char i;  
     unsigned long start = (unsigned long)vma->vm_start;  
     //unsigned long end =  (unsigned long)vma->vm_end;  
     unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);  
       
     //得到物理地址  
     page = virt_to_phys(buffer);      
     //將用戶空間的一個vma虛擬內存區映射到以page開始的一段連續物理頁面上  
     if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三個參數是頁幀號,由物理地址右移PAGE_SHIFT得到  
         return -1;  
       
     //往該內存寫10字節數據  
     for(i=0;i<10;i++)  
         buffer[i] = array[i];  
           
     return 0;  
 }  
       
       
 static struct file_operations dev_fops = {  
     .owner    = THIS_MODULE,  
     .open    = my_open,  
     .mmap   = my_map,  
 };  
       
 static struct miscdevice misc = {  
     .minor = MISC_DYNAMIC_MINOR,  
     .name = DEVICE_NAME,  
     .fops = &dev_fops,  
 };  
       
       
 static int __init dev_init(void)  
 {  
     int ret;      
       
     //注冊混雜設備  
     ret = misc_register(&misc);  
     //內存分配  
     buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL);  
     //將該段內存設置為保留  
     SetPageReserved(virt_to_page(buffer));  
       
     return ret;  
 }  
       
       
 static void __exit dev_exit(void)  
 {  
     //注銷設備  
     misc_deregister(&misc);  
     //清除保留  
     ClearPageReserved(virt_to_page(buffer));  
     //釋放內存  
     kfree(buffer);  
 }  
       
       
 module_init(dev_init);  
 module_exit(dev_exit);  
 MODULE_LICENSE("GPL");  
 MODULE_AUTHOR("LKN@SCUT");

本文URL地址:http://www.bianceng.cn/OS/Linux/201410/45413.htm

應用程序:

#include <unistd.h>  
 #include <stdio.h>  
 #include <stdlib.h>  
 #include <string.h>  
 #include <fcntl.h>  
 #include <linux/fb.h>  
 #include <sys/mman.h>  
 #include <sys/ioctl.h>   
       
 #define PAGE_SIZE 4096  
       
       
 int main(int argc , char *argv[])  
 {  
     int fd;  
     int i;  
     unsigned char *p_map;  
           
     //打開設備  
     fd = open("/dev/mymap",O_RDWR);  
     if(fd < 0)  
     {  
         printf("open fail\n");  
         exit(1);  
     }  
       
     //內存映射  
     p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);  
     if(p_map == MAP_FAILED)  
     {  
         printf("mmap fail\n");  
         goto here;  
     }  
       
     //打印映射後的內存中的前10個字節內容  
     for(i=0;i<10;i++)  
         printf("%d\n",p_map[i]);  
           
       
 here:  
     munmap(p_map, PAGE_SIZE);  
     return 0;  
 }

先加載驅動後執行應用程序,用戶空間打印如下:

Copyright © Linux教程網 All Rights Reserved