Linux內存管理之mmap詳解

一. mmap系統調用node

1. mmap系統調用    linux

    mmap將一個文件或者其它對象映射進內存。文件被映射到多個頁上,若是文件的大小不是全部頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操做,刪除特定地址區域的對象映射。app

當使用mmap映射文件到進程後,就能夠直接操做這段虛擬地址進行文件的讀寫等操做,沒必要再調用read,write等系統調用.但需注意,直接對該段內存寫時不會寫入超過當前文件大小的內容.ide

採用共享內存通訊的一個顯而易見的好處是效率高,因 爲進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次 數據:一次從輸入文件到共享內存區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再 從新創建共享內存區域。而是保持共享區域,直到通訊完畢爲止,這樣,數據內容一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在解除映射時 才寫回文件的。所以,採用共享內存的通訊方式效率是很是高的。  函數

    基 於文件的映射,在mmap和munmap執行過程的任什麼時候刻,被映射文件的st_atime可能被更新。若是st_atime字段在前述的狀況下沒有獲得 更新,首次對映射區的第一個頁索引時會更新該字段的值。用PROT_WRITE 和 MAP_SHARED標誌創建起來的文件映射,其 st_ctime 和 st_mtime在對映射區寫入以後,但在msync()經過MS_SYNC 和 MS_ASYNC兩個標誌調用以前會被更新。spa

用法:.net

#include <sys/mman.h>unix

void *mmap(void *start, size_t length, int prot, int flags,指針

int fd, off_t offset);對象

int munmap(void *start, size_t length);

返回說明:

成功執行時,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:試着訪問不屬於進程的內存區

參數:

start:映射區的開始地址。


length:映射區的長度。


prot:指望的內存保護標誌,不能與文件的打開模式衝突。是如下的某個值,能夠經過or運算合理地組合在一塊兒

PROT_EXEC //頁內容能夠被執行

PROT_READ //頁內容能夠被讀取

PROT_WRITE //頁能夠被寫入

PROT_NONE //頁不可訪問


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

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

MAP_SHARED //與其它全部映射這個對象的進程共享映射空間。對共享區的寫入,至關於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。

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:有效的文件描述詞。若是MAP_ANONYMOUS被設定,爲了兼容問題,其值應爲-1。


offset:被映射對象內容的起點。


2. 系統調用munmap() 

#include <sys/mman.h>


int munmap( void * addr, size_t len ) 
該調用在進程地址空間中解除一個映射關係,addr是調用mmap()時返回的地址,len是映射區的大小。當映射關係解除後,對原來映射地址的訪問將致使段錯誤發生。 


3. 系統調用msync() 

#include <sys/mman.h>


int msync ( void * addr , size_t len, int flags) 
通常說來,進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,每每在調用munmap()後才執行該操做。能夠經過調用msync()實現磁盤上文件內容與共享內存區的內容一致。  


系統調用mmap()用於共享內存的兩種方式: 

(1)使用普通文件提供的內存映射:適用於任何進程之間;此時,須要打開或建立一個文件,而後再調用mmap();典型調用代碼以下: 
 

  1. fd=open(name, flag, mode);

  2. if(fd<0)

  3.    ...

  4. ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

經過mmap()實現共享內存的通訊方式有許多特色和要注意的地方

(2) 使用特殊文件提供匿名內存映射:適用於具備親緣關係的進程之間;因爲父子進程特殊的親緣關係,在父進程中先調用mmap(),而後調用fork()。那麼 在調用fork()以後,子進程繼承父進程匿名映射後的地址空間,一樣也繼承mmap()返回的地址,這樣,父子進程就能夠經過映射區域進行通訊了。注 意,這裏不是通常的繼承關係。通常來講,子進程單獨維護從父進程繼承下來的一些變量。而mmap()返回的地址,卻由父子進程共同維護。 
對於具備親緣關係的進程實現共享內存最好的方式應該是採用匿名內存映射的方式。此時,沒必要指定具體的文件,只要設置相應的標誌便可.


