操作系統概念學習筆記 15 內存管理(一)

操作系統概念學習筆記 15

內存管理(一)


背景

內存是現代計算機運行的中心。內存有很大一組字或字節組成,每個字或字節都有它們自己的地址。CPU根據程序計數器(PC)的值從內存中提取指令,這些指令可能會引起進一步對特定內存地址的讀取和寫入。

一個典型指令執行週期,首先從內存中讀取指令。接着該指令被解碼,且可能需要從內存中讀取操作數。在指令對操作數執行後,其結果可能被存回到內存。內存單元只看到地址流,而並不直到這些地址是如何產生的(由指令計數器、索引、間接尋址、實地址等)或它們是什麼地址(指令或數據)。

基本硬件:

CPU所能直接訪問的存儲器只有內存和處理器內的寄存器。機器指令可以用內存地址作爲參數,而不能用磁盤地址作爲參數。如果數據不在內存中,那麼CPU使用前必須先把數據移到內存中。

CPU內置寄存器通常可以在一個CPU時鐘週期內完成訪問。對於寄存器的內容,絕大多數CPU可以在一個時鐘週期內解析並執行一個或多個指令,而對於內存就不行。完成內存訪問需要多個CPU時鐘週期,由於沒有數據以便完成正在執行的指令,CPU通常需要暫停(stall)。由於內存訪問頻繁,這種情況是難以忍受的,解決方法是在CPU與內存之間增加高速內存。這種協調速度差異的內存緩衝去,稱爲高速緩存(cache)

除了保證訪問物理內存的相對速度之外,還要確保操作系統不會被用戶進程所訪問,以及確保用戶進程不會被其他用戶進程訪問。

其中一種可能方案爲:

首先確保每個進程都有獨立的內存空間,爲此,需要確定進程可訪問的合法地址的範圍,並確保進程只能訪問其合法地址。通過基地址寄存器(base register)界限地址寄存器(limit register)可以實現這種保護。基地址寄存器(base register)含有最小的物理內存地址,界限地址寄存器(limit register)決定了範圍的大小。例如:如果基地址寄存器爲300040而界限寄存器爲120900,那麼程序可以訪問從300040到420940的所有地址。

==**圖1**==

內存空間保護的實現,是通過CPU硬件對用戶模式所產生的每個地址與寄存器的地址進程比較來完成的。如果訪問了不該訪問的地址,則會陷入到操作系統中,並作爲致命錯誤處理。

==**圖2**==

操作系統在內核模式下,可以無限制地訪問操作系統和用戶內存。因此操作系統可以將用戶程序裝入用戶內存,在出錯時輸出這些程序,訪問並修改系統調用的參數等。

地址綁定:

通常,程序以二進制可執行文件的形式存儲在磁盤上。爲了執行,程序被調入內存並放入進程空間內。

根據所使用的內存管理方案,進程在執行時,可以在磁盤和內存之間移動。在磁盤上等待調入內存以便執行的進程形成輸入隊列(input queue)

通常的步驟是從輸入隊列中選取一個進程並裝入內存。進程在執行時,會訪問內存中的指令和數據。最後,進程終止,其地址空間將被釋放。

許多系統允許用戶進程放在物理地址的任意位置。這種組合方式會影響用戶程序能夠使用的地址空間。在絕大多數情況下,用戶程序在執行前,會經過好幾個步驟,在這些步驟中,地址可能有不同的表示形式,源程序中的地址通常是用符號(如count)來表示,編譯器通常將這些符號地址綁定(bind)在可重定位的地址(如:從本模塊開始的第14字節)。鏈接程序或加載程序再將這些可重定位的地址綁定成絕對地址(如74014)。每次綁定都是從一個地址空間到另一地址空間的映射。

通常,將指令與數據綁定到內存地址有以下幾種情況:

  • 編譯時(compile time):如果編譯時就知道進程將在內存中的駐留地址,那麼就可以生成絕對代碼(absolute code)。如果將來開始地址發生變化,那麼就必須重新編譯代碼。

  • 加載時(load time):當編譯時不知道進程將駐留在內存的什麼地方,那麼編譯器就必須生成可重定位代碼(reloadable code)。綁定會延遲到加載時才進行。如果開始地址發生變化。只需要重新加載用戶代碼已引入改變值。

  • 執行時(execution time):如果進程在執行時可以從一個內存段移到另一個內存段,那麼綁定必須延遲到執行時才發生。絕大多數通用計算機操作系統採用這種方法。

邏輯地址空間與物理地址空間:

CPU生成的地址通常稱爲邏輯地址(logical address),而內存單元所看到的地址(即加載到內存地址寄存器(memory-address register)中的地址)通常稱爲物理地址(physical address)

編譯和加載時的地址綁定方法生成相同的邏輯地址和物理地址。但是,執行時的地址綁定方案導致不同的邏輯地址和物理地址。對於這種情況,通常稱邏輯地址爲虛擬地址(virtual address)。由程序所生成的所有邏輯地址稱爲邏輯地址空間(logical address space),與這些邏輯地址相對應的物理地址的集合稱爲物理地址空間(physical address space)

運行時從虛擬地址到物理地址的映射由被稱爲內存管理單元(memory-management unit,MMU)的硬件設備來完成。有很多可選擇的方法來完成這種映射,如使用一個簡單的MMU方案來實現這種映射,這是一種基地址寄存器方案的推廣,基地址寄存器在這裏稱爲重定位寄存器(relocation register),用戶進程所生成的地址在送交內存之前,都加上重定位寄存器的值。

假如,基地址爲14000,那麼用戶對地址346的訪問將映射爲地址14346。

用戶程序絕對不會看到真正的物理地址。如,程序可以創建一個指向位置346的指針,將他保存在內存中,使用它,與其他地址進行比較等等,所有這些操作都是基於346進行的。用戶程序處理邏輯地址時,內存映射硬件將邏輯地址轉變爲物理地址。所引用的內存地址只有在引用時才最後定位。

邏輯地址空間綁定到單獨的一套物理地址空間。

動態加載(dynamic loading):

一個進程的整個程序和數據如果都必須處於物理內存中,則進程的大小受物理內存大小的限制。

爲了獲得更好的內存空間使用率,使用動態加載(dynamic loading),即一個子程序只有在調用時才被加載。

所有的子程序都以可重定位的形式保存在磁盤上。主程序裝入內存並執行。當一個子程序需要調用另外一個子程序的時候,調用子程序首先檢查另一個子程序是否已經被加載。如果沒有,可重定位的鏈接程序將用來加載所需要的子程序,並更新程序的地址表以反應這一變化。接着控制傳遞給新加載的子程序。

動態加載的優點是不用子程序絕不會被加載,如果大多數代碼需要用來處理異常情況,如錯誤處理,那麼這種方法特別有用。對於這種情況,雖然總體上程序比較大,但是所使用的部分可能小很多。

動態加載不需要操作系統提供特別的支持。利用這種方法來設計程序主要是用戶的責任。

動態鏈接(dynamically linking)與共享庫:

有的操作系統只支持* 靜態鏈接(static linking)*此時系統語言庫的處理與其他目標模塊一樣,由加載程序合併到二進制程序鏡像中。

動態鏈接的概念與動態加載相似。只是這裏不是將加載延遲到運行時,而是將鏈接延遲到運行時。這一特點通常用於系統庫,如語言子程序庫。沒有這一點,系統上的所有程序都需要一份語言庫的副本,這一需求浪費了磁盤空間和內存空間。

如果有動態鏈接,二進制鏡像中每個庫程序的應用都有一個存根(stub)。存根是一小段代碼,用以指出如何定位適當的內存駐留的庫程序,或如果該程序不在內存中應如何安裝入庫。不管怎樣,存根會用子程序地址來代替自己,並開始執行子程序。因此,下次再執行該子程序代碼時,就可以直接進行,而不會因動態鏈接產生任何開銷。採用這種方案,使用語言庫的所有進程只需要一個庫代碼副本就可以了。

動態連接也可用於庫更新。一個庫可以被新的版本所替代,且使用該庫的所有程序會自動使用新的版本。沒有動態鏈接,所有這些程序必須重新鏈接以便訪問。

爲了不使程序錯用新的、不兼容版本的庫,程序和庫將包括版本信息。多個版本的庫都可以裝入內存,程序通過版本信息來確定使用哪個庫副本。

因此,只有用新庫編譯的程序纔會收到新庫的不兼容變化影響。在新程序裝入之前所鏈接的其他程序可以繼續使用老庫。這種系統也稱爲共享庫。

與動態加載不同,動態鏈接通常需要操作系統幫助。如果內存中的進程是彼此保護的,那麼只有操作系統纔可以檢查所需子程序是否在其他進程內存空間內,或是允許多個進程訪問同一內存地址。

交換

進程需要在內存中以便執行。進程也可以暫時從內存中交換(swap)備份存儲(backing store)上,當需要再次執行時在調回到內存中。

