操作系統的兩個角色分別是魔術師和管理者,在管理者這個角色中,除了CPU之外,內存是操作系統要管理的另外一個重要資源。內存管理需要達到兩個目標:一是地址保護,即一個程序不能訪問另一個程序的地址空間。二是地址獨立,即程序發出的地址應該與物理主存地址無關。這兩個目標就是衡量一個內存管理系統是否完善的標准,它是所有內存管理系統必須提供的基本抽象。
一、內存管理二三事
1.1 內存管理的目標
(1)地址保護:一個程序不能訪問另一個程序地址空間。
(2)地址獨立:程序發出的地址應該與物理主存地址無關。
這兩個目標是衡量一個內存管理系統是否完善的標准,它是所有內存管理系統必須提供的基本抽象。
1.2 虛擬內存的概念
虛擬內存的中心思想是將物理主存擴大到便宜、大容量的磁盤上,即將磁盤空間看做主存空間的一部分。可以理解為是將書桌上的比較老的文件先暫時收到抽屜里,用空出來的地方來攤開新的文件。在計算機中,體現在在內存容量不足時將不經常訪問的內存空間中的數據寫入硬盤,以增加“賬面上”可用內存容量的手段(想想我們的內存和硬盤容量對比就知道了)。

但是,如果在書桌和抽屜之間頻繁進行文件的交換,工作效率肯定會下降。如果每次要看一份文件都要先收拾書桌再到抽屜里面拿的話,那工作根本就無法進行了。
虛擬內存的優點在於除了讓程序員感覺到內存容量大大增加之外,還讓程序員感覺到內存速度也增快了。
虛擬內存也有同樣的缺點:硬盤的容量比內存大,但也只是相對的,速度卻非常緩慢,如果和硬盤之間的數據交換過於頻繁,處理速度就會下降,表面上看起來就像卡住了一樣,這種現象稱為抖動(Thrushing)。相信很多人都有過計算機停止響應的經歷,而造成死機的主要原因之一就是抖動。
二、基本內存管理
2.1 單道編程的內存管理
在單道編程環境下,整個內存里面只有兩個程序:一個是用戶程序,另一個是操作系統。
由於只有一個用戶程序,而操作系統所占用的內存空間是恆定的,所以我們可以將用戶程序總是加載到同一個內存地址上,即用戶程序永遠從同一個地方開始執行。
這樣,用戶程序里面的地址都可以事先計算出來,即在程序運行之前就計算出所有的物理地址。這種在運行前即將物理地址計算好的方式叫做靜態地址翻譯。下面看看此方式如何達到兩個目標。
(1)地址獨立:用戶在編寫程序時無需考慮具體的物理內存,用戶程序始終都被加載到同一個物理地址上。
(2)地址保護:整個系統里面只有一個用戶程序,因此,固定地址的內存管理因為只運行一個用戶程序而達到地址保護。
2.2 多道編程的內存管理
在多道編程環境下,無法將程序總是加到固定的內存地址上,也就是無法使用靜態地址翻譯。因此,必須在程序加載完畢之后才能計算物理地址,也就是在程序運行時進行地址翻譯,這種翻譯稱為動態地址翻譯。

多道編程環境下的內管管理策略有兩種:
(1)固定分區
顧名思義,固定分區管理就是講內存分為固定的幾個區域,每個區域大小固定。最下面的分區為OS占用,其他分區由用戶程序使用。分區大小通常各不相同,當需要加載程序時,選擇一個當前閑置且容量足夠大的分區進行加載,如下圖所示,這是一種共享隊列的固定分區(多個用戶程序排在一個共同的隊列里面等待分區):

由於程序大小和分區大小不一定匹配,有可能形成一個小程序占用一個大分區的情況,從而造成內存里雖然有小分區閑置但無法加載大程序的情況。這時,我們就想到也許可以采用多個隊列,給每個分區一個隊列,程序按照大小排在相應的隊列里,如下圖所示,這時一種分開隊列的固定分區:

上圖這種方式也有缺點:如果還有空閑分區,但等待的程序不在該分區的等待隊列上,就將造成有空間而不能運行程序的尷尬。
(2)非固定分區
非固定分區的思想在於除了划分給OS的空間之外,其余的內存空間是作為一個整體存在的。當一個程序需要占用內存空間時,就在該片空間里面分出一個大小剛剛滿足程序所需的空間。再來一個程序時,則在剩下的空間里再這樣分出一塊來。在這種模式下,一個程序可以加載到任何地方,也可以和物理內存一樣大。
例如,一開始內存中只有OS,這時候進程A來了,於是分出一片與進程A大小一樣的內存空間;隨后,進程B來了,於是在進程A之上分出一片給進程B;然后進程C來了,就在進程B上面再分出一片給C。如此,進程A、B和C的起始地址都不是固定的,如下圖所示:

仔細一看,這種方式存在一個重大問題:每個程序像疊羅漢一樣累計,如果程序B在成型過程中需要更多空間怎么辦?(例如在實際程序中,很多遞歸嵌套函數調用的時候回造成棧空間的增長)因此,我們可以想到可以再一開始的時候給程序分配空間時就分配足夠大的空間,留有一片閑置空間供程序增長使用,如下圖所示:

不過,OS怎么知道應該分配多少空間給一個程序呢?分配多了,就是浪費;而分配少了,則可能造成程序無法繼續執行。
因此,可以在空間不夠時,給程序換一個空間,這種方式將程序倒到磁盤上,再加載到內存中,被稱為交換(swap)。但是,如果在交換模式下程序的增長超過了物理內存,就不能再交換了。此時,可以將程序按照功能分成一段一段功能相對完整的單元,一個單元執行完成后再執行下一個單元,這就是重疊(overlay)。
但是,交換內存管理這種方式存在兩個重要問題:
(1)空間浪費:隨着程序在內存與磁盤間的交換,內存將變得越來越碎片化,即內存將被不同程序分割成尺寸大小無法使用的小片空間。
(2)程序大小受限:這有兩層意思,一是指空間增長效率低下(由於磁盤操作耗時,交換出去再找一片更大的空間來增長程序空間的做法效率非常低),二是空間增長存在天花板限制(單一程序不能超過物理內存空間)。
2.3 閑置空間管理
在管理內存的時候,OS需要知道內存空間有多少空閑?這就必須跟蹤內存的使用,跟蹤的辦法有兩種:
(1)給每個分配單元賦予一個字位,用來記錄該分配單元是否閑置。例如,字位取值為0表示單元閑置,取值為1則表示已被占用,這種表示方法就是位圖表示法,如下圖所示:

(2)將分配單元按照是否閑置鏈接起來,這種方法稱為鏈表表示法。如上圖所示的的位圖所表示的內存分配狀態,使用鏈表來表示的話則會如下圖所示:

在鏈表表示下,尋找一個給定大小的閑置空間意味着找到一個類型為H的鏈表項,其大小大於或等於給定的目標值。不過,掃描鏈表速度通常較慢。
參考資料

鄒恆明,《操作系統之哲學原理》,機械工業出版社
