1. 程式人生 > 實用技巧 >記憶體分配&記憶體分段&記憶體分頁&虛擬記憶體

記憶體分配&記憶體分段&記憶體分頁&虛擬記憶體

記憶體應容納作業系統和各種使用者程序,因此應該儘可能有效地分配記憶體。

一、記憶體

連續記憶體分配

通常,我們需要將多個程序同時放在記憶體中。因此我們需要考慮,如何為輸入佇列中需要調入記憶體的程序分配記憶體空間。在採用連續記憶體分配時,每個程序位於一個連續的記憶體區域,與包含下一個程序的記憶體相連。

記憶體分配

1. 最為簡單的記憶體分配方法之一,就是將記憶體分為多個固定大小的分割槽。每個分割槽可以只包含一個程序。因此,多道程式的程度受限於分割槽數。如果使用這種多分割槽方法,那麼當一個分割槽空閒時,可以從輸入佇列中選擇一個程序,以調入空閒分割槽。當該程序終止時,它的分割槽可以用於其他程序。

這種方法最初為 IBMOS/360 作業系統所使用,現在已不再使用。下面所描述的方法是固定分割槽方案的推廣(稱為 MVT),它主要用於批處理環境。這裡所描述的許多思想也可用於採用純分段記憶體管理的分時作業系統。


2. 對於可變分割槽方案,作業系統有一個表,用於記錄哪些記憶體可用和哪些記憶體已用。開始,所有記憶體都可用於使用者程序,因此可以作為一大塊的可用記憶體,稱為塊。最後,正如將會看到的,記憶體有一個集合,以包含各種大小的塊。
隨著程序進入系統,它們將被加入輸入佇列。作業系統根據所有程序的記憶體需求和現有可用記憶體的情況,決定哪些程序可分配記憶體。當程序分配到空間時,它就載入到記憶體,並開始競爭 CPU。當程序終止時,它將釋放記憶體,該記憶體可以被作業系統分配給輸入佇列內的其他程序。
任何時候,都有一個可用塊大小的列表和一個輸入佇列。作業系統根據排程演算法來對輸入佇列進行排序。記憶體不斷地分配給程序,直到下一個程序的記憶體需求不能滿足為止,這時沒有足夠大的可用塊來載入程序。作業系統可以等到有足夠大的空間,或者可以往下掃描輸入佇列,以確定是否有其他記憶體需求較小的程序可以被滿足。

通常,如上所述,可用的記憶體塊為分散在記憶體裡的不同大小的塊的集合。當新程序需 要記憶體時,系統為該程序查詢足夠大的塊。如果塊太大,那麼就分為兩塊:一塊分配給新程序,另一塊還回到塊集合。當程序終止時,它將釋放記憶體,該記憶體將還給塊的集合。如果新塊與其他塊相鄰,那麼將這些塊合併成大塊。這時,系統可以檢查,是否有程序在等待記憶體空間,以及新合併的記憶體空間是否滿足等待程序等。

這種方法是通用動態儲存分配問題(根據一組空閒塊來分配大小為 n 的請求)的一個特例。這個問題有許多解決方法。

從一組可用塊中選擇一個空閒塊的最為常用方法包括:首次適應最優適應最差適應

    • 首次適應:分配首個足夠大的塊。查詢可以從頭開始,也可以從上次首次適應結束時開始。一旦找到足夠大的空閒塊,就可以停止。
    • 最優適應:分配最小的足夠大的孔。應查詢整個列表,除非列表按大小排序。這種方法可以產生最小剩餘塊。
    • 最差適應:分配最大的塊。同樣,應查詢整個列表,除非列表按大小排序。這種方法可以產生最大剩餘塊,該塊可能比最優適應產生的較小剩餘塊更為適用。

模擬結果顯示,首次適應和最優適應在執行時間和利用空間方面都好於最差適應。首次適應和最優適應在利用空間方面難分伯仲,但是首次適應要更快些。

碎片