三. mmap進行內存映射的原理

     mmap系統調用的最終目的是將,設備或文件映射到用戶進程的虛擬地址空間,實現用戶進程對文件的直接讀寫,這個任務能夠分爲如下三步:

1.在用戶虛擬地址空間中尋找空閒的知足要求的一段連續的虛擬地址空間,爲映射作準備(由內核mmap系統調用完成)

       每一個進程擁有3G字節的用戶虛存空間。可是,這並不意味着用戶進程在這3G的範圍內能夠任意使用,由於虛存空間最終得映射到某個物理存儲空間(內存或磁盤空間),才真正可使用。

       那 麼,內核怎樣管理每一個進程3G的虛存空間呢?歸納地說,用戶進程通過編譯、連接後造成的映象文件有一個代碼段和數據段(包括data段和bss段),其中 代碼段在下,數據段在上。數據段中包括了全部靜態分配的數據空間,即全局變量和全部申明爲static的局部變量,這些空間是進程所必需的基本要求,這些 空間是在創建一個進程的運行映像時就分配好的。除此以外,堆棧使用的空間也屬於基本要求,因此也是在創建進程時就分配好的,如圖3.1所示:

 

 

 圖3.1  進程虛擬空間的劃分

      在內核中,這樣每一個區域用一個結構struct vm_area_struct 來表示.它描述的是一段連續的、具備相同訪問屬性的虛存空間,該虛存空間的大小爲物理內存頁面的整數倍。可使用 cat /proc/<pid>/maps來查看一個進程的內存使用狀況,pid是進程號.其中顯示的每一行對應進程的一個vm_area_struct結構.

下面是struct vm_area_struct結構體的定義:

  1. #include <linux/mm_types.h>


  2. /* This struct defines a memory VMM memory area. */


  3. struct vm_area_struct {

  4. struct mm_struct * vm_mm; /* VM area parameters */

  5. unsigned long vm_start;

  6. unsigned long vm_end;


  7. /* linked list of VM areas per task, sorted by address */

  8. struct vm_area_struct *vm_next;

  9. pgprot_t vm_page_prot;

  10. unsigned long vm_flags;


  11. /* AVL tree of VM areas per task, sorted by address */

  12. short vm_avl_height;

  13. struct vm_area_struct * vm_avl_left;

  14. struct vm_area_struct * vm_avl_right;


  15. /* For areas with an address space and backing store,

  16. vm_area_struct *vm_next_share;

  17. struct vm_area_struct **vm_pprev_share;

  18. struct vm_operations_struct * vm_ops;

  19. unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */

  20. struct file * vm_file;

  21. unsigned long vm_raend;

  22. void * vm_private_data; /* was vm_pte (shared mem) */

  23. };

        一般,進程所使用到的虛存空間不連續,且各部分虛存空間的訪問屬性也可能不一樣。因此一個進程的虛存空間須要多個vm_area_struct結構來描述。 在vm_area_struct結構的數目較少的時候,各個vm_area_struct按照升序排序,以單鏈表的形式組織數據(經過vm_next指針 指向下一個vm_area_struct結構)。可是當vm_area_struct結構的數據較多的時候,仍然採用鏈表組織的化,勢必會影響到它的搜索 速度。針對這個問題,vm_area_struct還添加了vm_avl_hight(樹高)、vm_avl_left(左子節點)、 vm_avl_right(右子節點)三個成員來實現AVL樹,以提升vm_area_struct的搜索速度。

  假如該vm_area_struct描述的是一個文件映射的虛存空間,成員vm_file便指向被映射的文件的file結構,vm_pgoff是該虛存空間起始地址在vm_file文件裏面的文件偏移,單位爲物理頁面。

圖3.2  進程虛擬地址示意圖 

所以,mmap系統調用所完成的工做就是準備這樣一段虛存空間,並創建vm_area_struct結構體,將其傳給具體的設備驅動程序.

