自:http://hi.baidu.com/flying5/blog 不錯的部落格,大家可以去看看
Linux的mmap檔案記憶體映射機制
mmap: memory map
在講述檔案對應的概念時, 不可避免的要牽涉到虛存(SVR 4的VM). 實際上, 檔案對應是虛存的中心概念
,
檔案對應一方面給使用者提供了一組措施, 好似使用者將檔案對應到自己地址空間的某個部分, 使用簡單的記憶體訪問指令讀寫檔案;另一方面,
它也可以用於核心的基主要組織模式, 在這種模式種, 核心將整個地址空間視為諸如檔案之類的一組不同對象的映射. 中的傳統檔案訪問方式是,
首先用open系統調用開啟檔案, 然後使用read, write以及lseek等調用進行順序或者隨即的I/O. 這種方式是非常低效的,
每一次I/O操作都需要一次系統調用. 另外, 如果若干個進程訪問同一個檔案, 每個進程都要在自己的地址空間維護一個副本, 浪費了記憶體空間.
而如果能夠通過一定的機制將頁面映射到進程的地址空間中
, 也就是說首先通過簡單的產生某些記憶體管理資料結構完成映射的建立. 當進程訪問頁面時產生一個缺頁中斷, 核心將頁面讀入記憶體並且更新頁表指向該頁面
. 而且這種方式非常方便於同一副本的共用.
VM是物件導向的方法設計的, 這裡的對象是指記憶體對象: 記憶體對象是一個軟體抽象的概念
,
它描述記憶體區與備份存放區之間的映射. 系統可以使用多種類型的備份存放區, 比如交換空間, 本地或者遠程檔案以及幀緩衝等等.
VM系統對它們統一處理, 採用同一操作集操作, 比如讀取頁面或者回寫頁面等. 每種不同的備份存放區都可以用不同的方法實現這些操作. 這樣,
系統定義了一套統一的介面, 每種備份存放區給出自己的實現方法. 這樣, 進程的地址空間就被視為一組映射到不同資料對象上的的映射組成.
所有的有效地址就是那些映射到資料對象上的地址. 這些對象為映射它的頁面提供了持久性的備份存放區. 映射使得使用者可以直接定址這些對象
.
值得提出的是,
VM體繫結構獨立於Unix系統, 所有的Unix系統語義, 如本文, 資料及堆棧區都可以建構在基本VM系統之上. 同時,
VM體繫結構也是獨立於儲存管理的, 儲存管理是由作業系統實施的, 如: 究竟採取什麼樣的對換和請求調頁演算法,
究竟是採取分段還是分頁機制進行儲存管理, 究竟是如何將虛擬位址轉換成為物理地址等等(Linux中是一種叫Three Level Page
Table的機制), 這些都與記憶體對象的概念無關.
下面介紹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這些操作.
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……
介紹完VM的基本概念後, 我們可以講述mmap和munmap系統調用了. 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的函式宣告:
- 標頭檔:
- 原型: 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指定檔案應被映射到進程空間的起始地址,一般被指定一個null 指標,此時選擇起始地址的任務留給核心來完成。函數的傳回值為最後檔案對應到進程空 間的地址,進程可直接操作起始地址為該值的有效地址。
看看下面這個圖(來自《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
下面這個例子顯示了把檔案對應到記憶體的方法
原始碼是:
/************關於本文 檔******************************************** *filename: mmap.c *purpose: 說明調用mmap把檔案對應到記憶體的方法 *wrote by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com) Linux愛好者 Linux知識傳播者 SOHO族 開發人員 最擅長C語言 *date time:2008-01-27 18:59 上海大雪天,據說是多年不遇 *Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途 * 但請遵循GPL *Thanks to: * Ubuntu 本程式在Ubuntu 7.10系統上測試完全正常 * Google.com 我通常通過google搜尋發現許多有用的資料 *Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力 * 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻! *********************************************************************/ #include <sys/mman.h> /* for mmap and munmap */ #include <sys/types.h> /* for open */ #include <sys/stat.h> /* for open */ #include <fcntl.h> /* for open */ #include <unistd.h> /* for lseek and write */ #include <stdio.h> int main(int argc, char **argv) { int fd; char *mapped_mem, * p; int flength = 1024; void * start_addr = 0; fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); flength = lseek(fd, 1, SEEK_END); write(fd, "/0", 1); /* 在檔案最後添加一個Null 字元,以便下面printf正常工作 */ lseek(fd, 0, SEEK_SET); mapped_mem = mmap(start_addr, flength, PROT_READ, //允許讀 MAP_PRIVATE, //不允許其它進程訪問此記憶體地區 fd, 0); /* 使用映射地區. */ printf("%s/n", mapped_mem); /* 為了保證這裡工作正常,參數傳遞的檔案名稱最好是一個文字檔 */ close(fd); munmap(mapped_mem, flength); return 0; } |
編譯運行此程式:
gcc -Wall mmap.c
./a.out text_filename
上面的方法因為用了PROT_READ,所以只能讀取檔案裡的內容,不能修改,如果換成PROT_WRITE就可以修改檔案的內容了。又由於
用了MAAP_PRIVATE所以只能此進程使用此記憶體地區,如果換成MAP_SHARED,則可以被其它進程訪問,比如下面的:
#include <sys/mman.h> /* for mmap and munmap */ #include <sys/types.h> /* for open */ #include <sys/stat.h> /* for open */ #include <fcntl.h> /* for open */ #include <unistd.h> /* for lseek and write */ #include <stdio.h> #include <string.h> /* for memcpy */ int main(int argc, char **argv) { int fd; char *mapped_mem, * p; int flength = 1024; void * start_addr = 0; fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); flength = lseek(fd, 1, SEEK_END); write(fd, "/0", 1); /* 在檔案最後添加一個Null 字元,以便下面printf正常工作 */ lseek(fd, 0, SEEK_SET); start_addr = 0x80000; mapped_mem = mmap(start_addr, flength, PROT_READ|PROT_WRITE, //允許寫入 MAP_SHARED, //允許其它進程訪問此記憶體地區 fd, 0); /* 使用映射地區. */ printf("%s/n", mapped_mem); /* 為了保證這裡工作正常,參數傳遞的檔案名稱最好是一個文本文 */ while((p = strstr(mapped_mem, "Hello"))) { /* 此處來修改檔案 內容 */ memcpy(p, "Linux", 5); p += 5; } close(fd); munmap(mapped_mem, flength); return 0; } |
man -a mmap 看更詳細的資訊 http://hi.baidu.com/donghaozheng/blog/item/81885f0fdafa7b276059f3b1.html |