虛擬記憶體管理

來源:互聯網
上載者:User
MMU 

現代作業系統普遍採用虛擬記憶體管理(Virtual Memory Management)機制,這需要處理器中的MMU(Memory Management Unit,記憶體管理單元)提供支援,本節簡要介紹MMU的作用。

首先引入兩個概念,虛擬位址和物理地址。如果處理器沒有MMU,或者有MMU但沒有啟用,CPU執行單元發出的記憶體位址將直接傳到晶片引腳上,被記憶體晶片(以下稱為實體記憶體,以便與虛擬記憶體區分)接收,這稱為物理地址(Physical Address,以下簡稱PA),如所示。

圖 17.5. 物理地址

 

如果處理器啟用了MMU,CPU執行單元發出的記憶體位址將被MMU截獲,從CPU到MMU的地址稱為虛擬位址(Virtual Address,以下簡稱VA),而MMU將這個地址翻譯成另一個地址發到CPU晶片的外部地址引腳上,也就是將VA映射成PA,如所示。

圖 17.6. 虛擬位址

 

如果是32位處理器,則內地址匯流排是32位的,與CPU執行單元相連(圖中只是示意性地畫了4條地址線),而經過MMU轉換之後的外地址匯流排則不一定是32位的。也就是說,虛擬位址空間和物理地址空間是獨立的,32位處理器的虛擬位址空間是4GB,而物理地址空間既可以大於也可以小於4GB。

MMU將VA映射到PA是以頁(Page)為單位的,32位處理器的頁尺寸通常是4KB。例如,MMU可以通過一個映射項將VA的一頁0xb7001000~0xb7001fff映射到PA的一頁0x2000~0x2fff,如果CPU執行單元要訪問虛擬位址0xb7001008,則實際訪問到的物理地址是0x2008。實體記憶體中的頁稱為物理頁面或者頁幀(Page Frame)。虛擬記憶體的哪個頁面映射到實體記憶體的哪個頁幀是通過頁表(Page Table)來描述的,頁表儲存在實體記憶體中,MMU會尋找頁表來確定一個VA應該映射到什麼PA。

作業系統和MMU是這樣配合的:

  1. 作業系統在初始化或分配、釋放記憶體時會執行一些指令在實體記憶體中填寫頁表,然後用指令設定MMU,告訴MMU頁表在實體記憶體中的什麼位置。

  2. 設定好之後,CPU每次執行訪問記憶體的指令都會自動引發MMU做查表和地址轉換操作,地址轉換操作由硬體自動完成,不需要用指令控制MMU去做。

我們在程式中使用的變數和函數都有各自的地址,程式被編譯後,這些地址就成了指令中的地址,指令中的地址被CPU解釋執行,就成了CPU執行單元發出的記憶體位址,所以在啟用MMU的情況下,程式中使用的地址都是虛擬位址,都會引發MMU做查表和地址轉換操作。那為什麼要設計這麼複雜的記憶體管理機制呢?多了一層VA到PA的轉換到底換來了什麼好處?All problems in computer science can be solved by another level of indirection.還記得這句話嗎?多了一層間接必然是為瞭解決什麼問題的,等講完了必要的預備知識之後,將在第 5 節 “虛擬記憶體管理”討論虛擬記憶體管理機制的作用。

MMU除了做地址轉換之外,還提供記憶體保護機制。各種體繫結構都有使用者模式(User Mode)和特權模式(Privileged Mode)之分,作業系統可以在頁表中設定每個記憶體頁面的存取權限,有些頁面不允許訪問,有些頁面只有在CPU處於特權模式時才允許訪問,有些頁面在使用者模式和特權模式都可以訪問,存取權限又分為可讀、可寫和可執行三種。這樣設定好之後,當CPU要訪問一個VA時,MMU會檢查CPU當前處於使用者模式還是特權模式,訪問記憶體的目的是讀資料、寫資料還是取指令,如果和作業系統設定的頁面許可權相符,就允許訪問,把它轉換成PA,否則不允許訪問,產生一個異常(Exception)。異常的處理過程和中斷類似,不同的是中斷由外部裝置產生而異常由CPU內部產生,中斷產生的原因和CPU當前執行的指令無關,而異常的產生就是由於CPU當前執行的指令出了問題,例如訪問記憶體的指令被MMU檢查出許可權錯誤,除法指令的除數為0等都會產生異常。

圖 17.7. 處理器模式

 

通常作業系統把虛擬位址空間劃分為使用者空間和核心空間,例如x86平台的Linux系統虛擬位址空間是0x00000000~0xffffffff,前3GB(0x00000000~0xbfffffff)是使用者空間,後1GB(0xc0000000~0xffffffff)是核心空間。使用者程式載入到使用者空間,在使用者模式下執行,不能訪問核心中的資料,也不能跳轉到核心代碼中執行。這樣可以保護核心,如果一個進程訪問了非法地址,頂多這一個進程崩潰,而不會影響到核心和整個系統的穩定性。CPU在產生中斷或異常時不僅會跳轉到中斷或異常服務程式,還會自動切換模式,從使用者模式切換到特權模式,因此從中斷或異常服務程式可以跳轉到核心代碼中執行。事實上,整個核心就是由各種中斷和例外處理常式組成的。總結一下:在正常情況下處理器在使用者模式執行使用者程式,在中斷或異常情況下處理器切換到特權模式執行核心程式,處理完中斷或異常之後再返回使用者模式繼續執行使用者程式。

段錯誤我們已經遇到過很多次了,它是這樣產生的:

    1. 使用者程式要訪問的一個VA,經MMU檢查無權訪問。

    2. MMU產生一個異常,CPU從使用者模式切換到特權模式,跳轉到核心代碼中執行異常服務程式。

    3. 核心把這個異常解釋為段錯誤,把引發異常的進程終止掉。

http://learn.akae.cn/media/ch20s05.html

我們知道作業系統利用體繫結構提供的VA到PA的轉換機制實現虛擬記憶體管理。有了共用庫的基礎知識之後,現在我們可以進一步理解虛擬記憶體管理了。首先分析一個例子:

$ ps  PID TTY          TIME CMD29977 pts/0    00:00:00 bash30032 pts/0    00:00:00 ps$ cat /proc/29977/maps 08048000-080f4000 r-xp 00000000 08:15 688142     /bin/bash080f4000-080f9000 rw-p 000ac000 08:15 688142     /bin/bash080f9000-080fe000 rw-p 080f9000 00:00 0 09283000-09497000 rw-p 09283000 00:00 0          [heap]b7ca8000-b7cb2000 r-xp 00000000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.sob7cb2000-b7cb3000 r--p 00009000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.sob7cb3000-b7cb4000 rw-p 0000a000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so...b7e15000-b7f6d000 r-xp 00000000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.sob7f6d000-b7f6f000 r--p 00158000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.sob7f6f000-b7f70000 rw-p 0015a000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so...b7fbd000-b7fd7000 r-xp 00000000 08:15 565466     /lib/ld-2.8.90.sob7fd7000-b7fd8000 r-xp b7fd7000 00:00 0          [vdso]b7fd8000-b7fd9000 r--p 0001a000 08:15 565466     /lib/ld-2.8.90.sob7fd9000-b7fda000 rw-p 0001b000 08:15 565466     /lib/ld-2.8.90.sobfac5000-bfada000 rw-p bffeb000 00:00 0          [stack]

用ps命令查看當前終端下的進程,得知bash進程的id是29977,然後用cat /proc/29977/maps命令查看它的虛擬位址空間。/proc目錄中的檔案並不是真正的磁碟檔案,而是由核心虛擬出來的檔案系統,當前系統中啟動並執行每個進程在/proc下都有一個子目錄,目錄名就是進程的id,查看目錄下的檔案可以得到該進程的相關資訊。此外,用pmap 29977命令也可以得到類似的輸出結果。

圖 20.4. 進程地址空間

 

在第 4 節 “MMU”講過,x86平台的虛擬位址空間是0x0000 0000~0xffff ffff,大致上前3GB(0x0000 0000~0xbfff ffff)是使用者空間,後1GB(0xc000 0000~0xffff ffff)是核心空間,在這裡得到了印證。0x0804 8000-0x080f 4000是從/bin/bash載入到記憶體的,存取權限為r-x,表示Text Segment,包含.text段、.rodata段、.plt段等。0x080f 4000-0x080f 9000也是從/bin/bash載入到記憶體的,存取權限為rw-,表示Data Segment,包含.data段、.bss段等。

