一、進程共享內存與mmap的關系
進程之間共享內存有兩種方式,分別是shm和mmap
1、shm共享內存是多個進程的虛擬地址直接共享同一塊物理內存區域,這種共享優點是進程間訪問速度更快,但是共享的內存大小有限
2、mmap共享內存是多個進程的虛擬地址空間共享磁盤的一段內存,有點是共享的內存可以很大,但是進程的訪問速度較慢
二、mmap共享內存
mmap是一種內存映射的方法,即將一個文件或其他對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中的一段虛擬地址的一一映射關系。
實現這樣的映射之后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必調用read、write等函數調用。
相反,內核空間對這段區域的修改也直接反映到用戶空間,從而實現不同進程之間的文件共享。如下圖所示:
虛擬內存區域(vm_area_struct)是進程的虛擬地址空間中的一個同質區間,即具有同樣特性的連續地址范圍。上圖中所示的text數據段(代碼段)、初始數據段、BSS數據段、堆、棧和內存映射,
都是一個獨立的虛擬內存區域。由上圖可以看出,進程的虛擬地址空間,由多個虛擬內存區域構成。
linux內核使用vm_area_struct結構來表示一個獨立的虛擬內存區域,由於每個不同質的虛擬內存區域功能和內部機制都不同,因此一個進程使用多個vm_area_struct結構來分別表示不同類型的虛擬內存區域。各個vm_area_struct結構使用鏈表或者樹形結構鏈接,方便進程快速訪問。
vm_area_struct結構中包含區域起始和終止地址以及其他相關信息,同時也包含一個vm_ops指針,其內部可引出所有針對這個區域可以使用的系統調用函數。mmap函數就是要創建一個新的vm_area_struct結構,並將其與文件的物理磁盤地址相連。
三、mmap內存映射原理:
mmap內存映射的實現過程,總的來說可以分為三個階段:
1、用戶空間內調用mmap,創建一個虛擬內存區域(vm_area_struct)
2、內核態調用mmap,實現磁盤地址到進程虛擬地址的映射,並且建立頁表(注意:1、內核空間的mmap和用戶空間的mmap不一樣。2、這里建立映射實際並不進行內存拷貝)
3、進程發起對這片映射空間的訪問(先在頁緩存中找),引發缺頁異常,實現文件內容到物理內存的拷貝
四、文件讀寫的基本流程
讀文件
1、進程調用庫函數向內核發起讀文件請求;
2、內核通過檢查進程的文件描述符定位到虛擬文件系統的已打開文件列表表項;
3、調用該文件可用的系統調用函數read()
3、read()函數通過文件表項鏈接到目錄項模塊,根據傳入的文件路徑,在目錄項模塊中檢索,找到該文件的inode;
4、在inode中,通過文件內容偏移量計算出要讀取的頁;
5、通過inode找到文件對應的address_space;
6、在address_space中訪問該文件的頁緩存樹,查找對應的頁緩存結點:
(1)如果頁緩存命中,那么直接返回文件內容;
(2)如果頁緩存缺失,那么產生一個頁缺失異常,創建一個頁緩存頁,同時通過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁;重新進行第6步查找頁緩存;
7、文件內容讀取成功。
寫文件
前5步和讀文件一致,在address_space中查詢對應頁的頁緩存是否存在:
6、如果頁緩存命中,直接把文件內容修改更新在頁緩存的頁中。寫文件就結束了。這時候文件修改位於頁緩存,並沒有寫回到磁盤文件中去。
7、如果頁緩存缺失,那么產生一個頁缺失異常,創建一個頁緩存頁,同時通過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁。此時緩存頁命中,進行第6步。
8、一個頁緩存中的頁如果被修改,那么會被標記成臟頁。臟頁需要寫回到磁盤中的文件塊。有兩種方式可以把臟頁寫回磁盤:
(1)手動調用sync()或者fsync()系統調用把臟頁寫回
(2)pdflush進程會定時把臟頁寫回到磁盤
同時注意,臟頁不能被置換出內存,如果臟頁正在被寫回,那么會被設置寫回標記,這時候該頁就被上鎖,其他寫請求被阻塞直到鎖釋放。
五、mmap和常規文件操作的區別:
函數的調用過程:
1、進程發起讀文件請求。
2、內核通過查找進程文件符表,定位到內核已打開文件集上的文件信息,從而找到此文件的inode。
3、inode在address_space上查找要請求的文件頁是否已經緩存在頁緩存中。如果存在,則直接返回這片文件頁的內容。
4、如果不存在,則通過inode定位到文件磁盤地址,將數據從磁盤復制到頁緩存。之后再次發起讀頁面過程,進而將頁緩存中的數據發給用戶進程。
總結來說,常規文件操作為了提高讀寫效率和保護磁盤,使用了頁緩存機制。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由於頁緩存處在內核空間,不能被用戶進程直接尋址,
所以還需要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中。這樣,通過了兩次數據拷貝過程,才能完成進程對文件內容的獲取任務。
寫操作也是一樣,待寫入的buffer在內核空間不能直接訪問,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回),也是需要兩次數據拷貝。
而使用mmap操作文件中,創建新的虛擬內存區域和建立文件磁盤地址和虛擬內存區域映射這兩步,沒有任何文件拷貝操作。而之后訪問數據時發現內存中並無數據而發起的缺頁異常過程,
可以通過已經建立好的映射關系,只使用一次數據拷貝,就從磁盤中將數據傳入內存的用戶空間中,供進程使用。
總而言之,常規文件操作需要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只需要從磁盤到用戶主存的一次數據拷貝過程。
說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不同數據不通的繁瑣過程。因此mmap效率更高。