linux中VM虛擬內存區域及mmap方法實現

linux中VM虛擬內存區域及mmap方法實現  
html


      文件映射是虛存的中心概念, 文件映射一方面給用戶提供了一組措施, 好似用戶將文件映射到本身地址空間的某個部分, 使用簡單的內存訪問指令讀寫文件;另外一方面, 它也能夠用於內核的基本組織模式, 在這種模式種, 內核將整個地址空間視爲諸如文件之類的一組不一樣對象的映射. 中的傳統文件訪問方式是, 首先用open系統調用打開文件, 而後使用read, write以及lseek等調用進行順序或者隨即的I/O. 這種方式是很是低效的, 每一次I/O操做都須要一次系統調用. 另外, 若是若干個進程訪問同一個文件,  每一個進程都要在本身的地址空間維護一個副本, 浪費了內存空間. 而若是可以經過必定的機制將頁面映射到進程的地址空間中, 也就是說首先經過簡單的產生某些內存管理數據結構完成映射的建立. 當進程訪問頁面時產生一個缺頁中斷, 內核將頁面讀入內存而且更新頁表指向該頁面. 並且這種方式很是方便於同一副本的共享。node

      VM是面向對象的方法設計的, 這裏的對象是指內存對象: 內存對象是一個軟件抽象的概念, 它描述內存區與後備存儲之間的映射. 系統可使用多種類型的後備存儲, 好比交換空間, 本地或者遠程文件以及幀緩存等等. VM系統對它們統一處理, 採用同一操做集操做, 好比讀取頁面或者回寫頁面等. 每種不一樣的後備存儲均可以用不一樣的方法實現這些操做. 這樣, 系統定義了一套統一的接口, 每種後備存儲給出本身的實現方法. 這樣, 進程的地址空間就被視爲一組映射到不一樣數據對象上的的映射組成. 全部的有效地址就是那些映射到數據對象上的地址.  這些對象爲映射它的頁面提供了持久性的後備存儲. 映射使得用戶能夠直接尋址這些對象。linux

 

 一個進程的內存區域能夠經過 /proc/pid/maps查看.pid爲具體的進程號.api

   08048000-0804f000 r-xp 00000000 08:01 573748 /sbin/rpc.statd #text數組

  ...............緩存

  每一行的域爲:數據結構

   start_end perm offset major:minior inodeapp

  start:該區域的起始虛擬地址ide

  end:該區域的結束虛擬地址函數

  perm:讀、寫和執行權限;表示對這個區域,容許進程作什麼。這個域的最後一個字符要麼是p 表示私有的。要麼是s表示共享的。

 offset:被映射部分在文件中的起始地址

 major minor :主次設備號

 inode:索引結點

Linux中VM的實現.

    一個進程應該包括一個mm_struct(memory manage struct), 該結構是進程虛擬地址空間的抽象描述, 裏面包括了進程虛擬空間的一些管理信息: start_code, end_code, start_data, end_data, start_brk, end_brk等等信息. 另外, 也有一個指向進程虛存區表(vm_area_struct: virtual memory area)的指針, 該鏈是按照虛擬地址的增加順序排列的. 在Linux進程的地址空間被分做許多區(vma), 每一個區(vma)都對應虛擬地址空間上一段連續的區域,  vma是能夠被共享和保護的獨立實體, 這裏的vma就是前面提到的內存對象. 下面是vm_area_struct的結構, 其中, 前半部分是公共的, 與類型無關的一些數據成員, 如: 指向mm_struct的指針, 地址範圍等等, 後半部分則是與類型相關的成員, 其中最重要的是一個指向vm_operation_struct向量表的指針vm_ops, vm_pos向量表是一組虛函數, 定義了與vma類型無關的接口. 每個特定的子類, 即每種vma類型都必須在向量表中實現這些操做. 這裏包括了: open, close, unmap, protect, sync, nopage, wppage, swapout這些操做.

一、內核內部的虛擬內存管理系統經過應用程序的調用的mmap()的變量,生產vm_area_struct結構體,從而內存映射,而後初始化該結構體的域,最後才傳遞的過程。二、應用程序鎖調用mmap()函數中使用的多數變量值由vma內核處理爲vm_area_struct結構體形式,而後傳遞到設備驅動程序上。

