在進入正題前先來談談操作系統內存管理機制的發展歷程,了解這些有利於我們更好的理解目前操作系統的內存管理機制。
一 早期的內存分配機制
在早期的計算機中,要運行一個程序,會把這些程序全都裝入內存,程序都是直接運行在內存上的,也就是說程序中訪問的內存地址都是實際的物理內存地址。當計算機同時運行多個程序時,必須保證這些程序用到的內存總量要小於計算機實際物理內存的大小。那當程序同時運行多個程序時,操作系統是如何為這些程序分配內存的呢?下面通過實例來說明當時的內存分配方法:
某台計算機總的內存大小是128M,現在同時運行兩個程序A和B,A需占用內存10M,B需占用內存110。計算機在給程序分配內存時會采取這樣的方法:先將內存中的前10M分配給程序A,接着再從內存中剩余的118M中划分出110M分配給程序B。這種分配方法可以保證程序A和程序B都能運行,但是這種簡單的內存分配策略問題很多。
圖一 早期的內存分配方法

問題1:進程地址空間不隔離。由於程序都是直接訪問物理內存,所以惡意程序可以隨意修改別的進程的內存數據,以達到破壞的目的。有些非惡意的,但是有bug的程序也可能不小心修改了其它程序的內存數據,就會導致其它程序的運行出現異常。這種情況對用戶來說是無法容忍的,因為用戶希望使用計算機的時候,其中一個任務失敗了,至少不能影響其它的任務。
問題2:內存使用效率低。在A和B都運行的情況下,如果用戶又運行了程序C,而程序C需要20M大小的內存才能運行,而此時系統只剩下8M的空間可供使用,所以此時系統必須在已運行的程序中選擇一個將該程序的數據暫時拷貝到硬盤上,釋放出部分空間來供程序C使用,然后再將程序C的數據全部裝入內存中運行。可以想象得到,在這個過程中,有大量的數據在裝入裝出,導致效率十分低下。
問題3:程序運行的地址不確定。當內存中的剩余空間可以滿足程序C的要求后,操作系統會在剩余空間中隨機分配一段連續的20M大小的空間給程序C使用,因為是隨機分配的,所以程序運行的地址是不確定的。
二 分段
為了解決上述問題,人們想到了一種變通的方法,就是增加一個中間層,利用一種間接的地址訪問方法訪問物理內存。按照這種方法,程序中訪問的內存地址不再是實際的物理內存地址,而是一個虛擬地址,然后由操作系統將這個虛擬地址映射到適當的物理內存地址上。這樣,只要操作系統處理好虛擬地址到物理內存地址的映射,就可以保證不同的程序最終訪問的內存地址位於不同的區域,彼此沒有重疊,就可以達到內存地址空間隔離的效果。
當創建一個進程時,操作系統會為該進程分配一個4GB大小的虛擬進程地址空間。之所以是4GB,是因為在32位的操作系統中,一個指針長度是4字節,而4字節指針的尋址能力是從0x00000000~0xFFFFFFFF,最大值0xFFFFFFFF表示的即為4GB大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。如果你的計算機上安裝了512M大小的內存,那么這個物理地址空間表示的范圍是0x00000000~0x1FFFFFFF。當操作系統做虛擬地址到物理地址映射時,只能映射到這一范圍,操作系統也只會映射到這一范圍。當進程創建時,每個進程都會有一個自己的4GB虛擬地址空間。要注意的是這個4GB的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。那是不是這4GB的虛擬地址空間應用程序可以隨意使用呢?很遺憾,在Windows系統下,這個虛擬地址空間被分成了4部分:NULL指針區、用戶區、64KB禁入區、內核區。應用程序能使用的只是用戶區而已,大約2GB左右(最大可以調整到3GB)。內核區為2GB,內核區保存的是系統線程調度、內存管理、設備驅動等數據,這部分數據供所有的進程共享,但應用程序是不能直接訪問的。
人們之所以要創建一個虛擬地址空間,目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建立一種映射關系。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段(Sagmentation)的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說虛擬地址空間中某個10M大小的空間映射到物理地址空間中某個10M大小的空間。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的
物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。還是以實例說明,假設有兩個進程A和B,進程A所需內存大小為10M,其虛擬地址空間分布在0x00000000到0x00A00000,進程B所需內存為100M,其虛擬地址空間分布為0x00000000到0x06400000。那么按照分段的映射方法,進程A在物理內存上映射區域為0x00100000到0x00B00000,,進程B在物理內存上映射區域為0x00C00000到0x07000000。於是進程A和進程B分別被映射到了不同的內存區間,彼此互不重疊,實現了地址隔離。從應用程序的角度看來,進程A的地址空間就是分布在0x00000000到0x00A00000,在做開發時,開發人員只需訪問這段區間上的地址即可。應用程序並不關心進程A究竟被映射到物理內存的那塊區域上了,所以程序的運行地址也就是相當於說是確定的了。 圖二顯示的是分段方式的內存映射方法。
圖二 分段方式的內存映射方法

