內存映射mmap


1.mmap


  mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的虛擬地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必再調用read,write等系統調用函數。

  mmap()函數返回一個指針ptr,它指向進程虛擬地址空間的一個地址,這樣進程就可以直接通過ptr對文件進行讀寫。

  另外建立映射時並沒有數據拷貝,此時和物理內存無關。只有操作ptr進行數據讀寫的時候,才會把數據讀寫到物理內存,修改過的臟頁面並不會立即更新回文件中,而是有一段時間的延遲,可以調用msync()來強制同步, 這樣所寫的內容就能立即保存到文件里了。

  使用mmap需要注意的一個關鍵點是,mmap映射區域大小必須是物理頁大小(page_size)的整倍數(32位系統中通常是4k字節)。原因是,內存的最小粒度是頁,而進程虛擬地址空間和內存的映射也是以頁為單位。為了匹配內存的操作,mmap從磁盤到虛擬地址空間的映射也必須是頁,例如映射的文件大小是5000字節,但是需要用兩個頁,所以實際映射的內存為8192字節。另外如果映射的大小大於文件大小,則操作超出的部分會返回異常。

  映射建立之后,即使文件關閉,映射依然存在,另外如果文件的大小一直在擴張,只要在映射區域范圍內的數據,進程都可以合法得到,這和映射建立時文件的大小無關。因為映射的是磁盤的地址,不是文件本身,和文件句柄無關。同時可用於進程間通信的有效地址空間不完全受限於被映射文件的大小,因為是按頁映射。

2.相關函數


  void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

    參數addr表示使用某個特定的內存地址(當前進程的某個虛擬地址),通常傳NULL。

    參數length表示映射的內存長度。

    參數prot用於設置訪問這段內存的權限。包括:PROT_EXEC(執行)、PROT_READ(讀取)、 PROT_WRITE(寫入)、 PROT_NONE(不可訪問)。

    參數flags用於控制程序對內存段改變的影響。

  int msync ( void * addr, size_t len, int flags)

    進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,往往在調用munmap()后才執行該操作。可以通過調用msync()函數來實現磁盤文件內容與共享內存區中的內容一致,即同步操作。

    內存段需要同步的部分由addr和len確定。flags參數控制修改的具體方式,可用的值為:MS_ASYNC(異步寫入)/MS_SYNC(同步寫入)/MS_INVALIDATE(從文件中讀回數據)。

  int munmap(void *addr, size_t length); 

    該函數用於解除映射關系,釋放內存段。

3.mmap和常規文件操作的區別


  常規文件操作,也就是buffer IO,為了提高讀寫效率保護磁盤,使用了頁緩存機制,這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由於頁緩存處在內核空間,不能被用戶進程直接尋址,所以還需要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中。這樣,通過了兩次數據拷貝過程,才能完成進程對文件內容的獲取任務。寫操作也是一樣,待寫入的buffer在內核空間不能直接訪問,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回),也是需要兩次數據拷貝。

  而使用mmap操作文件中,創建新的虛擬內存區域和建立文件磁盤地址和虛擬內存區域映射這兩步,沒有任何文件拷貝操作。而之后訪問數據時發現內存中並無數據而發起的缺頁異常過程,可以通過已經建立好的映射關系,只使用一次數據拷貝,就從磁盤中將數據傳入內存的用戶空間中,供進程使用。

  總而言之,常規文件操作需要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只需要從磁盤到用戶主存的一次數據拷貝過程。mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不同數據不通的繁瑣過程。因此mmap效率更高。

4.mmap用於共享內存的兩種方式


  一是使用普通文件,多個進程通過映射到同一個普通文件,進行通信。

  二是使用匿名映射,此時用於父子進程中,首先在父進程調用mmap(),之后調用fork(),子進程繼承父進程匿名映射后的地址空間,同樣也繼承mmap()返回的地址,這樣,父子進程就可以通過映射區域進行通信了。

5.問題


  問題1:文件大小5000字節,映射大小為10000字節,由於會映射2個頁也就是8192字節,所以操作8192字節之內的數據不會發生錯誤(5000-8192之內會讀到全0,並且寫這部分內存時不會同步到文件),但是讀寫8192之后的數據會發生SIGBUS錯誤。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM