操作系统概念学习笔记 15 内存管理(一)
操作系統(tǒng)概念學(xué)習(xí)筆記 15
內(nèi)存管理(一)
背景
內(nèi)存是現(xiàn)代計算機運行的中心。內(nèi)存有非常大一組字或字節(jié)組成,每一個字或字節(jié)都有它們自己的地址。CPU依據(jù)程序計數(shù)器(PC)的值從內(nèi)存中提取指令。這些指令可能會引起進一步對特定內(nèi)存地址的讀取和寫入。
一個典型指令運行周期,首先從內(nèi)存中讀取指令。
接著該指令被解碼,且可能須要從內(nèi)存中讀取操作數(shù)。在指令對操作數(shù)運行后,其結(jié)果可能被存回到內(nèi)存。
內(nèi)存單元僅僅看到地址流,而并不直到這些地址是怎樣產(chǎn)生的(由指令計數(shù)器、索引、間接尋址、實地址等)或它們是什么地址(指令或數(shù)據(jù))。
基本硬件:
CPU所能直接訪問的存儲器僅僅有內(nèi)存和處理器內(nèi)的寄存器。機器指令能夠用內(nèi)存地址作為參數(shù)。而不能用磁盤地址作為參數(shù)。假設(shè)數(shù)據(jù)不在內(nèi)存中,那么CPU使用前必須先把數(shù)據(jù)移到內(nèi)存中。
CPU內(nèi)置寄存器通常能夠在一個CPU時鐘周期內(nèi)完畢訪問。
對于寄存器的內(nèi)容,絕大多數(shù)CPU能夠在一個時鐘周期內(nèi)解析并運行一個或多個指令,而對于內(nèi)存就不行。
完畢內(nèi)存訪問須要多個CPU時鐘周期。因為沒有數(shù)據(jù)以便完畢正在運行的指令,CPU通常須要暫停(stall)。因為內(nèi)存訪問頻繁,這樣的情況是難以忍受的。解決方法是在CPU與內(nèi)存之間添加快速內(nèi)存。這樣的協(xié)調(diào)速度差異的內(nèi)存緩沖去,稱為快速緩存(cache)。
除了保證訪問物理內(nèi)存的相對速度之外,還要確保操作系統(tǒng)不會被用戶進程所訪問,以及確保用戶進程不會被其它用戶進程訪問。
當(dāng)中一種可能方案為:
首先確保每一個進程都有獨立的內(nèi)存空間,為此,須要確定進程可訪問的合法地址的范圍,并確保進程僅僅能訪問其合法地址。通過基地址寄存器(base register)和界限地址寄存器(limit register)能夠?qū)崿F(xiàn)這樣的保護。基地址寄存器(base register)含有最小的物理內(nèi)存地址,界限地址寄存器(limit register)決定了范圍的大小。
比如:假設(shè)基地址寄存器為300040而界限寄存器為120900,那么程序能夠訪問從300040到420940的全部地址。
內(nèi)存空間保護的實現(xiàn)。是通過CPU硬件對用戶模式所產(chǎn)生的每一個地址與寄存器的地址進程比較來完畢的。假設(shè)訪問了不該訪問的地址。則會陷入到操作系統(tǒng)中。并作為致命錯誤處理。
操作系統(tǒng)在內(nèi)核模式下,能夠無限制地訪問操作系統(tǒng)和用戶內(nèi)存。
因此操作系統(tǒng)能夠?qū)⒂脩舫绦蜓b入用戶內(nèi)存,在出錯時輸出這些程序,訪問并改動系統(tǒng)調(diào)用的參數(shù)等。
地址綁定:
通常,程序以二進制可運行文件的形式存儲在磁盤上。為了運行,程序被調(diào)入內(nèi)存并放入進程空間內(nèi)。
依據(jù)所使用的內(nèi)存管理方案,進程在運行時。能夠在磁盤和內(nèi)存之間移動。在磁盤上等待調(diào)入內(nèi)存以便運行的進程形成輸入隊列(input queue)。
通常的步驟是從輸入隊列中選取一個進程并裝入內(nèi)存。進程在運行時。會訪問內(nèi)存中的指令和數(shù)據(jù)。最后。進程終止,其地址空間將被釋放。
很多系統(tǒng)同意用戶進程放在物理地址的任何位置。這樣的組合方式會影響用戶程序能夠使用的地址空間。在絕大多數(shù)情況下。用戶程序在運行前。會經(jīng)過好幾個步驟,在這些步驟中,地址可能有不同的表示形式,源程序中的地址一般是用符號(如count)來表示,編譯器通常將這些符號地址綁定(bind)在可重定位的地址(如:從本模塊開始的第14字節(jié))。鏈接程序或載入程序再將這些可重定位的地址綁定成絕對地址(如74014)。每次綁定都是從一個地址空間到還有一地址空間的映射。
通常。將指令與數(shù)據(jù)綁定到內(nèi)存地址有下面幾種情況:
編譯時(compile time):假設(shè)編譯時就知道進程將在內(nèi)存中的駐留地址。那么就能夠生成絕對代碼(absolute code)。假設(shè)將來開始地址發(fā)生變化,那么就必須又一次編譯代碼。
載入時(load time):當(dāng)編譯時不知道進程將駐留在內(nèi)存的什么地方,那么編譯器就必須生成可重定位代碼(reloadable code)。綁定會延遲到載入時才進行。假設(shè)開始地址發(fā)生變化。僅僅須要又一次載入用戶代碼已引入改變值。
運行時(execution time):假設(shè)進程在運行時能夠從一個內(nèi)存段移到還有一個內(nèi)存段。那么綁定必須延遲到運行時才發(fā)生。
絕大多數(shù)通用計算機操作系統(tǒng)採用這樣的方法。
邏輯地址空間與物理地址空間:
CPU生成的地址通常稱為邏輯地址(logical address)。而內(nèi)存單元所示地址(即載入到內(nèi)存地址寄存器(memory-address register)中的地址)通常稱為物理地址(physical address)。
編譯和載入時的地址綁定方法生成相同的邏輯地址和物理地址。可是。運行時的地址綁定方案導(dǎo)致不同的邏輯地址和物理地址。對于這樣的情況,通常稱邏輯地址為虛擬地址(virtual address)。
由程序所生成的全部邏輯地址稱為邏輯地址空間(logical address space)。與這些邏輯地址相相應(yīng)的物理地址的集合稱為物理地址空間(physical address space)。
運行時從虛擬地址到物理地址的映射由被稱為內(nèi)存管理單元(memory-management unit,MMU)的硬件設(shè)備來完畢。有非常多可選擇的方法來完畢這樣的映射,如使用一個簡單的MMU方案來實現(xiàn)這樣的映射,這是一種基地址寄存器方案的推廣。基地址寄存器在這里稱為重定位寄存器(relocation register),用戶進程所生成的地址在送交內(nèi)存之前。都加上重定位寄存器的值。
假如,基地址為14000,那么用戶對地址346的訪問將映射為地址14346。
用戶程序絕對不會看到真正的物理地址。如,程序能夠創(chuàng)建一個指向位置346的指針,將他保存在內(nèi)存中。使用它,與其它地址進行比較等等,全部這些操作都是基于346進行的。用戶程序處理邏輯地址時。內(nèi)存映射硬件將邏輯地址轉(zhuǎn)變?yōu)槲锢淼刂?。所引用的?nèi)存地址僅僅有在引用時才最后定位。
邏輯地址空間綁定到單獨的一套物理地址空間。
動態(tài)載入(dynamic loading):
一個進程的整個程序和數(shù)據(jù)假設(shè)都必須處于物理內(nèi)存中,則進程的大小受物理內(nèi)存大小的限制。
為了獲得更好的內(nèi)存空間使用率。使用動態(tài)載入(dynamic loading),即一個子程序僅僅有在調(diào)用時才被載入。
全部的子程序都以可重定位的形式保存在磁盤上。
主程序裝入內(nèi)存并運行。當(dāng)一個子程序須要調(diào)用另外一個子程序的時候,調(diào)用子程序首先檢查還有一個子程序是否已經(jīng)被載入。假設(shè)沒有,可重定位的鏈接程序?qū)⒂脕磔d入所須要的子程序,并更新程序的地址表以反應(yīng)這一變化。接著控制傳遞給新載入的子程序。
動態(tài)載入的長處是不用子程序絕不會被載入。假設(shè)大多數(shù)代碼須要用來處理異常情況。如錯誤處理。那么這樣的方法特別實用。對于這樣的情況,盡管整體上程序比較大,可是所使用的部分可能小非常多。
動態(tài)載入不須要操作系統(tǒng)提供特別的支持。利用這樣的方法來設(shè)計程序主要是用戶的責(zé)任。
動態(tài)鏈接(dynamically linking)與共享庫:
有的操作系統(tǒng)僅僅支持* 靜態(tài)鏈接(static linking)*此時系統(tǒng)語言庫的處理與其它目標(biāo)模塊一樣,由載入程序合并到二進制程序鏡像中。
動態(tài)鏈接的概念與動態(tài)載入類似。僅僅是這里不是將載入延遲到運行時。而是將鏈接延遲到運行時。這一特點通經(jīng)常使用于系統(tǒng)庫。如語言子程序庫。沒有這一點,系統(tǒng)上的全部程序都須要一份語言庫的副本,這一需求浪費了磁盤空間和內(nèi)存空間。
假設(shè)有動態(tài)鏈接,二進制鏡像中每一個庫程序的應(yīng)用都有一個存根(stub)。
存根是一小段代碼,用以指出怎樣定位適當(dāng)?shù)膬?nèi)存駐留的庫程序。或假設(shè)該程序不在內(nèi)存中應(yīng)怎樣安裝入庫。無論怎樣,存根會用子程序地址來取代自己,并開始運行子程序。因此,下次再運行該子程序代碼時,就能夠直接進行。而不會因動態(tài)鏈接產(chǎn)生不論什么開銷。採用這樣的方案,使用語言庫的全部進程僅僅須要一個庫代碼副本就能夠了。
動態(tài)連接也可用于庫更新。一個庫能夠被新的版本號所替代,且使用該庫的全部程序會自己主動使用新的版本號。
沒有動態(tài)鏈接。全部這些程序必須又一次鏈接以便訪問。
為了不使程序錯用新的、不兼容版本號的庫。程序和庫將包含版本號信息。多個版本號的庫都能夠裝入內(nèi)存。程序通過版本號信息來確定使用哪個庫副本。
因此,僅僅實用新庫編譯的程序才會收到新庫的不兼容變化影響。在新程序裝入之前所鏈接的其它程序能夠繼續(xù)使用老庫。這樣的系統(tǒng)也稱為共享庫。
與動態(tài)載入不同,動態(tài)鏈接通常須要操作系統(tǒng)幫助。假設(shè)內(nèi)存中的進程是彼此保護的。那么僅僅有操作系統(tǒng)才干夠檢查所需子程序是否在其它進程內(nèi)存空間內(nèi),或是同意多個進程訪問同一內(nèi)存地址。
交換
進程須要在內(nèi)存中以便運行。進程也能夠臨時從內(nèi)存中交換(swap)到備份存儲(backing store)上,當(dāng)須要再次運行時在調(diào)回到內(nèi)存中。
在交換策略的變種被用在基于優(yōu)先級的調(diào)度算法中。
假設(shè)有一個更高優(yōu)先級的進程且須要服務(wù),內(nèi)存管理器能夠交換出低優(yōu)先級的進程,以便裝入和運行更高優(yōu)先級的的進程。當(dāng)高優(yōu)先級進程運行完后,低優(yōu)先級進程能夠交換回內(nèi)存繼續(xù)運行。這樣的交換有時候稱為滾入(roll in/swap in)和滾出(roll out/swap out)。
通常,一個交換出的進程須要交換回它原來所占有的內(nèi)存空間。這一限制是由地址綁定方式?jīng)Q定的。假設(shè)綁定是在匯編時或載入時所定的,那么就不能夠移動到不同的位置。假設(shè)綁定在運行時才確定,因為物理地址是在運行時才確定。那么進程能夠移動到不同的地址空間。
交換須要備份存儲。
備份存儲一般是快速磁盤。
這必須足夠大。以便容納全部不同用戶的內(nèi)存鏡像副本,它也必須提供對這些內(nèi)存鏡像的直接訪問。
系統(tǒng)有一個就緒隊列。它包含在備份存儲或內(nèi)存中等待運行的全部進程。
當(dāng)CPU調(diào)度程序決定運行進程時,它調(diào)用調(diào)度程序。
調(diào)度程序檢查隊列中的下一進程是否在內(nèi)存中,假設(shè)不在內(nèi)存中且沒有空暇內(nèi)存空間。調(diào)度程序講一個已在內(nèi)存中的進程交換出去,并換入所須要的進程。然后。它又一次裝載寄存器,并將控制轉(zhuǎn)交給所選擇的進程。
交換系統(tǒng)的上下文切換時間比較長。為了有效使用CPU,須要使每一個進程的運行時間比交換時間長。
注意交換時間主要部分是轉(zhuǎn)移時間??偟霓D(zhuǎn)移時間與所交換的內(nèi)存空間總量成正比。因此假設(shè)僅僅需交換真正使用的內(nèi)存。便能夠降低交換時間。
為有效使用這樣的方法,用戶須要告訴系統(tǒng)其內(nèi)存需求情況。因此。具有動態(tài)內(nèi)存需求的進程要通過系統(tǒng)調(diào)用(請求內(nèi)存和釋放內(nèi)存)來通知操作系統(tǒng)其內(nèi)存需求變化情況。
交換也受其它因素限制。
假設(shè)要換進的進程,那么必須確保該進程全然處于空暇狀態(tài)。
假設(shè)I/O異步訪問用戶內(nèi)存的I/O緩沖區(qū),那么該進程就不能被換出。假定因為設(shè)備忙,I/O操作在排隊等待。假設(shè)換出進程P1換入進程P2。那么I/O操作可能試圖使用如今已經(jīng)屬于進程P2的內(nèi)存。
對于這個問題有兩種解決方法:
一是,不能換出有待處理I/O的進程。
二是,I/O操作的運行僅僅能使用操作系統(tǒng)緩沖區(qū)。僅當(dāng)換入進程后。才運行操作系統(tǒng)緩沖與進程內(nèi)存之間的數(shù)據(jù)轉(zhuǎn)移。
交換空間通常作為磁盤的一整塊,且獨立與文件系統(tǒng)。因此使用就可能非???。
通常并不運行交換,但當(dāng)有很多進程運行且內(nèi)存空間吃緊時,交換開始啟動。假設(shè)系統(tǒng)負(fù)荷降低,交換就暫停。
連續(xù)內(nèi)存分配(contiguous memory allocation)
內(nèi)存必須容納操作系統(tǒng)和各種用戶進程,因此應(yīng)該盡可能有效地分配內(nèi)存的各個部分。
內(nèi)存通常分為兩個區(qū)域:一個用于駐留操作系統(tǒng),一個用于用戶進程。操作系統(tǒng)能夠位于低內(nèi)存或高內(nèi)存,影響這一決定的主要因素是中斷向量的位置。因為中斷向量通常位于低內(nèi)存,因此程序猿通常將操作系統(tǒng)放到低內(nèi)存。
通常須要將多個進程同一時候放入內(nèi)存中。因此須要考慮怎樣為輸入隊列中須要調(diào)入內(nèi)存的進程分配內(nèi)存空間。
採用連續(xù)內(nèi)存分配(contiguous memory allocation)時,每一個進程位于一個連續(xù)的內(nèi)存區(qū)域。
內(nèi)存映射與保護
通過採用重定位寄存器和界限地址寄存器能夠?qū)崿F(xiàn)保護。
重定位寄存器含有最小的物理地址值;界限地址寄存器含有邏輯地址的范圍值。
這樣每一個邏輯地址必須小于界限地址寄存器。MMU動態(tài)第將邏輯地址加上重定位寄存器的值后影射成物理地址。
映射后的物理地址再送交內(nèi)存單元。
當(dāng)CPU調(diào)度器選擇一個進程來運行時,作為上下文切換工作的一個部分,調(diào)度程序會用正確的值來初始化重定位寄存器和界限地址寄存器,因為CPU所產(chǎn)生的每一地址都須要與寄存器進程核對,所以能夠保證操作系統(tǒng)和其它用戶程序和數(shù)據(jù)不受該進程運行所影響。
重定位寄存器機制為同意操作系統(tǒng)動態(tài)改變提供了一個有效方法。如某驅(qū)動程序(或其它操作系統(tǒng)服務(wù))不常使用便能夠不必在內(nèi)存中,這類代碼有時稱為臨時(transient)操作系統(tǒng)代碼,它們依據(jù)須要調(diào)入或調(diào)出。因此,使用這樣的代碼能夠在程序運行時動態(tài)改變操作系統(tǒng)的大小。
內(nèi)存分配
最簡單的內(nèi)存分配方法之中的一個是將內(nèi)存分為多個固定大小的分區(qū)(partition)。
每一個分區(qū)僅僅能容納一個進程。那么多道程序的程度會受分區(qū)數(shù)限制。假設(shè)使用這樣的多分區(qū)方法(multiple-partition method),當(dāng)一個分區(qū)空暇時,能夠輸入隊列中選擇一個進程,以調(diào)入到空暇分區(qū)。當(dāng)進程終止時,其分區(qū)能夠被其它進程所使用。這樣的方法如今已不再使用。對于固定分區(qū)方案的推廣(稱為MVT),它主要用于批處理環(huán)境。也可用于純分段內(nèi)存管理的分時操作系統(tǒng)。
在可變分區(qū)(variable-partition)方案中,操作系統(tǒng)有一個表。用于記錄那些內(nèi)存可用和哪些內(nèi)存已被占用。
一開始,全部內(nèi)存都可用于用戶進程,因此能夠作為一大塊可用內(nèi)存。稱為孔(hole),當(dāng)新進程須要內(nèi)存時,為該進程查找足夠大的孔,假設(shè)找到,能夠從該孔進程分配所需的內(nèi)存,孔內(nèi)未分配的內(nèi)存可用于下次再用。
隨著進程進入系統(tǒng),它們將被添加輸入隊列中。
操作系統(tǒng)依據(jù)調(diào)度算法來對輸入隊列進行排序。
內(nèi)存不斷地分配給進程,直到下一個進程的內(nèi)存需求不能滿足為止,假設(shè)沒有足夠大的孔來裝入進程,操作系統(tǒng)能夠等到有足夠大的空間?;蛘咄聮呙栎斎腙犃幸源_定是否其它內(nèi)存需求較小的進程能夠被滿足。
通常,一組不同大小的孔分散在內(nèi)存中。
當(dāng)新進程須要內(nèi)存時,系統(tǒng)為進程查找足夠大的孔。假設(shè)孔太大。那么就分成兩塊:一塊分配給新進程,還有一塊還回到孔集合,當(dāng)進程終止時。它將釋放其內(nèi)存,改內(nèi)存將還給孔集合。假設(shè)孔與其它孔相鄰。那么將這些孔合并為大孔。這時,系統(tǒng)能夠檢查是否有進程在等待內(nèi)存空間。新合并的內(nèi)存空間是否滿足等待進程。
這樣的方法是通用動態(tài)存儲分配問題的一種情況(依據(jù)一組空暇孔來分配大小為n的請求)。這個問題有很多解決方法。
從一組可用孔中選擇一個空暇孔的最為經(jīng)常用法有首次適應(yīng)(first-fit)(第一個對夠大的孔)、最佳適應(yīng)(best-fit)(最小的最夠大的孔)、最差適應(yīng)(worst-fit)(分配最大的孔)。
首次適應(yīng)(first-fit):分配第一個足夠大的孔,查找能夠從頭開始。也能夠從上次首次適應(yīng)結(jié)束時開始。一旦找到足夠大的空暇孔,就能夠停止。
最佳適應(yīng)(best-fit):分配最小的足夠大的孔。
必須查找整個列表,除非列表依照大小排序。這樣的方法能夠產(chǎn)生最小剩余孔。
最差適應(yīng)(worst-fit):分配最大的孔,相同必須查找整個列表。除非列表依照大小排序。
這樣的方法能夠產(chǎn)生最大剩余孔。
該孔可能比最佳適應(yīng)方法產(chǎn)生的最小剩余孔更實用。
模擬結(jié)果顯示:首次適應(yīng)和最佳適應(yīng)方法在運行時間和利用空間方面都好于最差適應(yīng)方法。
首次適應(yīng)和最佳適應(yīng)方法在利用空間方面難分伯仲。首次適應(yīng)方法更快些。
碎片(fragmentation)
首次適應(yīng)和最佳適應(yīng)算法都有外部碎片問題(external fragmentation)。隨著進程裝入和移出內(nèi)存??障緝?nèi)存空間被切割為小分段,
當(dāng)全部總的空用內(nèi)存之和能夠滿足請求,但并不連續(xù)時,這就出現(xiàn)了外部碎片問題。
最壞的情況下。每兩個進程之間就有空暇塊(或浪費)。假設(shè)這些內(nèi)存是一整塊。那么就能夠再運行多個進程。
在首次適應(yīng)和最佳適應(yīng)之間的選擇可能會影響碎片的量。還有一個影響因素是從空暇塊的哪端開始分配。無論使用哪種算法。外部碎片始終是個問題。
依據(jù)內(nèi)存的總大小和平均進程大小的不同。外部碎片化的重要程度也不同。
比如。對採用首次適應(yīng)方法的統(tǒng)計說明,對于首次適應(yīng)方法無論怎么優(yōu)化,假定N個可分配塊。那么可能有0.5N個塊為外部碎片。即1/3內(nèi)存可能不能使用,這一特性稱為50%規(guī)則。
內(nèi)存碎片能夠是內(nèi)部的。也能夠是外部的。假設(shè)內(nèi)存以固定大小的塊為單元來分配,進程所分配的內(nèi)存可能比所要的要大。這兩個數(shù)字之差稱為內(nèi)部碎片(internal fragmentation)這部分內(nèi)存在分區(qū)內(nèi),但又不能使用。
一種解決外部碎片問題的方法是緊縮(compaction),緊縮的目的是移動內(nèi)存內(nèi)容,以便全部空暇空間合并成一整塊??墒蔷o縮并不是總是可能的。假設(shè)重定位是靜態(tài)的。而且在匯編時或裝入時進行的,那么就不能緊縮。
緊縮僅在重定位是動態(tài)的并在運行時可採用。假設(shè)地址被動態(tài)重定位,能夠首先移動程序和數(shù)據(jù),然后再跟據(jù)新基地址的值來改變基地址寄存器。假設(shè)採用緊縮。還要評估其開銷。最簡單的合并算法是簡單地將全部進城移到內(nèi)存的一端。而將全部的孔移到內(nèi)存的還有一端。以生成一個大的空暇塊。
這樣的方案開銷較大。
還有一種解決方法外部碎片問題的方法是同意物理地址為非連續(xù)的。這樣僅僅要有物理內(nèi)存就能夠為進程分配。這樣的方案有兩種互補的實現(xiàn)技術(shù):分頁和分段。這兩種技術(shù)也能夠合并。
轉(zhuǎn)載于:https://www.cnblogs.com/lxjshuju/p/7281990.html
總結(jié)
以上是生活随笔為你收集整理的操作系统概念学习笔记 15 内存管理(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 喵喵喵?
- 下一篇: 从入门到放弃,.net构建博客系统(二)