在交換策略的變種被用在基於優先級的調度算法中。如果有一個更高優先級的進程且需要服務,內存管理器可以交換出低優先級的進程,以便裝入和執行更高優先級的的進程。當高優先級進程執行完後,低優先級進程可以交換回內存繼續執行,這種交換有時候稱爲滾入(roll in/swap in)滾出(roll out/swap out)

通常,一個交換出的進程需要交換回它原來所佔有的內存空間。這一限制是由地址綁定方式決定的。如果綁定是在彙編時或加載時所定的,那麼就不可以移動到不同的位置。如果綁定在運行時才確定,由於物理地址是在運行時才確定,那麼進程可以移動到不同的地址空間。

交換需要備份存儲。備份存儲通常是快速磁盤。這必須足夠大,以便容納所有不同用戶的內存鏡像副本,它也必須提供對這些內存鏡像的直接訪問。系統有一個就緒隊列,它包括在備份存儲或內存中等待運行的所有進程。當CPU調度程序決定執行進程時,它調用調度程序。調度程序檢查隊列中的下一進程是否在內存中,如果不在內存中且沒有空閒內存空間,調度程序講一個已在內存中的進程交換出去,並換入所需要的進程。然後,它重新裝載寄存器,並將控制轉交給所選擇的進程。

交換系統的上下文切換時間比較長。爲了有效使用CPU,需要使每個進程的執行時間比交換時間長。

注意交換時間主要部分是轉移時間。總的轉移時間與所交換的內存空間總量成正比。因此如果只需交換真正使用的內存,便可以減少交換時間。爲有效使用這種方法,用戶需要告訴系統其內存需求情況。因此,具有動態內存需求的進程要通過系統調用(請求內存和釋放內存)來通知操作系統其內存需求變化情況。

交換也受其他因素限制。如果要換進的進程,那麼必須確保該進程完全處於空閒狀態。如果I/O異步訪問用戶內存的I/O緩衝區,那麼該進程就不能被換出。假定由於設備忙,I/O操作在排隊等待。如果換出進程P1換入進程P2,那麼I/O操作可能試圖使用現在已經屬於進程P2的內存。

對於這個問題有兩種解決方法:

  • 一是,不能換出有待處理I/O的進程。

  • 二是,I/O操作的執行只能使用操作系統緩衝區。僅當換入進程後,才執行操作系統緩衝與進程內存之間的數據轉移。

交換空間通常作爲磁盤的一整塊,且獨立與文件系統,因此使用就可能很快。

通常並不執行交換,但當有許多進程運行且內存空間吃緊時,交換開始啓動。如果系統負荷降低,交換就暫停。

連續內存分配(contiguous memory allocation)

內存必須容納操作系統和各種用戶進程,因此應該儘可能有效地分配內存的各個部分。

內存通常分爲兩個區域:一個用於駐留操作系統,一個用於用戶進程。操作系統可以位於低內存或高內存,影響這一決定的主要因素是中斷向量的位置。由於中斷向量通常位於低內存,因此程序員通常將操作系統放到低內存。

通常需要將多個進程同時放入內存中,因此需要考慮如何爲輸入隊列中需要調入內存的進程分配內存空間。

採用連續內存分配(contiguous memory allocation)時,每個進程位於一個連續的內存區域。

內存映射與保護

通過採用重定位寄存器和界限地址寄存器可以實現保護。

重定位寄存器含有最小的物理地址值;界限地址寄存器含有邏輯地址的範圍值。

這樣每個邏輯地址必須小於界限地址寄存器。MMU動態第將邏輯地址加上重定位寄存器的值後影射成物理地址。映射後的物理地址再送交內存單元。

這裏寫圖片描述

當CPU調度器選擇一個進程來執行時,作爲上下文切換工作的一個部分,調度程序會用正確的值來初始化重定位寄存器和界限地址寄存器,由於CPU所產生的每一地址都需要與寄存器進程覈對,所以可以保證操作系統和其他用戶程序和數據不受該進程運行所影響。

重定位寄存器機制爲允許操作系統動態改變提供了一個有效方法。如某驅動程序(或其他操作系統服務)不常使用便可以不必在內存中,這類代碼有時稱爲暫時(transient)操作系統代碼,它們根據需要調入或調出。因此,使用這種代碼可以在程序執行時動態改變操作系統的大小。

內存分配

最簡單的內存分配方法之一是將內存分爲多個固定大小的分區(partition)。每個分區只能容納一個進程。那麼多道程序的程度會受分區數限制。如果使用這種多分區方法(multiple-partition method),當一個分區空閒時,可以輸入隊列中選擇一個進程,以調入到空閒分區。當進程終止時,其分區可以被其他進程所使用。這種方法現在已不再使用。對於固定分區方案的推廣(稱爲MVT),它主要用於批處理環境。也可用於純分段內存管理的分時操作系統。

