很多文章分析了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
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!