轉自:https://zhuanlan.zhihu.com/p/116896185
Linux中的虛擬內存機制和內存映射
最近學習了Linux中的虛擬內存,這個機制真的是非常的妙。虛擬內存可以說是硬件異常、主存、外存和操作系統的完美交互,而且更妙的是,這個機制是完全自動運行的。如果我們理解一點點虛擬內存的原理,就可以理解經常出現的內存錯誤的原因,還可以理解什么是內存映射mmap。
一、物理地址空間是什么
理解虛擬地址空間還得從物理地址空間開始說起。我們知道內存就像一個數組,每個存儲單元被分配了一個地址,這個地址就是物理地址,所有物理地址構成的集合就是物理地址空間。物理地址也就是真實的地址,對應真實的那個內存條。
如果CPU使用物理地址向內存尋址的話,就是下面這樣,這條指令中的地址就是數據真實存放的地址。
二、虛擬地址空間是什么
引入虛擬地址之后,對於每一個進程,操作系統提供一種假象,讓每個進程感覺自己擁有一個巨大的連續的內存可以使用,這個虛擬的空間甚至還可以比內存的容量還大。這個“假象”就是虛擬地址空間。虛擬地址是面向每個進程的,只是一個“假象”罷了。
此時CPU使用虛擬地址向內存尋址,通過專用的內存管理單元(MMU)硬件把虛擬地址轉換為真實的物理地址(地址翻譯),操作系統負責把虛擬地址和物理地址的映射關系維護在頁表之中。
指令中的地址不是數據真實存放的地址
三、程序和進程
當我們寫完代碼,編譯,鏈接並且生成可執行文件后,得到的這個東西就是一系列二進制代碼的集合,我們管這東西叫做程序,存儲在磁盤上。只有當我們執行這個文件后,程序才會被操作系統讀入內存運行,但是注意系統並不會把程序全部讀入內存,我們把正在運行的程序叫做進程。
從進程的視角來看,我的數據和代碼被存放在一個連續的空間之中,每個區域分別有着不同的功能。典型的如存放代碼的區域和存放數據的區域。
然而我們知道,這只是個假象。代碼和數據中的地址都是一個虛擬地址,還需要經過地址翻譯才能得到真正的物理地址。
四、分頁、頁表和缺頁異常
虛擬地址和物理地址的映射關系是以“頁”為單位的。分頁就是把整個虛擬內存和物理內存分割成大小固定的塊,以一個頁作為映射的最小單位。運行時,CPU請求一個虛擬地址,虛擬地址又被翻譯為物理地址,從而確定數據在內存中的哪個位置。下面的頁表中記錄了這個進程虛擬內存每個頁的映射關系。
當CPU尋址的時候,這個映射會有三種可能。
- 未分配:虛擬地址所在的那一頁並未被分配,代表沒有數據和他們關聯,這部分也不會占用內存。
- 未緩存:虛擬地址所在的那一頁被分配了,但並不在內存中。
- 已緩存:虛擬地址所在的那一頁就在內存中。
當訪問一個未緩存的區域時,系統將產生缺頁中斷,然后進程被阻塞,等待操作系統將缺失的那一頁從磁盤復制到內存。當復制完成后,CPU繼續執行導致缺頁中斷的那條指令,此時就會正常執行了。這種僅在需要的時候將頁面拷貝到內存的策略叫做按需調度頁面。 可以想象當程序被裝入內存的時候,開始時僅有有很小的一部分內容被放入內存。程序在運行中不斷缺頁,不斷的把需要的部分拷貝進內存。
從上面的圖中還可以看出,虛擬內存實際上就是磁盤的緩存。系統通過缺頁中斷的機制,小心的維護着每個進程的虛擬地址假象。
五、虛擬內存的應用
在Linux中,將一片虛擬內存和一個磁盤上的對象關聯起來,並用磁盤上的對象初始化這片虛擬內存,這個機制就叫做內存映射。
1、化簡資源的共享
當我們使用共享庫的函數時時,例如printf(),沒有必要為每個進程拷貝一本代碼,這樣太浪費內存了。我們只需要讓每個進程的一塊虛擬內存映射到相同的對象上就可以了。
假如進程1和進程2想要共享同一個文件。其中文件A已經被映射到進程2(文件被緩存在內存中),進程2還是通過缺頁中斷載入的文件A。
此時進程1也打開文件A,由於文件的名字在系統中是唯一的,操作系統清楚文件A已經被緩存在內存之中。因此系統將進程1的虛擬內存映射到相同內存之中,完成文件的共享。
2、零拷貝技術的一種實現
對於linux中最常用的I/O函數 read() 來說,文件先會被系統復制到內核空間的緩沖區,然后再復制到用戶空間。讀一個文件需要復制兩次顯然不是我們希望的,尤其是讀大文件的時候, sad...
通過內存映射,我們可以繞過內核緩沖,直接將文件A映射到虛擬內存,這里一共就發生一次拷貝,nice~
六、總結
虛擬內存無時無刻都在為我們工作,而且我們不需要任何干涉就能自動地工作。虛擬內存可以看成對磁盤的一個緩存,它通過缺頁中斷觸發操作系統處理訪問未緩存塊的問題。虛擬內存可以應用在處理共享對象、減少I/O開銷問題中。
虛擬內存十分的強大,以上介紹的僅僅是虛擬內存的一部分功能。如果文中出現錯誤歡迎指出,希望這篇文章對你有用。如果希望看到更多類似的內容請關注我的個人公眾號↓