傳統儲存管理存在的問題
虛擬記憶體這個東西他為什麼會出現?他出現的背景是什麼?
前文 記憶體管理兩部曲之實體記憶體管理 提到:隨著使用者程式功能的增加,程式所需要的記憶體空間越來越大,程式空間很容易就突破了實體記憶體的實際大小,導致程式無法執行。
因此,為了解決記憶體不足的情況,緩和大程式與小記憶體之間的矛盾,擴充記憶體容量勢在必行。
可以從物理和邏輯兩方面來考慮擴充記憶體容量,物理擴容沒啥技術含量,需要我們研究的自然是如何從邏輯上擴充記憶體容量。
所謂邏輯擴充,就是說實際上實體記憶體的容量沒有發生改變,但是它能裝的東西卻變多了,使得使用者看來似乎有一個比實際記憶體大得多的記憶體。
對記憶體的邏輯擴充技術主要有三種:覆蓋技術、交換技術、以及虛擬記憶體(Virtual Memory),也稱為虛擬儲存器。事實上,這些邏輯擴充技術的核心理念都是一致的,研究的都是將哪個程式(或程式的某部分)暫時從記憶體移到外存(磁碟),以騰出記憶體空間供其他程式(或程式的某部分)佔用。
覆蓋(Overlay)和交換(Swapping)這兩種存在於早期作業系統中的邏輯擴充技術現在已經成為歷史,這裡就簡單介紹下:
前文說過,早期作業系統僅將記憶體空間分成兩塊:系統區(用於存放作業系統相關資料)和使用者區(用於存放使用者程式相關資料,記憶體中只能有一道使用者程式,使用者程式獨佔整個使用者區空間,顯然,記憶體空間容不下某個使用者程式的現象常會發生。
覆蓋技術(Overlay)的基本思想就是:程式執行時並非任何時候都要訪問程式及資料的所有部分(尤其是大程式),因此可以把使用者空間(記憶體)分成一個固定區和一個或多個覆蓋區。
將程式經常活躍的部分放在固定區,其餘部分按呼叫關係進行分段:首先將那些即將要用的段放在覆蓋區,其他段放在外存(磁碟),在需要呼叫前由使用者來安排特定的系統呼叫將這些放在外存中的段調入覆蓋區,替換覆蓋區中原有的段。
覆蓋技術的缺點顯而易見並且可以說是讓人無法接受的,那就是覆蓋技術是把解決記憶體空間不足的問題交給了使用者。作業系統僅僅為使用者提供將覆蓋段調入記憶體的系統呼叫,但是必須由使用者自己來說明覆蓋哪個段、調入哪個段。
合著我用個電腦還得算著怎麼才能讓我的程式不崩潰?
OK,可以看出來,覆蓋技術其實是用在同一個作業/程式的不同段之間的,那麼不同的作業/程式之間怎麼辦呢?
這就是交換技術的適用場景。
交換技術(Swapping)的基本思想是:空閒程式/作業主要儲存在外存(磁碟)上,當其中某個程式/作業需要執行的時候,就將其從磁碟中完整地調入記憶體,使該程式執行一段時間,然後再把它返回磁碟。所以說當程式/作業不執行的時候它們是不會佔用記憶體的。
事實上,覆蓋和交換技術分別解決了傳統儲存管理(實體記憶體管理)中存在的某個問題:
- 覆蓋技術打破了作業/程式必須一次性全部裝入記憶體後才能開始執行(一次性)的限制
- 交換技術打破了一旦作業被裝入記憶體,就會一直駐留在記憶體中,直至作業執行結束(駐留性)的限制
當然了,Anyway,這兩種邏輯擴充技術已經成為歷史,虛擬記憶體技術才是目前的主流,它綜合了這兩種古老技術的特點,單槍匹馬解決了傳統儲存管理中存在的這兩個問題。
什麼是虛擬記憶體
有了上述交換技術的鋪墊,理解起虛擬記憶體來也就不那麼陌生了。
當然了,在此之前,我一定要著重宣告的是,不要把虛擬記憶體當作一個實際存在的東西,它是一門技術!和交換技術覆蓋技術一樣是一門用來邏輯擴充記憶體空間的技術!
虛擬記憶體技術基於一個非常重要的原理,區域性性原理:
1)時間區域性性:如果執行了程式中的某條指令,那麼不久後這條指令很有可能再次執行;如果某個資料被訪問過,不久之後該資料很可能再次被訪問。(因為程式中存在大量的迴圈)
2)空間區域性性:一旦程式訪問了某個儲存單元,在不久之後,其附近的儲存單元也很有可能被訪問(因為很多資料在記憶體中都是連續存放的,並且程式的指令也是順序地在記憶體中存放的)
基於這個區域性性原理,在一個程式裝入記憶體的時候,可以只將這個程式中很快會用到的部分裝入記憶體,暫時用不到的部分仍然留在外存(磁碟),並且程式可以正常執行;
而在程式執行過程中,當 CPU 所需要的資訊不在記憶體中的時候,由作業系統負責將所需資訊從外存(磁碟)調入記憶體,然後繼續執行程式;
如果調入記憶體的時候記憶體空間不夠,由作業系統負責將記憶體中暫時用不到的資訊換出到外存。
以上,就是虛擬記憶體技術。
如何實現虛擬記憶體技術
可以看見,虛擬記憶體允許一個作業/程式分多次調入記憶體,那如果採用連續分配方式,不方便實現,所以虛擬記憶體技術的實現是建立在不連續分配管理方式之上的。
傳統的基本分頁管理、基本分段管理、基本段頁式管理和虛擬記憶體技術結合,分別稱為請求分頁管理(頁式虛存系統)、請求分段管理(段式虛存系統)、請求段頁式管理(段頁式虛存系統)。
這幾個概念非常容易混淆,其實很容易區分,記住這句話就 OK,摘自百度百科:
如果不具備請求調頁、頁面置換的功能,則稱為基本分頁管理(或稱為純分頁管理),它不具有支援實現虛擬記憶體的功能,它要求把每個作業(程式)全部裝入記憶體後方能執行。
請求分段儲存管理也差不多,它建立在分段儲存管理之上,但增加了請求調段、段置換功能。
請求調頁、頁面置換 和 請求調段、段置換概念差不多,這裡以請求調頁和頁面置換為例解釋下。
- 在程式執行過程中,當所訪問的資訊不在記憶體時,由作業系統負責將所需資訊從外存(磁碟)調入記憶體,然後繼續執行程式(作業系統要提供請求調頁的功能, 將記憶體中缺失的頁面從磁碟調入記憶體 );
- 若記憶體空間不夠,由作業系統負責將記憶體中暫時用不到的資訊換出到磁碟(作業系統要提供頁面置換的功能, 將暫時用不到的頁面換出磁碟)。
具體來說,在頁式虛存系統中,每當 CPU 要訪問的頁面不在記憶體時,就會產生一個缺頁中斷,然後由作業系統的缺頁中斷處理程式來處理中斷。此時,缺頁的這個程式/作業就會被阻塞住,放入阻塞佇列,調頁完成後再將其喚醒,放回就緒佇列。
- 如果記憶體中有空閒塊,則為該程式分配一個空閒塊,將所缺的頁面裝入這個塊中,並修改頁表中相應的頁表項。
- 如果記憶體中沒有空閒塊,則由頁面置換演算法選擇一個頁面淘汰,若該頁面在記憶體期間被修改過,則要將其寫回外存,未修改過的頁面不用寫回外存。
可以看出來,這並不是一個簡單的過程,基本分頁管理中的簡單頁表已經無法勝任這樣的工作。
我們還是先來回顧下基本分頁管理的頁表,它只有頁號和塊號兩個欄位:
請求分頁管理的頁表自然是會複雜不少的:
1)為了實現 “請求調頁” 功能,作業系統需要知道每個頁面是否已經調入記憶體,如果還沒調入,那麼也需要知道該頁面在磁碟中存放的位置。
2)而當記憶體空間不夠時,要實現 “頁面置換” 功能,作業系統需要通過某些指標來決定到底換出哪個頁面,有的頁面沒有被修改過,就不用浪費時間寫回磁碟;有的頁面修改過,就需要將磁碟中的舊資料覆蓋。因此,作業系統也需要記錄各個頁面是否被修改的資訊。
為此,請求分頁管理的頁表中新增了 4 個欄位:
- 狀態位:該頁面是否已調入記憶體
- 訪問欄位:可記錄該頁面最近被訪問過幾次,或記錄上次訪問該頁面的時間,供頁面置換演算法換出頁面時參考
- 修改位:該頁面調入記憶體後是否被修改過
- 外存地址:該頁面在外存中的存放地址
頁面置換演算法也是一個很重要的內容,本來應該在這篇文章裡一起寫的,But 想到 “頁面置換” 問題不僅僅是在虛擬記憶體中存在,在計算機設計的其他領域也會同樣發生(比如多數計算機都會把最近使用過的 32 位元組或者 64 位元組儲存塊儲存在一個或多個快取記憶體中,當這些快取記憶體存滿後就必須選取一些塊丟棄掉,以此來存入最新的使用過的儲存塊),所以決定後續單開一篇文章。
? 關注公眾號 | 飛天小牛肉,即時獲取更新
- 博主東南大學碩士在讀,攜程 Java 後臺開發暑期實習生,利用課餘時間運營一個公眾號『 飛天小牛肉 』,2020/12/29 日開通,專注分享計算機基礎(資料結構 + 演算法 + 計算機網路 + 資料庫 + 作業系統 + Linux)、Java 技術棧等相關原創技術好文。本公眾號的目的就是讓大家可以快速掌握重點知識,有的放矢。關注公眾號第一時間獲取文章更新,成長的路上我們一起進步
- 並推薦個人維護的開源教程類專案: CS-Wiki(Gitee 推薦專案,現已累計 1.7k+ star), 致力打造完善的後端知識體系,在技術的路上少走彎路,歡迎各位小夥伴前來交流學習 ~ ?
- 如果各位小夥伴春招秋招沒有拿得出手的專案的話,可以參考我寫的一個專案「開源社群系統 Echo」Gitee 官方推薦專案,目前已累計 800+ star,基於 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 並提供詳細的開發文件和配套教程。公眾號後臺回覆 Echo 可以獲取配套教程,目前尚在更新中。