這種分段的映射方法雖然解決了上述中的問題一和問題三,但並沒能解決問題二,即內存的使用效率問題。在分段的映射方法中,每次換入換出內存的都是整個程序,這樣會造成大量的磁盤訪問操作,導致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實際上,程序的運行有局部性特點,在某個時間段內,程序只是訪問程序的一小部分數據,也就是說,程序的大部分數據在一個時間段內都不會被用到。基於這種情況,人們想到了粒度更小的內存分割和映射方法,這種方法就是分頁(Paging)。
三 分頁
分頁的基本方法是,將地址空間分成許多的頁。每頁的大小由CPU決定,然后由操作系統選擇頁的大小。目前Inter系列的CPU支持4KB或4MB的頁大小,而PC上目前都選擇使用4KB。按這種選擇,4GB虛擬地址空間共可以分成1048576個頁,512M的物理內存可以分為131072個頁。顯然虛擬空間的頁數要比物理空間的頁數多得多。
在分段的方法中,每次程序運行時總是把程序全部裝入內存,而分頁的方法則有所不同。分頁的思想是程序運行時用到哪頁就為哪頁分配內存,沒用到的頁暫時保留在硬盤上。當用到這些頁時再在物理地址空間中為這些頁分配內存,然后建立虛擬地址空間中的頁和剛分配的物理內存頁間的映射。
下面通過介紹一個可執行文件的裝載過程來說明分頁機制的實現方法。一個可執行文件(PE文件)其實就是一些編譯鏈接好的數據和指令的集合,它也會被分成很多頁,在PE文件執行的過程中,它往內存中裝載的單位就是頁。當一個PE文件被執行時,操作系統會先為該程序創建一個4GB的進程虛擬地址空間。前面介紹過,虛擬地址空間只是一個中間層而已,它的功能是利用一種映射機制將虛擬地址空間映射到物理地址空間,所以,創建4GB虛擬地址空間其實並不是要真的創建空間,只是要創建那種映射機制所需要的數據結構而已,這種數據結構就是頁目和頁表。
當創建完虛擬地址空間所需要的數據結構后,進程開始讀取PE文件的第一頁。在PE文件的第一頁包含了PE文件頭和段表等信息,進程根據文件頭和段表等信息,將PE文件中所有的段一一映射到虛擬地址空間中相應的頁(PE文件中的段的長度都是頁長的整數倍)。這時PE文件的真正指令和數據還沒有被裝入內存中,操作系統只是根據PE文件的頭部等信息建立了PE文件和進程虛擬地址空間中頁的映射關系而已。當CPU要訪問程序中用到的某個虛擬地址時,當CPU發現該地址並沒有相相關聯的物理地址時,CPU認為該虛擬地址所在的頁面是個空頁面,CPU會認為這是個頁錯誤(Page Fault),CPU也就知道了操作系統還未給該PE頁面分配內存,CPU會將控制權交還給操作系統。操作系統於是為該PE頁面在物理空間中分配一個頁面,然后再將這個物理頁面與虛擬空間中的虛擬頁面映射起來,然后將控制權再還給進程,進程從剛才發生頁錯誤的位置重新開始執行。由於此時已為PE文件的那個頁面分配了內存,所以就不會發生頁錯誤了。隨着程序的執行,頁錯誤會不斷地產生,操作系統也會為進程分配相應的物理頁面來滿足進程執行的需求。
分頁方法的核心思想就是當可執行文件執行到第x頁時,就為第x頁分配一個內存頁y,然后再將這個內存頁添加到進程虛擬地址空間的映射表中,這個映射表就相當於一個y=f(x)函數。應用程序通過這個映射表就可以訪問到x頁關聯的y頁了。
怎樣通俗的理解操作系統中內存管理分頁和分段?
鏈接:https://www.zhihu.com/question/50796850/answer/522734117
來源:知乎
理解分段和分頁,那么得理解為什么會出現分段和分頁的技術
首先,這兩個技術都是為了利用和管理好計算機的資源--內存。
在分段這個技術還沒有出現之前,程序運行是需要從內存中分配出足夠多的連續的內存,然后把整個程序裝載進去。舉個例子,某個程序大小是10M,然后,就需要有連續的10M內存空間才能把這個程序裝載到內存里面。如果無法找到連續的10M內存,就無法把這個程序裝載進內存里面,程序也就無法得到運行。
上面這種直接把整個程序裝載進內存的方式是有一定的問題的。例如:
1、地址空間不隔離
如何理解地址空間不隔離?
舉個例子,假設我有兩個程序,一個是程序A,一個是程序B。程序A在內存中的地址假設是0x00000000~0x00000099,程序B在內存中的地址假設是0x00000100~x00000199。那么假設你在程序A中,本來想操作地址0x00000050,不小心手殘操作了地址0x00000150,那么,不好的事情或許會發生。你影響了程序A也就罷了,你把程序B也搞了一頓。
2、程序運行時候的地址不確定
如何理解程序運行時候的地址不確定?
因為我們程序每次要運行的時候,都是需要裝載到內存中的,假設你在程序中寫死了要操作某個地址的內存,例如你要地址0x00000010。但是問題來了,你能夠保證你操作的地址0x00000010真的就是你原來想操作的那個位置嗎?很可能程序第一次裝載進內存的位置是0x00000000~0x00000099,而程序第二次運行的時候,這個程序裝載進內存的位置變成了0x00000200~0x00000299,而你操作的0x00000010地址壓根就不是屬於這個程序所占有的內存。
3、內存使用率低下
如何理解內存使用率低下呢?
舉個例子,假設你寫了3個程序,其中程序A大小為10M,程序B為70M,程序C的大小為30M你的計算機的內存總共有100M。
這三個程序加起來有110M,顯然這三個程序是無法同時存在於內存中的。
並且最多只能夠同時運行兩個程序。可能是這樣的,程序A占有的內存空間是0x00000000~0x00000009,程序B占有的內存空間是0x00000010~0x00000079。假設這個時候程序C要運行該怎么做?可以把其中的一個程序換出到磁盤上,然后再把程序C裝載到內存中。假設是把程序A換出,那么程序C還是無法裝載進內存中,因為內存中空閑的連續區域有兩塊,一塊是原來程序A占有的那10M,還有就是從0x00000080~0x00000099這20M,所以,30M的程序C無法裝載進內存中。那么,唯一的辦法就是把程序B換出,保留程序A,但是,此時會有60M的內存無法利用起來,很浪費對吧。
然后,人們就去尋求一種辦法來解決這些問題。
有一句話說的好:計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決。
(這種思想在現在也用的很廣泛,例如很多優秀的中間層:Nginx、Redis等等)
所以,分段這種技術就出現了。
為了實現分段的這個技術,需要引入虛擬地址空間的概念。那么什么是地址空間呢?簡單的說就是可以尋址的一片空間。如果這個空間是虛擬的,我們就叫做虛擬地址空間;如果這個空間是真實存在的,我們就叫做物理地址空間。虛擬地址空間是可以任意的大的,因為是虛擬的。而物理地址空間是真實存在的,所以是有限的。
然后,分段這個技術做了一件什么事情呢?
它把虛擬地址空間映射到了物理地址空間,並且你寫的程序操作的是虛擬地址。假設,程序A的虛擬地址空間是0x00000100~0x00000200。此時,不僅需要一塊連續的物理內存來存放程序A,還需要把程序A的虛擬地址空間映射到(轉換為)物理地址空間。可能,程序A的虛擬地址空間從0x00000100~0x00000200映射到了物理地址空間0x00000000~0x00000100。
那么分段的技術可以解決什么問題呢?可以解決上面1、2兩個問題。
在問題1中,假設程序A的虛擬地址空間是0x00000000~0x00000099,映射到的物理地址空間是0x00000600~0x00000699,程序B的虛擬地址空間是0x00000100~0x00000199,映射到的物理地址空間是0x00000300~0x00000399。假設你還是手殘,在程序A中操作了地址0x00000150,但是英文此時的地址0x00000150是虛擬的,而虛擬化的操作是在操作系統的掌控中的,所以,操作系統有能力判斷,這個虛擬地址0x00000150是有問題的,然后阻止后續的操作。所以,體現出了隔離性。(另一種體現隔離性的方式就是,操作同一個虛擬地址,實際上可能操作的是不同的物理地址)
(注意,實際上,很可能程序A和程序B的虛擬地址都是0x00000000~0x00000099。這里的舉例只是為了方便理解。)
問題2也很好的解決了。正是因為這種映射,使得程序無需關注物理地址是多少,只要虛擬地址沒有改變,那么,程序就不會操作地址不當。
但是問題3仍然沒有解決。
因為第三個問題是換入換出的問題,這個問題的關鍵是能不能在換出一個完整的程序之后,把另一個完整的程序換進來。而這種分段機制,映射的是一片連續的物理內存,所以問題3得不到解決。
而問題出在哪呢?就是完整和連續。
而分頁技術的出現就是為了解決這個問題的。分頁這個技術仍然是一種虛擬地址空間到物理地址空間映射的機制。但是,粒度更加的小了。單位不是整個程序,而是某個“頁”,一段虛擬地址空間組成的某一頁映射到一段物理地址空間組成的某一頁。(如何理解這個“頁”的概念,這個問題下的其他同學回答過)
分頁這個技術,它的虛擬地址空間仍然是連續的,但是,每一頁映射后的物理地址就不一定是連續的了。正是因為有了分頁的概念,程序的換入換出就可以以頁為單位了。那么,為什么就可以只換出某一頁呢?實際上,不是為什么可以換出某一頁,而是可以換出CPU還用不到的那些程序代碼、數據。但是,把這些都換出到磁盤,萬一下次CPU就要使用這些代碼和數據怎么辦?又得把這些代碼、數據裝載進內存。性能有影響對吧。所以,我們把換入換出的單位變小,變成了“頁”。(實際上,這利用了空間局部性)
所以,分段和分頁的區別在於:粒度