0x0928 3000-0x0949 7000不是從磁碟檔案載入到記憶體的,這段空間稱為堆(Heap),以後會講到用malloc函數動態分配記憶體是在這裡分配的。從0xb7ca 8000開始是共用庫的映射空間,每個共用庫也分為幾個Segment,每個Segment有不同的存取權限。可以看到,從堆空間的結束位址(0x0949 7000)到共用庫映射空間的起始地址(0xb7ca 8000)之間有很大的地址空洞,在動態分配記憶體時堆空間是可以向高地址增長的。堆空間的地址上限(0x09497000)稱為Break,堆空間要向高地址增長就要抬高Break,映射新的虛擬記憶體頁面到實體記憶體,這是通過系統調用brk實現的,malloc函數也是調用brk向核心請求分配記憶體的。

/lib/ld-2.8.90.so就是動態連結器/lib/ld-linux.so.2,後者是前者的符號連結。標有[vdso]的位址範圍是linux-gate.so.1的映射空間,我們講過這個共用庫是由核心虛擬出來的。0xbfac 5000-0xbfad a000是棧空間,其中高地址的部分儲存著進程的環境變數和命令列參數,低地址的部分儲存函數棧幀,棧空間是向低地址增長的,但顯然沒有堆空間那麼大的可供增長的餘地,因為實際的應用程式動態分配大量記憶體的並不少見,但是有幾十層深的函數調用並且每層調用都有很多局部變數的非常少見。總之,棧空間是可能用盡的,並且比堆空間更容易用盡,在第 3 節 “遞迴”講過,無窮遞迴會用盡棧空間最終導致段錯誤。

虛擬記憶體管理起到了什麼作用呢?可以從以下幾個方面來理解。

第一,虛擬記憶體管理可以控制實體記憶體的存取權限。實體記憶體本身是不限制訪問的,任何地址都可以讀寫,而作業系統要求不同的頁面具有不同的存取權限,這是利用CPU模式和MMU的記憶體保護機制實現的。例如,Text Segment被唯讀保護起來,防止被錯誤的指令意外改寫,核心地址空間也被保護起來,防止在使用者模式下執行錯誤的指令意外改寫核心資料。這樣,執行錯誤指令或惡意代碼的破壞能力受到了限制,頂多使當前進程因段錯誤終止,而不會影響整個系統的穩定性。

第二,虛擬記憶體管理最主要的作用是讓每個進程有獨立的地址空間。所謂獨立的地址空間是指,不同進程中的同一個VA被MMU映射到不同的PA,並且在某一個進程中訪問任何地址都不可能訪問到另外一個進程的資料,這樣使得任何一個進程由於執行錯誤指令或惡意代碼導致的非法記憶體訪問都不會意外改寫其它進程的資料,不會影響其它進程的運行,從而保證整個系統的穩定性。另一方面,每個進程都認為自己獨佔整個虛擬位址空間,這樣連結器和載入器的實現會比較容易,不必考慮各進程的位址範圍是否衝突。

繼續前面的實驗,再開啟一個終端視窗,看一下這個新的bash進程的地址空間,可以發現和先前的bash進程地址空間的布局差不多:

$ ps  PID TTY          TIME CMD30697 pts/1    00:00:00 bash30749 pts/1    00:00:00 ps$ cat /proc/30697/maps08048000-080f4000 r-xp 00000000 08:15 688142     /bin/bash080f4000-080f9000 rw-p 000ac000 08:15 688142     /bin/bash080f9000-080fe000 rw-p 080f9000 00:00 0 082d7000-084f9000 rw-p 082d7000 00:00 0          [heap]b7cf1000-b7cfb000 r-xp 00000000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.sob7cfb000-b7cfc000 r--p 00009000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.sob7cfc000-b7cfd000 rw-p 0000a000 08:15 581665     /lib/tls/i686/cmov/libnss_files-2.8.90.so...b7e5e000-b7fb6000 r-xp 00000000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.sob7fb6000-b7fb8000 r--p 00158000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.sob7fb8000-b7fb9000 rw-p 0015a000 08:15 581656     /lib/tls/i686/cmov/libc-2.8.90.so...b8006000-b8020000 r-xp 00000000 08:15 565466     /lib/ld-2.8.90.sob8020000-b8021000 r-xp b8020000 00:00 0          [vdso]b8021000-b8022000 r--p 0001a000 08:15 565466     /lib/ld-2.8.90.sob8022000-b8023000 rw-p 0001b000 08:15 565466     /lib/ld-2.8.90.sobff0e000-bff23000 rw-p bffeb000 00:00 0          [stack]

