內存管理
問題思考:
1、為什么進行內存管理?2、頁式管理中每個頁表項大小的下限如何決定?
3、多級頁面解決了什么問題,帶來了什么問題?
內存管理的基本原理和要求
內存管理(Memory Management)計算機硬件盡管很大,但依然不能一次將所有用戶的進程和系統所需要的程序全部裝入主存(通常稱 16G, 8G內存指的就是這個主存,它比硬盤速度快,便於程序的的調度),因此操作系統必須對內存空間進行合理的划分和有效的動態分配,這就是內存管理的概念
有效的內存管理在多道程序設計上非常重要,它不僅可以方便用戶使用存儲器,提高內存利用率,還可以通過虛擬技術來擴充存儲器
內存管理的主要功能
內存空間的分配與回收: 由操作系統完成主存儲器空間的分配和管理,使程序員擺脫存儲分配的麻煩,提高編程效率.
地址轉換: 在多道程序環境下,程序中的邏輯地址與內存中的物理地址不可能一致,因此存儲地址必須提供地址變換功能,把相應的邏輯地址變換為物理地址
內存空間的擴充: 利用虛擬存儲技術或自動覆蓋技術,從邏輯上擴充內存
存儲保護: 保證各道作業在各自的存儲空間內運行,互不干擾
在進行內存管理前,需要了解進程運行的基本原理和要求
程序如何運行:編譯、鏈接、裝入
(一)地址概念和程序如何運行
在多道程序環境下,要使程序運行,必須先為之創建進程。而創建進程的第一件事,便是將程序和數據裝入內存。如何將一個用戶源程序變為一個可在內存中執行的程序,通常都要經過以下幾個步驟:
1、首先是要編譯:
編譯: 由編譯程序將用戶的源代碼編譯稱若干目標模塊
由編譯程序(Compiler)將用戶源代碼編譯成cpu可執行的目標代碼,產生了若干個目標模塊(Object Module)(即若干程序段)。形成的目標代碼,每個目標代碼都是以0為基址順序進行編址,原來用符號名訪問的單元用具體的數據——單元號取代。這樣生成的目標程序占據一定的地址空間,稱為作業的邏輯地址
空間,簡稱邏輯空間。
在邏輯空間中每條指令的地址和指令中要訪問的操作數地址統稱為邏輯地址 。很簡單,邏輯地址就是你源程序里使用的地址,或者源代碼經過編譯以后編譯器將一些標號,變量轉換成的地址。
2、其次是鏈接:
什么是鏈接,為什么需要鏈接
鏈接: 由鏈接程序(Linker)將編譯后形成的一組目標模塊(程序段),以及它們所需要的庫函數鏈接在一起,形成一個完整的裝入模塊(Load Module);
在實際開發中,我們一定是多文件編程,所有文件在編譯后,需要合在一起,合在一起的過程就是鏈接的過程。
每一個源文件(就是.c文件,上圖中的程序1)都有對應的零碎文件(就是.h文件),通過預編譯(通過#include實現)把.c和.h文件整合成一個組合C文件,這個組合C文件的擴展名為.i。把組合C文件編譯成匯編文件.s,目標文件為機器指令(放在一個.o文件當中),單個目標文件是不能工作的,因為各個目標文件是相互支撐工作的。
把各個目標文件整合的過程就叫鏈接過程。整合后的文件就叫可執行程序,windows后綴為.exe,Linux后綴為.out
鏈接過程都干了什么事
目標文件主要分為兩個區域:數據區域和指令區域。 每一個指令和數據都被安排了地址。
目標文件被整合的時候,每個目標文件的數據區被整合到一起,每個目標文件的指令區被整合到一起。假如目標文件1被整合前指令的地址是00000001,目標文件n整合前指令的地址也是00000001,整合到一起后,他們的地址是要重新編排的,這個叫地址重定位。數據區域的地址相應的也要重新編排。
3、最后是裝入(地址重定位):
由裝入程序(Loader)將裝入模塊裝入物理內存,cpu就能取內存中的程序運行了。物理內存是真實存在的插在主板內存槽上的內存條的容量的大小。
物理內存內存是由若干個存儲單元組成的,每個存儲單元有一個編號,這種編號可唯一標識一個存儲單元,稱為內存地址(或物理地址)。我們可以把內存看成一個從0字節一直到內存最大容量逐字節編號的存儲單元數組,即每個存儲單元與內存地址的編號相對應。
裝入模塊雖然具有統一的地址空間,但它仍是以“0”作為參考地址,即是浮動的。要把它裝入內存執行,就要確定裝入內存的實際物理地址,並修改程序中與 地址有關的代碼,這一過程叫做地址重定位。地址重定位主要是把邏輯地址轉換成物理內存絕對地址,這個工作又稱為地址映射。
(二)程序的鏈接
源程序經過編譯后,可得到一組目標模塊,再利用鏈接程序將這組目標模塊鏈接,形成裝入模塊。根據鏈接時間的不同,可把鏈接分成如下三種:
(1) 、 靜態鏈接。在程序運行之前,先將各目標模塊及它們所需的庫函數,鏈接成一個完整的裝配模塊,以后不再拆開。我們把這種事先進行鏈接的方式稱為靜態鏈接方式。
(2)、 裝入時動態鏈接。這是指將用戶源程序編譯后所得到的一組目標模塊,在裝入內存時,采用邊裝入邊鏈接的鏈接方式。
(3)、 運行時動態鏈接。這是指對某些目標模塊的鏈接,是在程序執行中需要該(目標)模塊時,才對它進行的鏈接。
1.靜態鏈接方式(Static Linking)
我們通過一個例子來說明在實現靜態鏈接時應解決的一些問題。在圖 4-4(a)中示出了經過編譯后所得到的三個目標模塊A、B、C,它們的長度分別為 L、M和N。在模塊A中有一條語句CALL B,用於調用模塊B。在模塊B中有一條語句CALL C,用於調用模塊C。B和C都屬於外部調用符號,在將這幾個目標模塊裝配成一個裝入模塊時,須解決以下兩個問題:
(1) 對相對地址進行修改。在由編譯程序所產生的所有目標模塊中,使用的都是相對地址,其起始地址都為 0,每個模塊中的地址都是相對於起始地址計算的。在鏈接成一個裝入模塊后,原模塊B和 C在裝入模塊的起始地址不再是 0,而分別是 L和 L+M,所以此時須修改模塊B和C中的相對地址,即把原B中的所有相對地址都加上 L,把原 C中的所有相對地址都加上L+M。
(2) 變換外部調用符號。將每個模塊中所用的外部調用符號也都變換為相對地址,如把B 的起始地址變換為 L,把 C 的起始地址變換為 L+M,如圖 4-4(b)所示。這種先進行鏈接所形成的一個完整的裝入模塊,又稱為可執行文件。通常都不再拆開它,要運行時可直接將它裝入內存。這種事先進行鏈接,以后不再拆開的鏈接方式,稱為靜態鏈接方式。
圖 4-4 程序鏈接示意圖
2.裝入時動態鏈接(Load-time Dynamic Linking)
用戶源程序經編譯后所得的目標模塊,是在裝入內存時邊裝入邊鏈接的,即在裝入一個目標模塊時,若發生一個外部模塊調用事件,將引起裝入程序去找出相應的外部目標模塊,並將它裝入內存,還要按照圖4-4所示的方式來修改目標模塊中的相對地址。裝入時動態鏈接方式有以下優點:
(1) 、 便於修改和更新。對於經靜態鏈接裝配在一起的裝入模塊,如果要修改或更新其中的某個目標模塊,則要求重新打開裝入模塊。這不僅是低效的,而且有時是不可能的。若采用動態鏈接方式,由於各目標模塊是分開存放的,所以要修改或更新各目標模塊是件非常容易的事。
(2)、 便於實現對目標模塊的共享。在采用靜態鏈接方式時,每個應用模塊都必須含有其目標模塊的拷貝,無法實現對目標模塊的共享。但采用裝入時動態鏈接方式,OS則很容易將一個目標模塊鏈接到幾個應用模塊上,實現多個應用程序對該模塊的共享。
3.運行時動態鏈接(Run-time Dynamic Linking)
在許多情況下,應用程序在運行時,每次要運行的模塊可能是不相同的。但由於事先無法知道本次要運行哪些模塊,故只能是將所有可能要運行到的模塊都全部裝入內存,並在裝入時全部鏈接在一起。
顯然這是低效的,因為往往會有些目標模塊根本就不運行。
比較典型的例子是作為錯誤處理用的目標模塊,如果程序在整個運行過程中都不出現錯誤,則顯然就不會用到該模塊。
近幾年流行起來的運行時動態鏈接方式,是對上述在裝入時鏈接方式的一種改進。這種鏈接方式是將對某些模塊的鏈接推遲到程序執行時才進行鏈接,亦即,在執行過程中,當發現一個被調用模塊尚未裝入內存時,立即由OS去找到該模塊並將之裝入內存,把它鏈接到調用者模塊上。凡在執行過程中未被用到的目標模塊,都不會被調入內存和被鏈接到裝入模塊上,這樣不僅可加快程序的裝入過程,而且可節省大量的內存空間。
三. 程序的裝入(地址的變換)
為了闡述上的方便,我們先介紹一個無需進行鏈接的單個目標模塊的裝入過程。該目標模塊也就是裝入模塊。在將一個裝入模塊裝入內存時,可以有絕對裝入方式、可重定位裝入方式和動態運行時裝入方式,下面分別簡述之。
1.絕對裝入方式(Absolute Loading Mode)
在編譯時,如果知道程序將駐留在內存的什么位置,那么,編譯程序將產生絕對地址的目標代碼。即按照物理內存的位置賦予實際的物理地址。
例如,事先已知用戶程序(進程)駐留在從R處開始的位置,則編譯程序所產生的目標模塊(即裝入模塊)便從R處開始向上擴展。絕對裝入程序按照裝入模塊中的地址,將程序和數據裝入內存。裝入模塊被裝入內存后,由於程序中的邏輯地址與實際內存地址完全相同,故不須對程序和數據的地址進行修改。程序中所使用的絕對地址,既可在編譯或匯編時給出,也可由程序員直接賦予。
這個方式的優點:是CPU執行目標代碼快。
缺點:
1)是由於內存大小限制,能裝入內存並發執行的進程數大大減少
2)編譯程序必須知道內存的當前空閑地址部分和其地址,並且把進程的不同程序段連續地存放起來,編譯非常復雜。由於程序
因此,通常是寧可在程序中采用符號地址,然后在編譯或匯編時,再將這些符號地址轉換為絕對地址。
如何把虛擬內存地址空間變換到內存唯一的一維物理線性空間?涉及到兩個問題:
一是虛擬空間的划分問題。
二是把虛擬空間中已經鏈接和划分好的內容裝入內存,並將虛擬空間地址映射內存地址的問題。即地址映射。
地址映射就是建立虛擬地址與內存地址的關系。
2.靜態地址重定位(可重定位裝入方式 Relocation Loading Mode)
絕對裝入方式只能將目標模塊裝入到內存中事先指定的位置。在多道程序環境下,編譯程序不可能預知所編譯的目標模塊應放在內存的何處,因此,絕對裝入方式只適用於單道程序環境。
在多道程序環境下,所得到的目標模塊的起始地址通常是從 0 開始的,程序中的其它地址也都是相對於起始地址計算的。此時應采用可重定位裝入方式,根據內存的當前情況,將裝入模塊裝入到內存的適當位置。
靜態地址重定位:即在程序裝入對目標代碼裝入內存的過程中完成,是指在程序開始運行前,程序中指令和數據的各個地址均已完成重定位,即完成虛擬地址到內存地址映射。地址變換通常是在裝入時一次完成的,以后不再改變。
值得注意的是, 在采用可重定位裝入程序將裝入模塊裝入內存后, 會使裝入模塊中的所有邏輯地址與實際裝入內存的物理地址不同,圖4-3示出了這一情況。
圖4-3 作業裝入內存時的情況
例如,在用戶程序的 1000 號單元處有一條指令LOAD 1,2500,該指令的功能是將 2500 單元中的整數 365 取至寄存器 1。但若將該用戶程序裝入到內存的 10000~15000號單元而不進行地址變換, 則在執行11000號單元中的指令時,它將仍從 2500 號單元中把數據取至寄存器1而導致數據錯誤。由圖4-3 可見,正確的方法應該是將取數指令中的地址 2500 修改成 12500,即把指令中的相對地址 2500 與本程序在內存中的起始地址 10000 相加,才得到正確的物理地址12500。除了數據地址應修改外,指令地址也須做同樣的修改,即將指令的相對地址 1000 與起始地址 10000 相加,得到絕對地址 11000。
優點:無需硬件支持
缺點:
1)程序重定位之后就不能在內存中搬動了;
2)要求程序的存儲空間是連續的,不能把程序放在若干個不連續的區域中。
3.動態地址重地位(動態運行時裝入方式 Dynamic Run-time Loading)
可重定位裝入方式可將裝入模塊裝入到內存中任何允許的位置,故可用於多道程序環境;但這種方式並不允許程序運行時在內存中移動位置。因為,程序在內存中的移動,意味着它的物理位置發生了變化, 這時必須對程序和數據的地址(是絕對地址)進行修改后方能運行。然而,實際情況是,在運行過程中它在內存中的位置可能經常要改變,此時就應采用動態運行時裝入的方式。
動態地址重定位:不是在程序執行之前而是在程序執行過程中進行地址變換。更確切的說,是把這種地址轉換推遲到程序真正要執行時才進行,即在每次訪問內存單元前才將要訪問的程序或數據地址變換成內存地址。動態重定位可使裝配模塊不加任何修改而裝入內存。為使地址轉換不影響指令的執行速度,這種方式需要一個重定位寄存器的支持
優點:
1)目標模塊裝入內存時無需任何修改,因而裝入之后再搬遷也不會影響其正確執行,這對於存儲器緊縮、解決碎片問題是極其有利的;
2)一個程序由若干個相對獨立的目標模塊組成時,每個目標模塊各裝入一個存儲區域,這些存儲區域可以不是順序相鄰的,只要各個模塊有自己對應的定位寄存器就行。
缺點:需要硬件支持。