struct vm_area_struct {

/*公共的, 與vma類型無關的 */

struct mm_struct * vm_mm; 
unsigned long vm_start; 
unsigned long vm_end; 
struct vm_area_struct *vm_next; 
pgprot_t vm_page_prot; 
unsigned long vm_flags; 
short vm_avl_height; 
struct vm_area_struct * vm_avl_left; 
struct vm_area_struct * vm_avl_right; 
struct vm_area_struct *vm_next_share; 
struct vm_area_struct **vm_pprev_share;

/* 與類型相關的 */

struct vm_operations_struct * vm_ops; 
unsigned long vm_pgoff; 
struct file * vm_file; 
unsigned long vm_raend; 
void * vm_private_data;
}; 
vm_ops: open, close, no_page, swapin, swapout……

mmap調用實際上就是一個內存對象vma的建立過程, mmap的調用格式是:

void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

其中start是映射地址, length是映射長度, 若是flags的MAP_FIXED不被置位, 則該參數一般被忽略, 而查找進程地址空間中第一個長度符合的空閒區域;Fd是映射文件的文件句柄, offset是映射文件中的偏移地址;prot是映射保護權限, 能夠是PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE, flags則是指映射類型, 能夠是MAP_FIXED, MAP_PRIVATE, MAP_SHARED, 該參數必須被指定爲MAP_PRIVATE和MAP_SHARED其中之一,MAP_PRIVATE是建立一個寫時拷貝映射(copy-on-write), 也就是說若是有多個進程同時映射到一個文件上,  映射創建時只是共享一樣的存儲頁面, 可是某進程企圖修改頁面內容, 則複製一個副本給該進程私用, 它的任何修改對其它進程都不可見. 而MAP_SHARED則不管修改與否都使用同一副本, 任何進程對頁面的修改對其它進程都是可見的.

mmap系統調用的實現過程是: 
1.先經過文件系統定位要映射的文件; 
2.權限檢查, 映射的權限不會超過文件打開的方式, 也就是說若是文件是以只讀方式打開, 那麼則不容許創建一個可寫映射; 
3.建立一個vma對象, 並對之進行初始化; 
4.調用映射文件的mmap函數, 其主要工做是給vm_ops向量表賦值; 
5.把該vma鏈入該進程的vma鏈表中, 若是能夠和先後的vma合併則合併; 
6.若是是要求VM_LOCKED(映射區不被換出)方式映射, 則發出缺頁請求, 把映射頁面讀入內存中.

munmap(void * start, size_t length):

該調用能夠看做是mmap的一個逆過程. 它將進程中從start開始length長度的一段區域的映射關閉, 若是該區域不是剛好對應一個vma, 則有可能會分割幾個或幾個vma.

msync(void * start, size_t length, int flags):

把映射區域的修改回寫到後備存儲中. 由於munmap時並不保證頁面回寫, 若是不調用msync, 那麼有可能在munmap後丟失對映射區的修改. 其中flags能夠是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回寫完成後才返回, MS_ASYNC發出回寫請求後當即返回, MS_INVALIDATE使用回寫的內容更新該文件的其它映射. 該系統調用是經過調用映射文件的sync函數來完成工做的.

brk(void * end_data_segement):

將進程的數據段擴展到end_data_segement指定的地址, 該系統調用和mmap的實現方式十分類似, 一樣是產生一個vma, 而後指定其屬性. 不過在此以前須要作一些合法性檢查, 好比該地址是否大於mm->end_code, end_data_segement和mm->brk之間是否還存在其它vma等等. 經過brk產生的vma映射的文件爲空,  這和匿名映射產生的vma類似, 關於匿名映射不作進一步介紹. 庫函數malloc就是經過brk實現的.


===============================================

Linux提供了內存映射函數mmap, 它把文件內容映射到一段內存上(準確說是虛擬內存上), 經過對這段內存的讀取和修改, 實現對文件的讀取和修改, 先來看一下mmap的函數聲明:


頭文件: 
< unistd.h> 
< sys/mman.h>
原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize); 
返回值: 成功則返回映射區起始地址, 失敗則返回MAP_FAILED(-1). 
參數: 
addr: 指定映射的起始地址, 一般設爲NULL, 由系統指定. 
length: 將文件的多大長度映射到內存. 
prot: 映射區的保護方式, 能夠是: 
PROT_EXEC: 映射區可被執行. 
PROT_READ: 映射區可被讀取. 
PROT_WRITE: 映射區可被寫入. 
PROT_NONE: 映射區不能存取.
flags: 映射區的特性, 能夠是: 
MAP_SHARED: 對映射區域的寫入數據會複製迴文件, 且容許其餘映射該文件的進程共享. 
MAP_PRIVATE: 對映射區域的寫入操做會產生一個映射的複製(copy-on-write), 對此區域所作的修改不會寫回原文件. 
此外還有其餘幾個flags不很經常使用, 具體查看linux C函數說明.
fd: 由open返回的文件描述符, 表明要映射的文件. 
offset: 以文件開始處的偏移量, 必須是分頁大小的整數倍, 一般爲0, 表示從文件頭開始映射.

    下面說一下內存映射的步驟:


