Mmap的實現原理和應用

很多文章分析了mmap的實現原理。從代碼的邏輯來分析,總是覺沒有把mmap後讀寫映射區域和普通的read/write聯繫起來。不得不產生疑問:

1,普通的read/write和mmap後的映射區域的讀寫到底有什麼區別。

2, 爲什麼有時候會選擇mmap而放棄普通的read/write。

3,如果文章中的內容有不對是或者是不妥的地方,歡迎大家指正。

 

圍繞着這兩個問題分析一下,其實在考慮這些問題的同時不免和其他的很多系統機制產生交互。雖然是講解mmap,但是很多知識還是爲了闡明問題做必要的鋪墊。這些知識也正是linux的繁瑣所在。一個應用往往和系統中的多種機制交互。這篇文章中儘量減少對源代碼的引用和分析。把這樣的工作留到以後的細節分析中。但是很多分析的理論依據還是來自於源代碼。可見源代碼的重要地位。


基礎知識:

1, 進程每次切換後,都會在tlb base寄存器中重新load屬於每一個進程自己的地址轉換基地址。在cpu當前運行的進程中都會有current宏來表示當前的進程的信息。應爲這個代碼實現涉及到硬件架構的問題,爲了避免差異的存在在文章中用到硬件知識的時候還是指明是x86的架構,畢竟x86的資料和分析的研究人員也比較多。其實arm還有其他類似的RISC的芯片,只要有mmu支持的,都會有類似的基地址寄存器。

2, 在系統運行進程之前都會爲每一個進程分配屬於它自己的運行空間。並且這個空間的有效性依賴於tlb base中的內容。32位的系統中訪問的空間大小爲4G。在這個空間中進程是“自由”的。所謂“自由”不是說對於4G的任何一個地址或者一段空間都可以訪問。如果要訪問,還是要遵循地址有效性,就是tlb base中所指向的任何頁錶轉換後的物理地址。其中的有效性有越界,權限等等檢查。

3, 任何一個用戶進程的運行在系統分配的空間中。這個空間可以有
vma:struct vm_area_struct來表示。所有的運行空間可以有這個結構體描述。用戶進程可以分爲text data 段。這些段的具體在4G中的位置有不同的vma來描述。Vma的管理又有其他機制保證,這些機制涉及到了算法和物理內存管理等。請看一下兩個圖片:

圖 一:


圖 二:


系統調用中的write和read:

 這裏沒有指定確切的文件系統類型作爲分析的對象。找到系統調用號,然後確定具體的文件系統所帶的file operation。在特定的file operation中有屬於每一種文件系統自己的操作函數集合。其中就有read和write。

圖 三:

在真正的把用戶數據讀寫到磁盤或者是存儲設備前,內核還會在page cache中管理這些數據。這些page的存在有效的管理了用戶數據和讀寫的效率。用戶數據不是直接來自於應用層,讀(read)或者是寫入(write)磁盤和存儲介質,而是被一層一層的應用所劃分,在每一層次中都會有不同的功能對應。最後發生交互時,在最恰當的時機觸發磁盤的操作。通過IO驅動寫入磁盤和存儲介質。這裏主要強調page cache的管理。應爲page的管理設計到了緩存,這些緩存以page的單位管理。在沒有IO操作之前,暫時存放在系統空間中,而並未直接寫入磁盤或者存貯介質。

系統調用中的mmap:

當創建一個或者切換一個進程的同時,會把屬於這個當前進程的系統信息載入。這些系統信息中包含了當前進程的運行空間。當用戶程序調用mmap後。函數會在當前進程的空間中找到適合的vma來描述自己將要映射的區域。這個區域的作用就是將mmap函數中文件描述符所指向的具體文件中內容映射過來。

原理是:mmap的執行,僅僅是在內核中建立了文件與虛擬內存空間的對應關係。用戶訪問這些虛擬內存空間時,頁面表裏面是沒有這些空間的表項的。當用戶程序試圖訪問這些映射的空間時,於是產生缺頁異常。內核捕捉這些異常,逐漸將文件載入。所謂的載入過程,具體的操作就是read和write在管理pagecache。Vma的結構體中有很文件操作集。vma操作集中會有自己關於page cache的操作集合。這樣,雖然是兩種不同的系統調用,由於操作和調用觸發的路徑不同。但是最後還是落實到了page cache的管理。實現了文件內容的操作。

Ps:

文件的page cache管理也是很好的內容。涉及到了address space的操作。其中很多的內容和文件操作相關。

效率對比:

    這裏應用了網上一篇文章。發現較好的分析,着這裏引用一下。

Mmap:

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <sys/mman.h>

void main()

{

int fd = open("test.file", 0);

struct stat statbuf;

char *start;

char buf[2] = {0};

int ret = 0;

fstat(fd, &statbuf);

start = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

do {

*buf = start[ret++];

}while(ret < statbuf.st_size);

}  

Read:

#include <stdio.h>

#include <stdlib.h>

void main()

{

FILE *pf = fopen("test.file", "r");

char buf[2] = {0};

int ret = 0;

do {

ret = fread(buf, 1, 1, pf);

}while(ret);

}   


運行結果:

[xiangy@compiling-server test_read]$ time ./fread

real    0m0.901s

user    0m0.892s

sys     0m0.010s

[xiangy@compiling-server test_read]$ time ./mmap

real    0m0.112s

user    0m0.106s

sys     0m0.006s

[xiangy@compiling-server test_read]$ time ./read

real    0m15.549s

user    0m3.933s

sys     0m11.566s

[xiangy@compiling-server test_read]$ ll test.file

-rw-r--r-- 1 xiangy svx8004 23955531 Sep 24 17:17 test.file   


可以看出使用mmap後發現,系統調用所消耗的時間遠遠比普通的read少很多。
---------------------  
作者:小陸zi  
來源:CSDN  
原文:https://blog.csdn.net/edwardlulinux/article/details/8604400  
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章