可變分區(variable-partition)方案中,操作系統有一個表,用於記錄那些內存可用和哪些內存已被佔用。一開始,所有內存都可用於用戶進程,因此可以作爲一大塊可用內存,稱爲孔(hole),當新進程需要內存時,爲該進程查找足夠大的孔,如果找到,可以從該孔進程分配所需的內存,孔內未分配的內存可用於下次再用。

隨着進程進入系統,它們將被加入輸入隊列中。操作系統根據調度算法來對輸入隊列進行排序。內存不斷地分配給進程,直到下一個進程的內存需求不能滿足爲止,如果沒有足夠大的孔來裝入進程,操作系統可以等到有足夠大的空間,或者往下掃描輸入隊列以確定是否其他內存需求較小的進程可以被滿足。

通常,一組不同大小的孔分散在內存中。當新進程需要內存時,系統爲進程查找足夠大的孔。如果孔太大,那麼就分成兩塊:一塊分配給新進程,另一塊還回到孔集合,當進程終止時,它將釋放其內存,改內存將還給孔集合。如果孔與其他孔相鄰,那麼將這些孔合併爲大孔。這時,系統可以檢查是否有進程在等待內存空間,新合併的內存空間是否滿足等待進程。

這種方法是通用動態存儲分配問題的一種情況(根據一組空閒孔來分配大小爲n的請求),這個問題有許多解決方法。從一組可用孔中選擇一個空閒孔的最爲常用方法有首次適應(first-fit)(第一個對夠大的孔)、最佳適應(best-fit)(最小的最夠大的孔)、最差適應(worst-fit)(分配最大的孔)。

  • 首次適應(first-fit):分配第一個足夠大的孔,查找可以從頭開始,也可以從上次首次適應結束時開始。一旦找到足夠大的空閒孔,就可以停止。

  • 最佳適應(best-fit):分配最小的足夠大的孔。必須查找整個列表,除非列表按照大小排序。這種方法可以產生最小剩餘孔。

  • 最差適應(worst-fit):分配最大的孔,同樣必須查找整個列表,除非列表按照大小排序。這種方法可以產生最大剩餘孔。該孔可能比最佳適應方法產生的最小剩餘孔更有用。

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

碎片(fragmentation)

首次適應和最佳適應算法都有外部碎片問題(external fragmentation)。隨着進程裝入和移出內存,空閒內存空間被分割爲小分段,
當所有總的空用內存之和可以滿足請求,但並不連續時,這就出現了外部碎片問題。最壞的情況下,每兩個進程之間就有空閒塊(或浪費)。如果這些內存是一整塊,那麼就可以再運行多個進程。

在首次適應和最佳適應之間的選擇可能會影響碎片的量。另一個影響因素是從空閒塊的哪端開始分配。不管使用哪種算法,外部碎片始終是個問題。

根據內存的總大小和平均進程大小的不同,外部碎片化的重要程度也不同。例如,對採用首次適應方法的統計說明,對於首次適應方法不管怎麼優化,假定N個可分配塊,那麼可能有0.5N個塊爲外部碎片。即1/3內存可能不能使用,這一特性稱爲50%規則。

內存碎片可以是內部的,也可以是外部的。如果內存以固定大小的塊爲單元來分配,進程所分配的內存可能比所要的要大。這兩個數字之差稱爲內部碎片(internal fragmentation)這部分內存在分區內,但又不能使用。

一種解決外部碎片問題的方法是緊縮(compaction),緊縮的目的是移動內存內容,以便所有空閒空間合併成一整塊。但是緊縮並非總是可能的。如果重定位是靜態的,並且在彙編時或裝入時進行的,那麼就不能緊縮。緊縮僅在重定位是動態的並在運行時可採用。如果地址被動態重定位,可以首先移動程序和數據,然後再跟據新基地址的值來改變基地址寄存器。如果採用緊縮,還要評估其開銷,最簡單的合併算法是簡單地將所有進城移到內存的一端,而將所有的孔移到內存的另一端,以生成一個大的空閒塊。這種方案開銷較大。

另一種解決方法外部碎片問題的方法是允許物理地址爲非連續的。這樣只要有物理內存就可以爲進程分配。這種方案有兩種互補的實現技術:分頁和分段。這兩種技術也可以合併。

發佈了87 篇原創文章 · 獲贊 204 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章