作業系統 記憶體管理/裝置管理/ 連結
記憶體管理
虛擬記憶體
每個程式擁有自己的地址空間,這個地址空間被分割成多個塊,每一塊稱為一頁。這些頁被對映到實體記憶體,但不需要對映到連續的實體記憶體,也不需要所有頁都必須在實體記憶體中。當程式引用到一部分不在實體記憶體中的地址空間時,由硬體執行必要的對映,將缺失的部分裝入實體記憶體並重新執行失敗的指令。
分頁與分段
1. 分頁
大部分虛擬記憶體系統都使用分頁技術。把由程式產生的地址稱為虛擬地址,它們構成了一個虛擬地址空間。例如有一臺計算機可以產生 16 位地址,它的虛擬地址空間為 0~64K,然而計算機只有 32KB 的實體記憶體,因此雖然可以編寫 64KB 的程式,但它們不能被完全調入記憶體執行。
虛擬地址空間劃分成固定大小的頁,在實體記憶體中對應的單元稱為頁框,頁和頁框大小通常相同,它們之間通過頁表進行對映。
程式最開始只將一部分頁調入頁框中,當程式引用到沒有在頁框的頁時,產生缺頁中斷,進行頁面置換,按一定的原則將一部分頁框換出,並將頁調入。
2. 分段
上圖為一個編譯器在編譯過程中建立的多個表,有 4 個表是動態增長的,如果使用分頁系統的一維地址空間,動態增長的特點會導致覆蓋問題的出現。
分段的做法是把每個表分成段,一個段構成一個獨立的地址空間。每個段的長度可以不同,並且可以動態增長。
每個段都需要程式設計師來劃分。
3. 段頁式
用分段方法來分配和管理虛擬儲存器。程式的地址空間按邏輯單位分成基本獨立的段,而每一段有自己的段名,再把每段分成固定大小的若干頁。
用分頁方法來分配和管理實存。即把整個主存分成與上述頁大小相等的儲存塊,可裝入作業的任何一頁。
程式對記憶體的調入或調出是按頁進行的,但它又可按段實現共享和保護。
4. 分頁與分段區別
對程式設計師的透明性:分頁透明,但是分段需要程式設計師顯示劃分每個段。
地址空間的維度:分頁是一維地址空間,分段是二維的。
大小是否可以改變:頁的大小不可變,段的大小可以動態改變。
出現的原因:分頁主要用於實現虛擬記憶體,從而獲得更大的地址空間;分段主要是為了使程式和資料可以被劃分為邏輯上獨立的地址空間並且有助於共享和保護。
分頁系統地址對映
- 記憶體管理單元(MMU):管理著虛擬地址空間和實體記憶體的轉換。
- 頁表(Page table):頁(虛擬地址空間)和頁框(實體記憶體空間)的對映表。例如下圖中,頁表的第 0 個表項為 010,表示第 0 個頁對映到第 2 個頁框。頁表項的最後一位用來標記頁是否在記憶體中。
下圖的頁表存放著 16 個頁,這 16 個頁需要用 4 個位元位來進行索引定位。因此對於虛擬地址(0010 000000000100),前 4 位是用來儲存頁面號,而後 12 位儲存在頁中的偏移量。
(0010 000000000100)根據前 4 位得到頁號為 2,讀取表項內容為(110 1),它的前 3 為為頁框號,最後 1 位表示該頁在記憶體中。最後對映得到實體記憶體地址為(110 000000000100)。
頁面置換演算法
在程式執行過程中,如果要訪問的頁面不在記憶體中,就發生缺頁中斷從而將該頁調入記憶體中。此時如果記憶體已無空閒空間,系統必須從記憶體中調出一個頁面到磁碟對換區中來騰出空間。
頁面置換演算法的主要目標是使頁面置換頻率最低(也可以說缺頁率最低)。
1. 最佳
Optimal
所選擇的被換出的頁面將是最長時間內不再被訪問,通常可以保證獲得最低的缺頁率。
是一種理論上的演算法,因為無法知道一個頁面多長時間不再被訪問。
舉例:一個系統為某程序分配了三個物理塊,並有如下頁面引用序列:
開始執行時,先將 7, 0, 1 三個頁面裝入記憶體。當程序要訪問頁面 2 時,產生缺頁中斷,會將頁面 7 換出,因為頁面 7 再次被訪問的時間最長。
2. 先進先出
FIFO, First In First Out
所選擇換出的頁面是最先進入的頁面。
該演算法會將那些經常被訪問的頁面也被換出,從而使缺頁率升高。
3. 最近最久未使用
LRU, Least Recently Used
雖然無法知道將來要使用的頁面情況,但是可以知道過去使用頁面的情況。LRU 將最近最久未使用的頁面換出。
可以用棧來實現該演算法,棧中儲存頁面的頁面號。當程序訪問一個頁面時,將該頁面的頁面號從棧移除,並將它壓入棧頂。這樣,最近被訪問的頁面總是在棧頂,而最近最久未使用的頁面總是在棧底。
4. 時鐘
Clock
需要用到一個訪問位,當一個頁面被訪問時,將訪問位置為 1。
首先,將記憶體中的所有頁面連結成一個迴圈佇列,當缺頁中斷髮生時,檢查當前指標所指向頁面的訪問位,如果訪問位為 0,就將該頁面換出;否則將該頁的訪問位設定為 0,給該頁面第二次的機會,移動指標繼續檢查。
五、裝置管理
磁碟排程演算法
當多個程序同時請求訪問磁碟時,需要進行磁碟排程來控制對磁碟的訪問。
磁碟排程的主要目標是使磁碟的平均尋道時間最少。
1. 先來先服務
FCFS, First Come First Served
根據程序請求訪問磁碟的先後次序來進行排程。優點是公平和簡單,缺點也很明顯,因為未對尋道做任何優化,使平均尋道時間可能較長。
2. 最短尋道時間優先
SSTF, Shortest Seek Time First
要求訪問的磁軌與當前磁頭所在磁軌距離最近的優先進行排程。這種演算法並不能保證平均尋道時間最短,但是比 FCFS 好很多。
3. 掃描演算法
SCAN
SSTF 會出現飢餓現象。考慮以下情況,新程序請求訪問的磁軌與磁頭所在磁軌的距離總是比一個在等待的程序來的近,那麼等待的程序會一直等待下去。
SCAN 演算法在 SSTF 演算法之上考慮了磁頭的移動方向,要求所請求訪問的磁軌在磁頭當前移動方向上才能夠得到排程。因為考慮了移動方向,那麼一個程序請求訪問的磁軌一定會得到排程。
當一個磁頭自裡向外移動時,移到最外側會改變移動方向為自外向裡,這種移動的規律類似於電梯的執行,因此又常稱 SCAN 演算法為電梯排程演算法。
4. 迴圈掃描演算法
CSCAN
CSCAN 對 SCAN 進行了改動,要求磁頭始終沿著一個方向移動。
六、連結
編譯系統
以下是一個 hello.c 程式:
#include <stdio.h> int main() { printf("hello, world\n"); return 0; }
在 Unix 系統上,由編譯器把原始檔轉換為目標檔案。
gcc -o hello hello.c
這個過程大致如下:
- 預處理階段:處理以 # 開頭的預處理命令;
- 編譯階段:翻譯成彙編程式;
- 彙編階段:將彙編程式翻譯可重定向目標程式,它是二進位制的;
- 連結階段:將可重定向目標程式和 printf.o 等單獨預編譯好的目標檔案進行合併,得到最終的可執行目標程式。
目標檔案
- 可執行目標檔案:可以直接在記憶體中執行;
- 可重定向目標檔案:可與其他可重定向目標檔案在連結階段合併,建立一個可執行目標檔案;
- 共享目標檔案:可以在執行時被動態載入進記憶體並連結;
靜態連結
靜態聯結器以一組可重定向目標檔案為輸入,生成一個完全連結的可執行目標檔案作為輸出。連結器主要完成以下兩個任務:
- 符號解析:每個符號對應於一個函式、一個全域性變數或一個靜態變數,符號解析的目的是將每個符號引用與一個符號定義關聯起來。
- 重定位:編譯器和彙編器生成從地址 0 開始的程式碼和資料節,連結器通過把每個符號定義與一個記憶體位置關聯起來,從而重定位這些節,然後修改所有對這些符號的引用,使得它們指向這個記憶體位置。
動態連結
靜態庫有以下兩個問題:
- 當靜態庫更新時那麼整個程式都要重新進行連結;
- 對於 printf 這種標準函式庫,如果每個程式都要有程式碼,這會極大浪費資源。
共享庫是為了解決靜態庫的這兩個問題而設計的,在 Linux 系統中通常用 .so 字尾來表示,Windows 系統上它們被稱為 DLL。它具有以下特點:
- 在給定的檔案系統中一個庫只有一個 .so 檔案,所有引用該庫的可執行目標檔案都共享這個檔案,它不會被複制到引用它的可執行檔案中;
- 在記憶體中,一個共享庫的 .text 節的一個副本可以被不同的正在執行的程序共享。
參考資料
- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014.
- 湯子瀛, 哲鳳屏, 湯小丹. 計算機作業系統[M]. 西安電子科技大學出版社, 2001.
- Bryant, R. E., & O’Hallaron, D. R. (2004). 深入理解計算機系統.