mmap 和 read 系統流程
在linux文件系統中,通常使用open(), read()讀取文件,但操作系統同樣提供了mmap()作為讀取文件的方式,而這兩者有什么不同呢?什么時候用read(), 什么時候用mmap()?
首先,read 的通常使用方法是 read(fd, buffer, size),將要讀取的數據讀到buffer中。這就涉及到兩個步驟,read是系統調用函數,每次使用read都要進入內核態,進行上下文切換。內核首先將文件數據從磁盤讀入page cache緩存,再將數據從page cache拷貝到buffer中。上下文切換和拷貝要消耗一定性能。
而如果使用 mmap 命令,VFS(虛擬文件系統)會分配對應的虛擬內存空間,記錄目標文件的 inode 和其他屬性,將起始虛擬地址返回給進程。當進程想要訪問某部分數據時,需要進行地址翻譯,但此時沒有更新頁表,會觸發缺頁中斷。linux根據VMA中記錄的 inode 信息,調用對應的文件系統進行處理。文件系統讀取該頁,返回給VFS,VFS再更新頁表,返回對應的物理頁。
在 mmap 之后,后續的讀寫操作都是在內存中進行,不需要再讀磁盤和進入內核態。
mmap的優點
因此 mmap 比起 read ,有如下優勢:
- 對於隨機訪問,不用頻繁 lseek。因為 mmap 是將整個文件映射到虛擬空間,在讀取時再按需分配物理內存。
- 減少后續系統調用次數。后續讀文件時不需要再進入內核態,減少了上下文切換
- 減少數據拷貝。免去了page cache 到 buffer 的數據拷貝。
- 當多個進程將同一頁面映射到內存時,數據可以在這些進程之間共享。對於 只讀 的頁面可以完全共享,需要寫入的文件可以使用COW(copy on write)私有化。這樣節省了大量內存。
mmapalso allows the operating system to optimize paging operations. For example, consider two programs; programAwhich reads in a1MBfile into a buffer creating withmalloc, and program B whichmmapsthe 1MB file into memory. If the operating system has to swap part ofA's memory out, it must write the contents of the buffer to swap before it can reuse the memory. InB's case any unmodifiedmmap'd pages can be reused immediately because the OS knows how to restore them from the existing file they weremmap'd from. (The OS can detect which pages are unmodified by initially marking writablemmap'd pages as read only and catching seg faults, similar to Copy on Write strategy).
mmap 還可以優化操作系統分頁。對於進程A、B,如果A通過 read 讀取了1MB數據到buffer中,而B通過 mmap 讀取1MB數據。如果OS想要把A中的 buffer 換入磁盤,首先要將buffer中的內容寫入磁盤,才可以重用該物理頁。而對於B中沒有被修改過的 mmap 頁,OS可以直接重用,因為OS可以從文件中再重新讀取該頁來恢復數據。
那么,如果 mmap 比起 open(),read() 有這么多優點,為什么不用 mmap 呢?對於系統來說,有優點往往意味着存在對應的缺點,這才是系統設計中的trade off。
mmap的缺點
- mmap 每次以頁為單位從文件中讀取數據,因此映射的頁面大小始終是整數。對於小文件可能會造成較多的內部碎片。同時,在讀取數據時也需要顯式修正數據在頁面中的偏移量。
- mmap 需要連續的虛擬內存空間用於儲存文件,如果文件較大,對於32位地址空間的系統來說,可能找不到足夠大的連續區域。
- mmap 本身開銷比 read 大,因為mmap涉及更多的系統調用,需要觸發缺頁中斷,更改虛擬內存映射。
總結
由於read 讀取文件更加直觀和易於理解,因此初學者依然使用 read 較多。但如果需要隨機訪問數據,或者和其他進程共享數據,用 mmap 不失為一個更好的選擇。