該進程也佔用了0x0000 0000-0xbfff ffff的地址空間,Text Segment也是0x0804 8000-0x080f 4000,Data Segment也是0x080f 4000-0x080f 9000,和先前的進程一模一樣,因為這些地址是在編譯連結時寫進/bin/bash這個可執行檔的,兩個進程都載入它。這兩個進程在同一個系統中同時運行著,它們的Data Segment佔用相同的VA,但是兩個進程各自幹各自的事情,顯然Data Segment中的資料應該是不同的,相同的VA怎麼會有不同的資料呢?因為它們被映射到不同的PA。如所示。

圖 20.5. 進程地址空間是獨立的

 

還可以看到,兩個進程都是bash進程,Text Segment是一樣的,並且Text Segment是唯讀,不會被改寫,因此作業系統會安排兩個進程的Text Segment共用相同的物理頁面。由於每個進程都有自己的一套VA到PA的映射表,整個地址空間中的任何VA都在每個進程自己的映射表中尋找相應的PA,因此不可能訪問到其它進程的地址,也就沒有可能意外改寫其它進程的資料。

另外,注意到兩個進程的共用庫載入地址並不相同,共用庫的載入地址是在運行時決定的,而不是寫在/bin/bash這個可執行檔中。但即使如此,也不影響兩個進程共用相同物理頁面中的共用庫,當然,只有唯讀部分是共用的,可讀可寫的部分不共用。

使用共用庫可以大大節省記憶體。比如libc,系統中幾乎所有的進程都映射libc到自己的進程地址空間,而libc的唯讀部分在實體記憶體中只需要存在一份,就可以被所有進程共用,這就是“共用庫”這個名稱的由來了。

現在我們也可以理解為什麼共用庫必須是位置無關代碼了。比如libc,不同的進程雖然共用libc所在的物理頁面,但這些物理頁面被映射到各進程的虛擬位址空間時卻位於不同的地址,所以要求libc的代碼不管載入到什麼地址都能正確執行。

第三,VA到PA的映射會給分配和釋放記憶體帶來方便,物理地址不連續的幾塊記憶體可以映射成虛擬位址連續的一塊記憶體。比如要用malloc分配一塊很大的記憶體空間,雖然有足夠多的空閑實體記憶體,卻沒有足夠大的連續空閑記憶體,這時就可以分配多個不連續的物理頁面而映射到連續的虛擬位址範圍。如所示。

圖 20.6. 不連續的PA可以映射為連續的VA

 

第四,一個系統如果同時運行著很多進程,為各進程分配的記憶體之和可能會大於實際可用的實體記憶體,虛擬記憶體管理使得這種情況下各進程仍然能夠正常運行。因為各進程分配的只不過是虛擬記憶體的頁面,這些頁面的資料可以映射到物理頁面,也可以臨時儲存到磁碟上而不佔用物理頁面,在磁碟上臨時儲存虛擬記憶體頁面的可能是一個磁碟分割,也可能是一個磁碟檔案,稱為交換裝置(Swap Device)。當實體記憶體不夠用時,將一些不常用的物理頁面中的資料臨時儲存到交換裝置,然後這個物理頁面就認為是閒置了,可以重新分配給進程使用,這個過程稱為換出(Page out)。如果進程要用到被換出的頁面,就從交換裝置再載入回實體記憶體,這稱為換入(Page in)。換出和換入操作統稱為換頁(Paging),因此:

系統中可分配的記憶體總量 = 實體記憶體的大小 + 交換裝置的大小

如所示。第一張圖是換出,將物理頁面中的資料儲存到磁碟,並解除地址映射,釋放物理頁面。第二張圖是換入,從閒置物理頁面中分配一個,將磁碟暫存的頁面載入回記憶體,並建立地址映射。

圖 20.7. 換頁

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.