2. 創建虛擬地址空間和文件或設備的物理地址之間的映射(設備驅動完成)

  創建文件映射的第二步就是創建虛擬地址和具體的物理地址之間的映射,這是經過修改進程頁表來實現的.mmap方法是file_opeartions結構的成員:

  int (*mmap)(struct file *,struct vm_area_struct *);


linux有2個方法創建頁表:

(1) 使用remap_pfn_range一次創建全部頁表.

   int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot); 

返回值:

成功返回 0, 失敗返回一個負的錯誤值
參數說明:

vma 用戶進程建立一個vma區域


virt_addr 從新映射應當開始的用戶虛擬地址. 這個函數創建頁表爲這個虛擬地址範圍從 virt_addr 到 virt_addr_size.


pfn 頁 幀號, 對應虛擬地址應當被映射的物理地址. 這個頁幀號簡單地是物理地址右移 PAGE_SHIFT 位. 對大部分使用, VMA 結構 的 vm_paoff 成員正好包含你須要的值. 這個函數影響物理地址 從 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size.


size 正在被從新映射的區的大小, 以字節.


prot 給新 VMA 要求的"protection". 驅動可(而且應當)使用在vma->vm_page_prot 中找到的值.

(2) 使用nopage VMA方法每次創建一個頁表項.

   struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);

返回值:

成功則返回一個有效映射頁,失敗返回NULL.

參數說明:

address 表明從用戶空間傳過來的用戶空間虛擬地址.

返回一個有效映射頁.


(3) 使用方面的限制:

remap_pfn_range 不能映射常規內存,只存取保留頁和在物理內存頂之上的物理地址。由於保留頁和在物理內存頂之上的物理地址內存管理系統的各個子模塊管理不到。 640 KB 和 1MB 是保留頁可能映射,設備I/O內存也能夠映射。若是想把kmalloc()申請的內存映射到用戶空間,則能夠經過 mem_map_reserve()把相應的內存設置爲保留後就能夠。

3. 當實際訪問新映射的頁面時的操做(由缺頁中斷完成)

(1)  page cache 及swap cache中頁面的區分:一個被訪問文件的物理頁面都駐留在page cache或swap cache中,一個頁面的全部信息由 struct page來描述。struct page中有一個域爲指針mapping ,它指向一個struct address_space類型結 構。page cache或swap cache中的全部頁面就是根據address_space結構以及一個偏移量來區分的。 (2) 文 件與 address_space結構的對應:一個具體的文件在打開後,內核會在內存中爲之創建一個struct inode結構,其中的 i_mapping域指向一個address_space結構。這樣,一個文件就對應一個address_space結構,一 個 address_space與一個偏移量可以肯定一個page cache 或swap cache中的一個頁面。所以,當要尋址某個數據時,很容易 根據給定的文件及數據在文件內的偏移量而找到相應的頁面。 (3) 進程調用mmap()時,只是在進程空間內新增了一塊相應大小的緩衝區,並設置了相應的訪問標識,但並無創建進程空間到物理頁面的映射。所以,第一次訪問該空間時,會引起一個缺頁異常。 


(4) 對 於共享內存映射狀況,缺頁異常處理程序首先在swap cache中尋找目標頁(符合address_space以及偏移量的物理頁),若是找到,則直接 返回地址;若是沒有找到,則判斷該頁是否在交換區 (swap area),若是在,則執行一個換入操做;若是上述兩種狀況都不知足,處理程序將分配新的 物理頁面,並把它插入到page cache中。進程最終將更新進程頁表。 

     注: 對於映射普通文件狀況(非共享映射),缺頁異常處理程序首先會在page cache中根據address_space以及數據偏移量尋找相應的頁面。如 果沒有找到,則說明文件數據尚未讀入內存,處理程序會從磁盤讀入相應的頁面,並返回相應地址,同時,進程頁表也會更新.
(5) 全部進程在映射同一個共享內存區域時,狀況都同樣,在創建線性地址與物理地址之間的映射以後,不論進程各自的返回地址如何,實際訪問的必然是同一個共享內存區域對應的物理頁面。 

相關文章
相關標籤/搜索