用於記憶體分配的首次適應和最優適應演算法都有外部碎片的問題。
隨著程序載入到記憶體和從記憶體退出,空閒記憶體空間被分為小的片段。當總的可用記憶體之和可以滿足請求但並不連續時,這就出現了外部碎片問題儲存被分成了大量的小塊,這個問題可能很嚴重
在最壞情況下,每兩個程序之間就有空閒(或浪費的)塊。如果這些記憶體是一整塊,那麼可能可以再執行多個程序。
選擇首次適應或者最優適應,可能會影響碎片的數量。(對一些系統來說,首次適應更好;對另一些系統,最優適應更好)。另一因素是從空閒塊的哪端開始分配。(哪個是剩餘的塊,是上面的還是下面的?)不管使用哪種演算法,外部碎片始終是個問題。
根據記憶體空間總的大小和平均程序大小的不同,外部碎片問題或許次要或許重要。例如,採用首次適應方法的統計說明,不管使用什麼優化,假定有 N 個可分配塊,那麼可能有 0.5N 個塊為外部碎片。即 1/3 的記憶體可能不能使用。這一特性稱為50%規則。
記憶體碎片可以是內部的,也可以是外部的。假設有一個 18 464 位元組大小的孔,並採用 多分割槽分配方案。假設有一個程序需要 18 462 位元組。如果只能分配所要求的塊,那麼還剩下一個 2 位元組的塊。維護這一小塊的開銷要比塊本身大很多。
因此,通常按固定大小的塊為單位(而不是位元組)來分配記憶體。採用這種方案,程序所分配的記憶體可能比所需的要大。這兩個數字之差稱為內部碎片,這部分記憶體在分割槽內部,但又不能用。

外部碎片問題的一種解決方法是緊縮。它的目的是移動記憶體內容,以便將所有空閒空間合併成一整塊。然而,緊縮並非總是可能的。如果重定位是靜態的,並且在彙編時或載入時進行的,那麼就不能緊縮。只有重定位是動態的,並且在執行時進行的,才可採用緊縮。
如果地址被動態重定位,可以首先移動程式和資料,然後再根據新基地址的值來改變基地址暫存器。如果能採用緊縮,那麼還要評估開銷。最簡單的合併演算法是簡單地將所有程序移到記憶體的一端,而將所有的塊移到記憶體的另一端,從而生成一個大的空閒塊。這種方案比較昂貴。

外部碎片化問題的另一個可能的解決方案是,允許程序的邏輯地址空間是不連續的,這樣,只要有實體記憶體可用,就允許為程序分配記憶體。有兩種互補的技術可以實現這個解決方案:分段和分頁。這兩個技術也可以組合起來。

碎片是一個常見問題,當需要管理資料塊時它就可能出現。

二、記憶體分段與分頁

分段

程式設計師通常願意將記憶體看作一組不同長度的段,這些段之間並沒有一定的順序(圖 1)。

當編寫程式時,程式設計師認為它是由主程式加上一組方法、過程或函式所構成的。它還可以包括各種資料結構,例如物件、陣列、堆疊、變數等。每個模組或資料元素通過名稱來引用。程式設計師會說“堆疊”、“數學庫”和“主程式”等,而並不關心這些元素所在記憶體的位置,及他不關心堆疊是放在函式 Sqrt() 之前還是之後。

分段就是支援這種使用者檢視的記憶體管理方案。邏輯地址空間是由一組段構成每個段都有名稱和長度。地址指定了段名稱段內偏移。因此使用者通過兩個量來指定地址:段名稱和段偏移

分頁

分段允許程序的實體地址空間是非連續的。分頁是提供這種優勢的另一種記憶體管理方案。然而,分頁避免了外部碎片和緊縮,而分段不可以。
不僅如此,分頁還避免了將不同大小的記憶體塊匹配到交換空間的問題,在分頁引入之前採用的記憶體管理方案都有這個問題。由於比早期方法更加優越,各種形式的分頁為大多數作業系統採用,包括大型機的和智慧手機的作業系統。實現分頁需要作業系統和計算機硬體的協作。

實現分頁的基本方法涉及將實體記憶體分為固定大小的塊稱為頁幀,而將邏輯記憶體也分為同樣大小的塊,稱為頁面。當需要執行一個程序時,它的頁從檔案系統或備份儲存等處,載入到記憶體的可用幀。備份儲存劃分為固定大小的塊,它與單個記憶體幀或與多個記憶體幀(簇)的大小一樣。

分頁優點之一是可以共享公共程式碼。對於分時環境,這種考慮特別重要。

三、虛擬記憶體

分頁,分段等,所有這些策略都有相同的目標,就是同時將多個程序儲存在記憶體中,以便允許多道程式。然而,這些策略都傾向於要求每個程序在執行之前應完全處於記憶體中

虛擬記憶體技術允許執行程序不必完全處於記憶體。這種方案的一個主要優點就是,程式可以大於實體記憶體。此外,虛擬記憶體將記憶體抽象成一個巨大的、統一的儲存陣列,進而實現了使用者看到的邏輯記憶體與實體記憶體的分離。這種技術使得程式設計師不再擔憂記憶體容量的限制
虛擬記憶體還允許程序輕鬆共享檔案和實現共享記憶體。此外,它為建立程序提供了有效的機制。然而,虛擬記憶體的實現並不容易,並且使用不當還可能會大大降低效能。


記憶體管理演算法的實現有一個基本要求,就是執行的指令應處於實體記憶體中。滿足這一要求的第一種方法是,將整個邏輯地址空間置於實體記憶體中。動態載入可以幫助緩解這種限制,但它通常需要特殊的預防措施和程式設計師的額外工作。
指令應處於實體記憶體以便執行的要求,似乎是必要的和合理的,但它也是有缺點的,因為它將程式的大小限制為實體記憶體的大小。事實上,通過實際程式的研究會發現,在許多情況下並不需要將整個程式置於記憶體中(見下例)

例如,分析以下內容:

  • 程式通常具有處理異常錯誤條件的程式碼。由於這些錯誤很少實際發生,所以這些程式碼幾乎從不執行。
  • 陣列、連結串列和表等所分配的記憶體量通常多於實際需要值。按100X100個元素來宣告的陣列,可能實際很少用到大於10X10個的元素。雖然彙編程式的符號表可能有 3000 個符號的空間,但是程式平均可能用到的只有不到 200 個符號。
  • 程式的某些選項和功能可能很少使用。例如,美國政府計算機的平衡預算程式多年來都沒有使用過。

即使在需要整個程式的情況下,也可能並不同時需要整個程式。分段能夠執行只有部分處於記憶體的程式,可以帶來許多好處:

  • 程式不再受實體記憶體的可用量所限制。使用者可以為一個巨大的虛擬地址空間編寫程式,從而簡化了程式設計任務。
  • 由於每個使用者程式可佔用較少的實體記憶體,因此可以同時執行更多的程式,進而增加 CPU 利用率和吞吐量,但沒有增加響應時間或週轉時間。
  • 由於載入或交換每個使用者程式到記憶體所需的 I/O 會更少,使用者程式會執行得更快。因此,執行不完全處於記憶體的程式將使系統和使用者都受益。

虛擬記憶體將使用者邏輯記憶體與實體記憶體分開。這在現有實體記憶體有限的情況下,為程式設計師提供了巨大的虛擬記憶體(如下圖所示)。

因此,虛擬記憶體使得程式設計更加容易,因為程式設計師不再需要擔心有限的實體記憶體空間,只需要關注所要解決的問題。

除了將邏輯記憶體與實體記憶體分開外,虛擬記憶體允許檔案和記憶體通過共享頁而為多個程序所共享。這帶來了以下好處:

  1. 通過將共享物件對映到虛擬地址空間中,系統庫可以為多個程序所共享。儘管每個程序都將庫視為其虛擬地址空間的一部分,但是駐留在實體記憶體中的庫的實際頁可由所有程序共享(圖 3)。通常,庫按只讀方式對映到與其連結的程序空間。
  2. 類似地,虛擬記憶體允許程序共享記憶體。程序之間可以通過使用共享記憶體來進行通訊。虛擬記憶體允許一個程序建立一個記憶體區域,以便與其他程序共享。共享這個記憶體區域的程序認為,它是其虛擬地址空間的一部分,而事實上這部分是共享的,如圖 3 所示。
  3. 當通過系統呼叫 fork() 建立程序時,可以共享頁面,從而加快程序建立。