用open系統調用打開文件, 並返回描述符fd. 
用mmap創建內存映射, 並返回映射首地址指針start. 
對映射(文件)進行各類操做, 顯示(printf), 修改(sprintf). 
用munmap(void *start, size_t lenght)關閉內存映射. 
用close系統調用關閉文件fd.

注意事項:

在修改映射的文件時, 只能在原長度上修改, 不能增長文件長度, 由於內存是已經分配好的.


Linux-mmap函數介紹(轉)


mmap函數是unix/linux下的系統調用,來看《Unix Netword programming》卷二12.2節對mmap的介紹:
The mmap function maps either a file or a Posix shared memory object into the address space of a process.We use this function for three purposes:
1. with a regular file to provide memory-mapped I/O 
2. with special files to provide anonymous memory mappings
3. with shm_open to provide Posix shared memory between unrelated processes

         mmap系統調用並非徹底爲了用於共享內存而設計的。它自己提供了不一樣於通常對普通文件的訪問方式,進程能夠像讀寫內存同樣對普通文件的操做。而 Posix或系統V的共享內存IPC則純粹用於共享目的,固然mmap()實現共享內存也是其主要應用之一。
mmap系統調用使得進程之間經過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程能夠像訪問普通內存同樣對文件進行訪問,沒必要再 調用read(),write()等操做。

         咱們的程序中大量運用了mmap,用到的正是mmap的這種「像訪問普通內存同樣對文件進行訪問」的功能。實踐證實,當要對一個文件頻繁的進行訪問,而且 指針來回移動時,調用mmap比用常規的方法快不少。
來看看mmap的定義:
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

         參數fd爲即將映射到進程空間的文件描述字,通常由open()返回,同時,fd能夠指定爲-1,此時須指定flags參數中的MAP_ANON,代表進 行的是匿名映射(不涉及具體的文件名,避免了文件的建立及打開,很顯然只能用於具備親緣關係的進程間通訊)。

         len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。

         prot參數指定共享內存的訪問權限。可取以下幾個值的或:PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執 行),PROT_NONE(不可訪問)。

         flags由如下幾個常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。
若是指定爲MAP_SHARED,則對映射的內存所作的修改一樣影響到文件。若是是MAP_PRIVATE,則對映射的內存所作的修改僅對該進程可見,對 文件沒有影響。

         offset參數通常設爲0,表示從文件頭開始映射。

         參數addr指定文件應被映射到進程空間的起始地址,通常被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函數的返回值爲最後文件映射到進程空 間的地址,進程可直接操做起始地址爲該值的有效地址。

         看看下面這個圖(來自《Unix Netword programming》卷二12.2節),對mmap進一步加深印象:



         這裏再也不詳細介紹mmap的參數,讀者可參考mmap手冊頁或者《Unix Netword programming》卷二12.2節得到進一步的信息。
         最後,舉個例子來結束本節。4.2節說過,Fileinformation數組是以二進制的形式寫進一個叫inforindex的文件中。那麼,當要訪問 Fileinformation數組時,代碼相似這樣:
struct stat st;
char buffer=」 inforindex」;
Fileinformation *_fileinfoIndexptr = NULL;
if(stat(buffer,&st)<0)
{
fprintf(stderr,"error to stat %s\n",buffer);
exit(-1);
}

// mmap the inforindex to _fileinfoIndexptr
int fd=open(buffer, O_RDONLY);
if(fd<0)
{
printf("error to open %s\n",buffer);
exit(-1);
}
_fileinfoIndexptr   = (Fileinformation*)mmap(NULL,st.st_size, PROT_READ,MAP_SHARED,fd,0);


if(MAP_FAILED == _fileinfoIndexptr)
{
printf("error to mmap %s\n",buffer);
close(fd);
exit(-1);
}
close(fd);

示例代碼:http://zhoulifa.bokee.com/6614538.html

下面這個例子顯示了把文件映射到內存的方法 源代碼是:

相關文章
相關標籤/搜索