其實內存它的做用就是用來存放數據。我們的程序本來是放在外存、放在磁盤當中的,可是磁盤的讀寫速度很慢,而CPU的處理速度又很快,因此若是CPU要執行這個程序,程序相關的數據都是從外存讀入的,那麼很顯然CPU的這個速度會被外存的速度給拖累。因此爲了緩和這個CPU和硬盤、外存之間的速度矛盾,因此我們必須先把我們要執行的、CPU要處理的這些程序數據把它放入內存裏。既然我們的內存是存放數據的,那麼我們的內存當中可能會存放不少不少數據,那操做系統是怎麼區分各個程序的數據是放在什麼地方的呢?那爲了區分這些數據存放的位置,就須要給內存進行一個地址的編號。就有點類似於說我們去住酒店的時候,怎麼區分我們每個人住在哪個房間?其實很簡單,酒店的作法就是給每個房間編號,那我們的內存其實和這個酒店是一樣的,只不過酒店的這些房間裏你能夠存的是人,而內存當中,它的這些「小房間」裏,它存的是一個一個的數據。那內存會被劃分紅這樣一個一個的「小房間」,每個小房間就是一個存儲單元。那接下來在劃分的這些存儲單元之後,就須要給這些存儲單元進行一個編號。那內存的這個地址編號通常來説是從零開始的,然後依次遞增。而且每個地址會對應一個數據的存儲單元,也就是會對應一個「小房間」。那麼,這樣的一個存儲單元能夠存放多少數據呢?這個具體得看計算機的編址方式。我們在操做系統這門課當中大部分遇到的情況是會告訴你說計算機按字節編址,按字節編址的意思就是一個地址它對應的是一個字節的數據,也就是說這樣的一個存儲單元,它能夠存放一個字節,而一個字節它又由8個二進制位組成,也就是8個0101這樣組成。那在有的題目當中也有可能會告訴我們這個計算機是按字編址的,若是它告訴我們是按字編址的話,那麼就意味著一個地址它所對應的存儲單元能夠存放一個字,而一個字的長度是多少個比特位?這個具體得看題目當中給出的條件。有的計算機當中字長是16位,那麼它一個字的大小就是16個比特。也有的計算機可能字長是32位,字長是64位等等。總之,我們須要根據題目給的條件來判斷一個字它佔有多少個比特位。好的,那麼在這個部分我們為你們介紹了內存的一些最基本的知識。什麼叫作存儲單元,就是用於存放數據的最小單元。另外,每一個地址能夠對應這樣的一個存儲單元。而一個存儲單元能夠存儲多少數據,那具體要看這個計算機它是怎麼設計的。對於我們考研來説,我們就要看它題目給的條件究竟是什麼。程序員
那在內存管理這個章節當中,可能會有不少題目會涉及到這個數據的一些基本單位。而對於不考計組的同學來説可能對這些單位的描述是比較陌生的,因此我們在這個地方還須要再介紹一下一些常見的單位。好比說我們平時所説的一個手機,或者說一臺電腦它有4GB內存,那除了GB以外,我們還經常看到什麼MB,KB這樣的單位。那所謂的1KB,其實就是2的10次方這麼多。而1MB,其實是2的20次方這麼多。而這裏的1GB,其實是2的30次方那麼多。因此這個地方4G其實它是一個數量,而B是一個數據的單位,這個大BByte指的是字節,小b小寫的b它指的是bit,是一個比特位,一個二進制位。一個Byte也就是一個大B等於8個小b,因此若是一個手機有4GB內存的話那麼就意味著這個手機的內存當中它能夠存放4*2^30這麼多個字節的數據。因此若是這個手機或者這個電腦它是按字節編址的,那麼這個內存的地址空間就應該是4*2^30這麼多個存儲單元。每一個存儲單元能夠存放一個字節。那我們知道,在計算機的世界當中,全部的這些數字其實都是用二進制0101這樣來表示的。包括我們的內存地址,其實也須要用二進制來表示。因此有的題目當中可能會告訴我們,內存的大小是多少。好比說內存大小是4GB,而且告訴我們這個內存是按字節編址的,題目可能會問我們到底須要多少個二進制位才能表示這麼多個存儲單元也就是2^32次方個存儲單元。那對於跨考的同學來說,必定要去了解一下二進制編碼還有二進制數和這個十進制數的一個轉換關係。對二進制比較熟悉的就能夠很快速地反應出來這麼多個存儲單元確定就須要32個二進制位來表示。因此若是手機的內存是4GB,而且它是按字節編址的,那麼對於這個手機來説它的地址至少須要用32個二進制位來表示。好的那麼再次提醒,對於跨考的同學來說,若是二進制和十進制的這個轉換不是很熟練的話,必定要下去練習。算法
在瞭解了內存的做用、內存的存儲單元、內存的地址這些概念之後,我們再結合以前我們提到過的一些基礎再給你們更進一步深刻地講解一下指令工做的具體原理。這個知識點有助於你們對後面的那些內容的更深刻的理解。那我們以前的學習當中提到過,其實我們用高級語言編程的代碼經過編譯之後,會造成與它對應的等價的一系列的機器語言指令。每一條指令就是讓CPU幹一件具體的事情。好比說我們用C語言寫的x=x+1;這樣一個很簡單的操做,經過編譯之後可能會造成這樣的三條與它對等的機器指令。那當這個程序運行的時候,系統會爲它創建相應的進程,而我們以前學到過一個進程在內存當中會有一片區域叫作程序段就是用於存放這個進程相關的那些代碼指令的。另外還有一個部分叫作數據段,數據段就是用來存放這個程序所處理的一些變量啊之類的數據。好比說我們這兒的x變量,它就是存放在所謂的數據段裏。那我們來看一下這三條指令分別表明著什麼呢?CPU在執行這幾條指令的時候首先它取出了指令1,然後指令1它發現由這樣的幾個部分組成。第一個部分紅色的這個部分叫作操做碼,就是指明瞭這條指令是要幹一件什麼事情。那這個地方的二進制碼我只是胡亂寫的,你們只須要理解它的原理就能夠了。那我們假設這個什麼101100它表明的是讓CPU作一個數據傳送的事情。那後面這兩段數據又是指明瞭這個操做相關的一些必要的參數。好比說我們的指令1就是讓CPU從內存地址01001111把這個地方存放的數據把它取到對應十進制就是編號為3的這個寄存器當中。因此CPU在執行這個指令的時候,它就知道我現在要作的事情是要作數據的傳送。那怎麼傳送呢?我須要從地址為79的這個內存單元當中,把它裏面的數據取出來,然後把它放到編號為3的這個寄存器當中。因此指令1的執行就會導致編號為3的這個寄存器當中有了10這個數。把x的值放到了這個寄存器中。那在執行了指令1之後,CPU就會開始執行指令2。編程
同樣的,它會解析這個指令2究竟是要幹一件什麼事情。根據它前面的這個部分,也就是所謂的操做碼,它能夠判斷出這個指令是要作一個加法操做,加法運算。而怎麼加呢?CPU須要把編號為00000011也就是換成十進制的話也就是編號為3的這個寄存器當中的內容加上1,因此根據這條指令CPU會把這個寄存器當中的值從10加1,也就是變成11。數組
那再接下來它又執行的是第三條指令。這個指令同樣是一個數據傳送的指令。能夠看到它的這個操做碼和第一個指令的操做碼是一樣的,就説明這兩條指令它們要幹的是同一個事情,是同一種指令。只不過它們的參數是不一樣的,你們能夠對比著來看一下。那這個指令3是讓CPU幹這樣的一個事情。它須要把編號為3的這個寄存器當中的內容,把它寫回編號為01001111這個內存單元當中,因此CPU在執行第三條指令的時候,就會把這個寄存器當中的內容把它寫回x這個變量在內存當中存放的那個地址。所以這就完成了x=x+1;這樣的一個操做。當然剛才我們講的這三條指令只是我本身胡亂寫的,其實並不嚴謹。若是你們想要了解這些指令真正的什麼操做碼啊參數啊究竟是什麼樣一種規範,那還須要學習計算機組成原理。可是對於不考那門課的同學來説,只要理解到這一步就差很少了。其實CPU在執行這些一條一條指令的過程當中,它就是在處理這些內存啊或者寄存器當中的數據,而怎麼處理這些數據,怎麼找到這些數據呢?它就是基於地址這個很重要的概念來進行的。我們的內存會有它本身的一些地址編址,同樣的我們的寄存器也會有一些它本身的地址編址。總之我們的程序經過編譯之後,會造成一系列等價的機器指令。在這個機器指令當中它會有一些相應的參數,告訴CPU你應該去哪些地址去讀數據,或者往哪些地址寫數據。那在剛才我們講的這個例子當中,我們默認了我們所提到的這個進程它是從0這個地址開始連續存放的。因此在它的這個指令當中,是直接指明瞭各個變量的存放位置。好比說x的存放地址,它就直接把它寫死在了這個指令裏。它是存放在79這個地址所對應的存儲單元裏的。那接下來我們要思考的一個問題是這樣的,若是我們的這個地址它不是從零開始存放的,而是從別的地址開始存放的,會不會導致我們的這個進程的運行出現一些問題呢?我們來具體看一下。緩存
這個可執行文件在Windows系統當中就是.exe,這個可執行文件又能夠稱做為裝入模塊。這個概念我們之後還會具體細聊。總之我們造成了這個裝入模塊,造成了這個可執行文件之後,就能夠把這個可執行文件放入內存裏然後就開始執行這個程序了。不過須要注意的是,我們所造成的這個可執行文件,它的這些指令當中所指明的這些地址參數,其實指的是一個邏輯地址,一個相對地址。所謂的相對地址就是指,這個地址指的是它相對於這個進程的起始地址而言的地址。有點繞,不過其實並不難理解,在以前的那個例子當中,我們是默認了這個進程它相關的這些數據是從內存地址為零這個地方開始存放的。因此這條指令它是要進行x這個變量的初始化,而且它指明瞭x這個變量它存放的地址是79,它的初始值為10,因此CPU在執行這條指令的時候,它會往79這個地址所對應的內存單元裏寫入x的初始值10,那這是我們剛才提到的情況。我們的這個程序裝入模塊,它是從內存地址為零這個地方開始往後依次存放的,因此我們的指令當中指明的這些地址並不會出現什麼問題。安全
那接下來再來看另外一種情況。假設我們的這個程序的裝入模塊,它裝入內存的時候,並非從地址為零的地方開始的,而是從地址為100的這個地方開始的。那麼這就意味著操做系統給這個進程、給這個程序分配的地址空間其實是一百到279這麼多,因此若是是這種情況的話,這個程序的這些邏輯地址和它最終存放的物理地址就會出現對應不上的情況。好比説我們的指令零是要給x這個變量進行初始化,可是這個指令指明瞭x這個變量的值它是要寫到地址為79的那個內存單元當中的,因此若是CPU執行這條指令的話,數據結構
它就會把x的值10把它寫在上面的這個地方,79這個地址所對應的內存單元裏。而這上面的這一片內存空間,極有多是分配給其餘進程的,因此也就意味著本來是這個進程它本身的數據然而它強行往其餘進程的那個地址空間裏去寫入了本身的數據。那這顯然是一個危險的而且應該被阻止的一種行爲。而事實上在這個例子當中,我們期待的x這個變量的正確的存放位置,應該是從它的這個起始位置開始往後79個單位這樣的一個內存單元裏,也就是179這個地址所對應的內存單元當中。若是x的值寫在這兒,那就是沒問題的。相信你們對邏輯地址和物理地址應該有一個比較直觀的體會了。總之我們的程序它編譯鏈接等等之後,所造成的這些指令當中通常來説使用的是邏輯地址,也就是相對地址。而這個程序最終被裝到內存的什麼位置,這個其實是我們沒辦法確定的,因此在內存管理這個章節當中有一個很重要的我們須要解決的問題就是如何把這些指令當中所指明的這些邏輯地址把它轉換為最終的物理地址、正確的物理地址。那這個小節當中我們會介紹三種策略來解決這個地址轉換的問題。這三種策略分別是絕對裝入、可重定位裝入(靜態重定位)和動態運行時裝入(動態重定位)。那我們會依次來看一下這三種策略是怎麼解決這個問題的。併發
首先來看第一種策略,絕對裝入。所謂的「絕對裝入」就是指,若是我們能夠在程序放入內存以前就知道這個程序會從哪個位置開始存放,那在這種情況下我們其實就能夠讓編譯程序把各個變量存放的那些地址直接把它修改爲一個正確的一個絕對地址。那還是以剛才的那個例子為例。好比說我們先前就已經知道了我們的那個裝入模塊它是要從地址為100的地方開始存放的,那麼按照以前我們的介紹來説,這個裝入模塊它裏面所使用到的這些地址都是相對地址,可是若是我們知道它是從100這個地址開始裝入的,框架
那其實在編譯的時候就能夠由編譯器把它改爲正確的地址。好比按照以前的分析我們知道,x那個變量它正確的存放地址應該是179。因此接下來我們把這個裝入模塊從起始地址為100的這個地方開始裝入,那麼當這個程序運行的時候就能夠把它的這些變量存放到一個正確的位置了,因此這是第一種方式。在編譯的那個時候,就把邏輯地址轉換成最終的物理地址。可是有一個前提就是我們須要知道我們的裝入模塊它會裝到內存的哪個位置,從什麼地方開始裝。因此這種方式的靈活性其實不好,它只適用於單道程序的環境,也就是早期的還沒有操做系統的那個階段,使用的就是這樣的一種方式。你們能夠想一下,若是採用絕對裝入這種方式的話,那麼假設個人這個可執行文件此時要運行在另一臺電腦當中,而另外一臺電腦當中又不能讓它從100這個位置開始存放,那是否是就意味著這個程序換一臺電腦它就不能執行了,因此這種方式它的靈活性是特別低的。函數
第二種裝入方式叫作可重定位裝入,又叫靜態重定位方式。若是採用這種方式的話,那麼編譯、鏈接最終造成的這個裝入模塊這些指令當中使用的地址依然是從0開始的邏輯地址,也就是相對地址。而把這個地址重定位這個過程是留在了裝入模塊裝入內存的時候進行。好比說這個裝入模塊裝到內存裏之後,它的起始物理地址是100,那麼若是我們採用的是靜態重定位這種方式的話,就意味著在這個程序裝入內存的時候,我們同時還須要把這個程序當中所涉及的全部的這些和地址相關的參數都把它進行加100的操做。好比說指令0我們就須要把它加100,然後指令1也對79這個內存單元進行了操做,因此這個地址我們也須要把它加100。因此靜態重定位這種方式就是在我們的程序裝入內存的時候再進行這個地址的轉換。那這種方式的特點是我們給這個做業分配的這些地址空間必須是連續的,而且這個做業必須一次所有裝入內存。也就是說在它執行以前就必須給它分配它所須要的所有的內存空間。難道還能夠只分配它所須要的部分空間嗎?那這個問題你們在學習了之後的虛擬存儲技術之後就會有更深刻的瞭解。而且這個地方其實也不是特別重要。那靜態重定位這種方式它還有一個特點就是,在這個程序運行期間它是不能夠移動的。這個很好理解,因爲我們的這些指令當中已經寫死了我們具體要操做那個物理地址究竟是多少。若是這個程序這個進程相關的這一系列的數據發生了移動的話,那麼這個地址的指向又會發生錯誤。因此這是靜態重定位這種方式的一個侷限性。
那最後我們來看一下現代的系統使用的這種地址轉換的機制,叫作動態重定位,又叫動態運行時裝入。那若是採用的是這種方式的話,程序經過編譯鏈接最後造成的裝入模塊當中,它這些指令所使用的其實也是邏輯地址也就是相對地址。而且這個可執行文件這個程序在裝入內存的時候,它們的這個指令當中所使用的同樣還是邏輯地址。若是一個系統支持這種動態重定位方式的話,那這個系統當中還須要設置一個專門的一個寄存器叫作重定位寄存器。重定位寄存器當中存放了這個進程,或者說這個做業它在內存當中的起始地址是多少,好比說我們的這個程序這個進程它是從起始地址為100的這個地方開始存放的,因此重定位寄存器當中我們就存放它的起始地址100。而當CPU在執行相應的這些指令的時候,好比說它在執行指令0的時候,這個指令0是讓他往地址為79的存儲單元當中寫入x這個變量的初始值10。CPU在對一個內存地址進行訪問的時候,它會作這樣的事情。它把邏輯地址和重定位寄存器當中存放的這個起始地址進行一個相加的操做,然後加出來的這個地址纔是最終它能夠訪問的地址。因此經過這樣的一步處理它就知道,指令0是讓它往地址為179的這個地方寫入數據10。那很顯然若是採用這種方式的話,我們想讓進程的數據在運行的過程當中發生移動是很方便的。好比說我們把這個進程的數據把它移到從200開始的話,那很簡單。我們只須要把重定位寄存器的值再修改爲200就能夠了,因此動態重定位方式有不少不少的優點。
它能夠把程序分配到不連續的存儲區。那不連續的分配這個現在先不展開,經過後續的學習你們會有更深刻的理解,這兒先簡單提一下。那這些內容現在還可能都看不懂,咱們在學習了以後的虛擬存儲管理以後就能夠對這個特性有更深刻的理解了。那這個地方咱們也暫時不展開,把這個點的理解日後挪一挪。
好的那麼剛纔咱們介紹了內存的基本知識,介紹了內存的地址,介紹了什麼叫邏輯地址什麼叫物理地址,而且也介紹了三種裝入方式來解決了邏輯地址到物理地址的轉換這樣的一個過程。那接下來咱們再從一個更宏觀更全局的這樣的一個角度再來看一下咱們從寫程序到程序運行它所經歷的步驟。目標模塊文件在C語言裏就是.o文件。而且這些目標模塊當中其實已經包含了這些代碼所對應的那些指令了,而這些指令的編址,都是一個邏輯地址也就是相對地址。每個模塊的編址都是從邏輯地址0開始的。因此通過了編譯以後咱們就把高級語言翻譯成了與它們等價的機器語言。只不過每個模塊的邏輯地址的編址都是相互獨立的,都是從0開始的。那接下來的一步叫作連接。這一步作的事情就是把這些目標模塊都給組裝起來,造成一個完整的裝入模塊。而在Windows電腦當中,所謂的裝入模塊就是咱們很熟悉的.exe文件,也就是可執行文件。把這些目標模塊連接起來以後,所造成的裝入模塊,就有一個完整的邏輯地址。固然在連接這一步,除了咱們本身編寫的這些目標模塊須要連接之外,還須要把它們所調用到的一些庫函數好比說printf啊之類的這些函數,也給連接起來,把它造成一個完整的裝入模塊。那有了裝入模塊或者說有了這個可執行文件以後,咱們就可讓這個程序開始運行了。那程序要運行首先要作的事情就是咱們剛纔一直強調的那個過程,就是須要把這個裝入模塊裝入內存當中,而且當它裝入內存以後就肯定了這個進程它所對應的實際的物理地址究竟是多少。因此這就是咱們從寫程序到程序運行的一個完整的流程。那以前咱們一直強調的是,裝入這個步驟怎麼完成,三種裝入的策略能夠實現邏輯地址到物理地址的轉換。那接下來咱們要介紹的是三種連接的方式,也就是這一步也有三種方法。
第一種連接方式叫作靜態連接,就是指在程序運行以前就把這些一個一個的目標模塊把它們連接成一個完整的可執行文件,也就是裝入模塊,以後便再也不拆開,就是剛纔咱們所提到的這種方式。也就是說在造成了這個裝入模塊以後,就肯定了這個裝入模塊的完整的邏輯地址。
那第二種連接方式叫作裝入式動態連接,就是說這些目標模塊不會先把它們連接起來,而是當這些目標模塊放入內存的時候纔會進行連接這個動做。
也就是說採用這種方式的話,這個進程的完整的邏輯地址是一邊裝入一邊造成的。
那第三種方式叫作運行時動態連接,若是採用這種方式的話那麼只有咱們須要用到某一個模塊的時候才須要把這個模塊調入內存。好比說剛開始是main函數運行,那麼咱們就須要把目標模塊1先放到內存當中,而後執行的過程中可能又發現main函數須要調用到a這個函數,因此咱們須要把目標模塊2也把它放到內存當中,而且把它裝入的時候同時進行一個連接的工做。那若是說b這個函數在整個過程中都用不到的話,那目標模塊3咱們就能夠不裝入內存。因此採用這種方式很顯然它的這個靈活性要更高,而且用這種方式能夠提高對於內存的利用率。
而一個存儲單元能夠存放多少數據,這個咱們須要看這個計算機它究竟是按字節編址仍是按字編址。若是是按字節編址的話,那麼一個存儲單元就是存放一個字節,也就是一個大B一個Byte。那內存地址其實就是給這些存儲單元的一個編號,CPU能夠根據內存地址這個參數來找到正確的存儲單元。那以後咱們又簡單地介紹了指令工做的原理。一條機器指令由操做碼和一些參數組成。操做碼給CPU指明瞭你如今須要幹一些什麼事情,而參數指明瞭你如今須要怎麼幹。而這個參數當中可能會包含地址參數,而通常來講這個指令中所包含的地址參數指的都是邏輯地址也就是相對地址。因此爲了讓這個指令正常地工做,咱們就須要完成從邏輯地址到物理地址的一個轉換。那爲了完成邏輯地址到物理地址的轉換,咱們又介紹了三種裝入方式,分別是絕對裝入、可重定位裝入和動態運行時裝入。其中可重定位裝入又稱做爲靜態重定位,而動態運行時裝入又稱爲動態重定位。這三種裝入方式是考研當中比較喜歡考查的內容。
那最後咱們還介紹了從咱們程序員寫程序到最後的程序運行須要經歷哪些步驟。首先是要編輯源代碼文件,而後源代碼文件通過編譯造成若干的目標模塊。目標模塊通過連接以後造成裝入模塊,最後再把裝入模塊裝入到內存。這個程序就能夠開始正常地運行了。那咱們還介紹了三種連接的方式分別是靜態連接、裝入時動態連接和運行時動態連接。其實通過剛纔的講解咱們可以體會到,連接這一步就是要把各個目標模塊的那些邏輯地址,把它們組合起來造成一個完整的邏輯地址,因此連接這一步其實就是肯定這個完整的邏輯地址這樣的一個步驟。而裝入這一步又是肯定了最終的物理地址,這個小節的內容其實考查的頻率很低,只不過是爲了讓你們更深刻地理解以後的內容因此才進行了一些補充。
咱們知道操做系統它做爲系統資源的管理者,固然也須要對系統當中的各類軟硬件資源進行管理,包括內存這種資源。那麼操做系統在管理內存的時候須要作一些什麼事情呢?咱們知道各類進程想要投入運行的時候,須要先把進程相關的一些數據放入到內存當中,就像這個樣子。那麼內存當中,有的區域是已經被分配出去的,而有的區域是還在空閒的。操做系統應該怎麼管理這些空閒或者非空閒的區域呢?另外,若是有一個新進程想要投入運行,那麼這個進程相關的數據須要放入內存當中。可是若是內存當中有不少個地方均可以放入這個進程相關的數據,那這個數據應該放在什麼位置呢?這也是操做系統須要回答的問題。第三,若是說有一個進程運行結束了,那麼這個進程以前所佔有的那些內存空間,應該怎麼被回收呢?那全部的這些都是操做系統須要負責的問題。所以,內存管理的第一件事就是要操做系統來負責內存空間的分配與回收。那內存空間的分配與回收這個問題比較龐大,如今暫時不展開細聊,以後還會有專門的小節進行介紹。
計算機當中也常常會遇到實際的內存空間不夠全部的進程使用的問題。因此操做系統對內存進行管理,也須要提供某一種技術,從邏輯上對內存空間進行擴充,也就是實現所謂的虛擬性,把物理上很小的內存拓展爲邏輯上很大的內存。那這個問題也暫時不展開細聊,以後還會有專門的小節進行介紹。
第三個須要實現的事情是地址轉換。爲了讓編程人員編程更方便,程序員在寫程序的時候應該只須要關注指令、數據的邏輯地址。而邏輯地址到物理地址的轉換,或者說地址重定位這個過程應該由操做系統來負責進行,這樣的話程序員就不須要再關心底層那些複雜的硬件細節。因此內存管理的第三個功能就是應該實現地址轉換。就是把程序當中使用的邏輯地址,把它轉換成最終的物理地址。那麼實現這個轉換的方法,我們在上個小節已經介紹過,
就是用三種裝入方式分別是絕對裝入、可重定位裝入和動態運行時裝入。絕對裝入是在編譯的時候就產生了絕對地址或者說在程序員寫程序的時候直接就寫了絕對地址。那麼這種裝入方式只在單道程序階段才使用。可是單道程序階段其實暫時尚未產生操做系統,因此這個地址轉換實際上是由編譯器來完成的,而不是由操做系統來完成的。那第二種方式叫作可重定位裝入,或者叫靜態重定位,就是指在裝入的時候把邏輯地址轉換爲物理地址,那這個轉換的過程是由裝入程序負責進行的。那裝入程序也是操做系統的一部分。那這種方法通常來講是用於早期的多道批處理操做系統當中。那第三種裝入方式叫作動態運行時裝入或者叫動態重定位,就是運行的時候才把邏輯地址轉換爲物理地址,固然這種轉換方式通常來講須要一個硬件——重定位寄存器的支持。而這種方式通常來講就是現代操做系統採用的方式,我們以後在學習頁式存儲還有段式存儲的時候會大量地接觸這種動態運行時裝入的方式。因此說操做系統通常會用可重定位裝入和動態運行時裝入這兩種方式實現從邏輯地址到物理地址的轉換。而採用絕對裝入的那個時期暫時尚未產生操做系統。那這就是內存管理須要實現的第三個功能——地址轉換。
第四個功能叫內存保護。就是指操做系統要保證各個進程在各自存儲空間內運行,互不干擾。
咱們直接用一個圖讓你們更形象地理解。在內存當中通常來講會分爲操做系統使用的內存區域還有普通的用戶程序使用的內存區域。那各個用戶進程都會被分配到各自的內存空間,好比說進程1使用的是這一塊內存區域,進程2使用的是這一塊內存區域。那若是說進程1想對操做系統的內存空間進行訪問的話,很顯然這個行爲應該被阻止。若是進程1能夠隨意地更改操做系統的數據,那麼很明顯會影響整個系統的安全。另外若是進程1想要訪問其餘進程的存儲空間的話,那麼顯然這個行爲也應該被阻止。若是進程1能夠隨意地修改進程2的數據的話,那麼顯然進程2的運行就會被影響,這樣也會致使系統不安全。因此進程1只能訪問進程1本身的那個內存空間,因此這就是內存保護想要實現的事情。讓各個進程只能訪問本身的那些內存空間,而不能訪問操做系統的也不能訪問別的進程的空間。那我們能夠採用這樣的方式來進行內存保護,就是在CPU當中設置一對上限寄存器和下限寄存器,分別用來存儲這個進程的內存空間的上限和下限。那若是進程1的某一條指令想要訪問某一個內存單元的時候,CPU會根據指令當中想要訪問的那個內存地址和上下限寄存器的這兩個地址進行對比。只有在這兩個地址之間才允許進程1訪問,因爲只有這兩個地址之間的這個部分才屬於進程1的內存空間。那這是第一種方法,能夠設置一對上下限寄存器。
第二種方法我們能夠採用重定位寄存器和界地址寄存器來判斷此時是否有越界的嫌疑。那麼重定位寄存器又能夠稱爲基址寄存器,界地址寄存器又稱爲限長寄存器。那重定位寄存器的概念咱們在上個小節已經接觸過,就是在動態運行時裝入那種方式裏,我們須要設置一個重定位寄存器,來記錄每一個進程的起始物理地址。界地址寄存器又能夠稱爲限長寄存器,就是用來存放這個進程的最大邏輯長度的。好比說像進程1它的邏輯地址是0~179,因此界地址寄存器當中應該存放的是它的最大的邏輯地址也就是179。而重定位寄存器的話應該存放這個進程的起始物理地址,也就是100。那麼假如現在進程1想要訪問邏輯地址為80的那個內存單元的話,首先這個邏輯地址會和界地址寄存器當中的這個值進行一個對比。若是說沒有超過界地址寄存器當中保存的最大邏輯地址的話,那麼我們就認爲這個邏輯地址是合法的。若是超過了,那麼會拋出一個越界異常。那沒有越界的話,邏輯地址會和重定位寄存器的這個起始物理地址進行一個相加,最終就能夠獲得實際的想要訪問的物理地址也就是180。
那這個小節中咱們學習了內存管理的總體框架。內存管理總共須要實現四個事情,內存空間的分配與回收,內存空間的擴充以實現虛擬性,另外還須要實現邏輯地址到物理地址的轉換。那麼地址轉換通常來講有三種方式,就是上個小節學習的內容——絕對裝入、可重定位裝入和動態運行時裝入。其中絕對裝入這個階段實際上是在早期的單道批處理階段才使用的,這個階段暫時尚未操做系統產生。而可重定位裝入通常用於早期的多道批處理系統,如今的操做系統大多使用的是動態運行時裝入。另外呢內存管理還須要提供存儲保護的功能,就是要保證各個進程它們只在本身的內存空間內運行,不會越界訪問。那通常來講有兩種方式,第一種是設置上下限寄存器。第二種方式是利用重定位寄存器和界地址寄存器進行判斷。那麼重定位寄存器又能夠叫作基址寄存器,而界地址寄存器又能夠叫作限長寄存器。這兩個別名你們也須要注意。那麼本章以後內容還會介紹更多的內存空間的分配與回收,還有內存空間的擴充的一些相關策略。那這個小節的內容不算特別重要,只是爲了讓你們對內存管理到底須要作什麼造成一個大致的框架。
那在以前的小節中我們已經學習到了操做系統對內存進行管理須要實現這樣四個功能。那地址轉換和存儲保護是上個小節詳細介紹過的。那這個小節我們會介紹兩種實現內存空間的擴充的技術——覆蓋技術和交換技術,那虛擬存儲技術會在之後用更多的專門的視頻來進行講解。
通常來説都不多有低於100MB字節的這種程序。因此可想而知1MB字節的大小不少時候應該是不能滿足這些程序的運行的。那麼後來人們爲了解決這個問題就引入了覆蓋技術,就是解決程序大小超過物理內存總和的問題。好比說一個程序原本須要這麼多的內存空間,但實際的內存大小又只有這麼多。那怎麼辦呢?覆蓋技術的思想就是要把程序分紅多個段,或者理解爲就是多個模塊。而後經常使用的段就須要常駐內存,不經常使用的段只有在須要的時候才須要調入內存。那內存當中會分一個「固定區」和若干個「覆蓋區」,經常使用的那些段須要放在固定區裏,而且調入以後就再也不調出,除非運行結束,這是固定區的特徵。那不經常使用的段就能夠放在「覆蓋區」裏,只有須要的時候才須要調入內存,也就是調入內存的覆蓋區,而後用不到時候就能夠調出內存。
A這個模塊會依次調用B模塊和C模塊。注意是依次調用,也就是說B模塊和C模塊只可能被A這個模塊在不一樣的時間段調用,不多是同時訪問B和C這兩個模塊。另外因爲B模塊和C模塊不可能同時被訪問,也就是說在同一個時間段內內存當中要麼有B要麼有C就能夠了,不須要同時存在B和C這兩個模塊。因此咱們可讓B和C這兩個模塊共享一個覆蓋區,那這個覆蓋區的大小以B和C這兩個模塊當中更大的這個模塊爲準,也就是10KB。由於若是咱們把這個覆蓋區設爲10KB的話,那既能夠存的下C也能夠存的下B。那一樣的,D、E、F這幾個模塊也不可能同時被使用。因此這幾個模塊也能夠像上面同樣共享一個覆蓋區,覆蓋區1,那它的大小就是按它們之間最大的這個也就是D模塊的大小12KB來計算。因此若是說咱們的程序有一個明顯的這種調用結構的話,那麼咱們能夠根據它這種自身的邏輯結構,讓這些不可能被同時訪問的程序段共享一個覆蓋區。那只有其中的某一個模塊被使用的時候,那這個模塊才須要放到覆蓋區裏。因此採用了覆蓋技術以後,在邏輯上看這個物理內存的大小是被拓展了的。不過這種技術也有一個很明顯的缺點,由於這個程序當中的這些調用結構操做其實系統確定是不知道的,因此程序的這種調用結構必須由程序員來顯性地聲明,而後操做系統會根據程序員聲明的這種調用結構或者說覆蓋結構,來完成自動覆蓋。因此這種技術的缺點就是對用戶不透明,增長了用戶編程的負擔。所以,覆蓋技術如今已經不多使用了,它通常就只用於早期的操做系統中,如今已經退出了歷史的舞臺。
因此其實採用這種技術(交換技術/對換技術)的時候,進程是在內存與磁盤或者說外存之間動態地調度的。那以前咱們其實已經提到過一個和交換技術息息相關的知識點,我們在第二章講處理機調度的時候,講過一個處理機調度層次的概念,分爲高級調度、中級調度和低級調度。那其中中級調度就是爲了實現交換技術而使用的一種調度策略。就是說本來我們的內存當中有不少進程正在並發地運行,那若是某一個時刻忽然發現內存空間緊張的時候我們就能夠把其中的某些進程把它放到暫時換出外存。
而進程相關的PCB會保留在內存當中,而且會插入到所謂的掛起隊列裏。那一直到內存空間不緊張了,內存空間充足的時候又能夠把這些進程相關的數據再給換入內存。那爲什麼進程的PCB須要常駐內存呢?因爲進程被換出外存之後其實我們必須要通過某種方式記錄下來這個進程究竟是放在外存的什麼位置,那這個信息也就是進程的存放位置這個信息,我們就能夠把它記錄在與它對應的這些PCB當中。那操做系統就能夠根據PCB當中記錄的這些信息,對這些進程進行管理了,因此進程的PCB是須要常駐內存的。
那麼中級調度或者説內存調度,其實就是在交換技術當中,選擇一個處於外存的進程把它換入內存這樣一個過程。那講到這個地方你們也須要再回憶一下低級調度和高級調度分別是什麼。
那既然提到了掛起我們就再來回憶一下和掛起相關的知識點。暫時換出外存等待的那些進程的狀態稱之爲掛起狀態或者簡稱掛起態。那掛起態又能夠進一步細分為就緒掛起和阻塞掛起兩種狀態。在引入了這兩種狀態之後我們就提出了一種叫作進程的七狀態模型。那若是一個本來處於就緒態的進程被換出了外存,那這個進程就處於就緒掛起態。若是一個本來處於阻塞態的進程被換出外存的話,那麼這個進程就處於阻塞掛起態。那七狀態模型相關的知識點咱們在第二章當中已經進行過補充,這兒就再也不贅述。那你們能夠再結合這個圖回憶一下這些狀態之間的轉換是怎麼進行的,特別是中間的這三個最基本的狀態之間的轉換。因此採用了交換技術之後,若是說某一個時刻內存裏的空間不夠用了,那麼我們能夠把內存中的某一些進程數據暫時換到外存裏,再把某一些更緊急的進程數據放回內存,因此交換技術其實也在邏輯上擴充了內存的空間。
在現代計算機當中,外存通常來説就是磁盤。那具備對換功能或者說交換功能的操做系統當中,通常來説會把磁盤的存儲空間分爲文件區和對換區這樣兩個區域。文件區主要是用來存放文件的,主要是須要追求存儲空間的利用率。因此在對文件區,通常來説是採用離散分配的方式。而這個地方一會兒再作解釋。那對換區的空間通常來説只佔磁盤空間的很小的部分,注意被換出的進程數據通常來説就是存放在對換區當中的,而不是文件區。那由於對換區的這個換入換出速度會直接影響到各個進程並發執行的這種速度,因此對於對換區來説我們應該首要追求換入換出的速度。所以對換區一般會經常採用連續分配的方式。那這個地方你們理解不了暫時沒有關係,咱們在第四章文件管理的那個章節會具體地再進一步學習什麼是對換區什麼是文件區,而且到時候你們就能夠理解爲什麼離散分配方式能夠更大地提升存儲空間的利用率,而連續分配方式能夠提升換入換出的速度。那這個地方你們只須要理解一個結論,對換區的I/O速度或者説輸入輸出的速度,是要比文件區更快的。因此我們的進程數據被換出的時候,通常是放在對換區,換入的時候也是從對換區再換回內存。
通常來説交換會發生在系統當中有不少進程在運行而且內存吃緊的時候。那在這種時候,我們能夠選擇換出一些進程來騰空內存空間那一直到系統負荷明顯下降的時候就能夠暫停換出。好比說若是操做系統在某一段時間發現許多進程運行的時候都經常發生缺頁,那這就説明內存的空間不夠用,因此這種時候就能夠選擇換出一些進程來騰空一些內存空間。那若是說缺頁率明顯降低,也就是說看起來系統負荷明顯下降了,我們就能夠暫停換出進程了。那這個地方涉及到之後的小節會講到的缺頁還有缺頁率這些相關的知識點。這兒理解不了沒有關係,你們能夠有個印象就能夠了。
首先我們能夠考慮優先換出一些阻塞的進程。因爲處於就緒態的進程,其實是能夠投入運行的。而處於阻塞態的進程,即便是在內存當中反正它暫時也運行不了了,因此我們能夠優先把阻塞進程調出換到外存當中。第二,我們能夠考慮換出優先級比較低的進程。那這個不用解釋,很好理解。第三,若是我們每次都是換出優先級更低的進程的話,那麼就有可能導致優先級低的進程剛被調入內存很快又被換出的問題。那這就有可能會導致優先級低的進程飢餓的現象。因此有的系統當中爲了防止這種現象,會考慮進程在內存當中的駐留時間。若是一個進程在內存當中駐留的時間過短,那這個進程就暫時不會把它換出外存。那這個地方再強調一點,PCB是會常駐內存的,並不會被換出外存。因此其實所謂的換出進程,並非把進程相關的全部的數據一個不漏的所有都調到外存裏,操做系統爲了保持對這些換出進程的管理,那PCB這個信息還是須要放在內存當中。那麼這就是交換技術。
那這個小節咱們學習了覆蓋技術和交換技術相關的知識點。那這兩個知識點通常來講只會在選擇題當中進行考查。你們只要可以理解這兩種技術的思想就能夠了。那麼可能稍微須要記憶一點的就是,固定區和覆蓋區相關的這些知識點。在固定區當中的程序段,在運行過程中是不會被調出的。而在覆蓋區當中的程序段,在運行過程中是有可能會根據須要進行調入調出的。另外,若是考查了覆蓋技術的話,那麼頗有可能會把覆蓋技術的缺點做爲其中的某一個選項進行考查。那在講解交換技術的過程中咱們補充了文件區和對換區相關的知識點,這些會在第四章中進行進一步的學習。那這個地方你們只須要知道換出的進程的數據通常來講是放在磁盤的對換區當中的。那最後咱們再來看一下覆蓋與交換這兩種技術的一個明顯的區別。其實覆蓋技術是在同一個程序或者進程當中進行的。那相比之下交換技術是在不一樣進程或做業之間進行的,而暫時運行不到的進程能夠調出外存。那比較緊急的進程能夠優先被再從新放回內存。
在以前的學習中咱們知道,操做系統對內存進行管理,須要實現這樣四個事情。那麼內存空間的擴充,地址轉換和存儲保護,這是以前的小節介紹過的內容。從這個小節開始咱們會介紹內存空間的分配與回收應該怎麼實現。咱們在這個小節中會先介紹連續分配管理方式,分別是單一連續分配,固定分區分配和動態分區分配。咱們會按從上至下的順序依次講解。那麼這兒須要注意的是,所謂的連續分配和非連續分配的區別在於,連續分配是指,系統爲用戶進程分配的必須是一個連續的內存空間。而非連續分配管理方式是指系統爲用戶分配的那些內存空間不必定是連續的,能夠是離散的。
那麼咱們先來看單一連續分配方式。採用單一連續分配方式的系統當中,會把內存分爲一個系統區和一個用戶區。那系統區就是用於存放操做系統相關的一些數據,用戶區就是用於存放用戶進程或者說用戶程序相關的一些數據。不過須要注意的是,採用單一連續分配方式的這種系統當中,內存當中同一時刻只能有一道用戶程序。也就是說它並不支持多道程序併發運行,因此用戶程序是獨佔整個用戶區的,無論這個用戶區有多大。好比說一個用戶進程或者說用戶程序,它原本只須要這麼大的內存空間。
那當它放到內存的用戶區以後,用戶區當中其餘那些空閒的區間其實也不會被分配給別的用戶程序。因此說是整個用戶程序獨佔整個用戶區的這種存儲空間的。因此這種方式其實優勢很明顯就是實現起來很簡單,而且沒有外部碎片。那外部碎片這個概念咱們在講到動態分區分配的時候再補充,這兒先有個印象。那因爲整個系統當中同一時刻只會有一個用戶程序在運行,因此採用這種分配方式的系統當中不必定須要採用內存保護。注意只是不必定,有的系統當中它也會設置那種越界檢查的一些機制。可是像早期的我的操做系統,微軟的DOS系統,就沒有采用這種內存保護的機制。由於系統中只會運行一個用戶程序,那麼即便這個用戶程序出問題了,那也只會影響用戶程序自己,或者說即便這個用戶程序越界把操做系統的數據損壞了,那這個數據通常來講也能夠經過重啓計算機就能夠很方便地就進行修復。因此說採用單一連續分配方式的系統當中,不必定採起內存保護,那這也是它的優勢。那另外一方面,這種方式的缺點也很明顯,就是隻適用於單用戶、單任務的操做系統,它並不支持多道程序併發運行,而且這種方式會產生內部碎片。那所謂的內部碎片,就是指咱們分配給某一個進程或者說程序的內存區間當中,若是有某一個部分沒有被用上,那這就是所謂的內部碎片。像這個例子當中,原本整個用戶區都是分配給這個用戶進程A的,可是有這麼大一塊它是空閒的,暫時沒有利用起來。那原本給這個進程分配了,可是這個進程沒有用上的這一部份內存區域就是所謂的內部碎片。因此這種方式也會致使存儲器的利用率很低。那這是單一連續分配方式。
多道程序技術就是可讓各個程序同時裝入內存,而且這些程序之間不能相互干擾,因此人們就想到了把用戶區劃分紅了若干個固定大小的分區,而且在每個分區內只能裝入一道做業。也就是說每一道做業或者說每一道程序它是獨享其中的某一個固定大小的分區的。那這樣的話就造成了最先的能夠支持多道程序的內存管理方式。那固定分區分配能夠分爲兩種,一種是分區大小相等,另一種是分區大小不等。若是說採用的是分區大小相等的策略的話,系統會把用戶區的這一整片的內存區間分割爲若干個固定大小而且大小相等的區域。
好比說每一個區域十個字節,像這樣子。那若是說採用的是分區大小不相等的這種策略的話,系統會把用戶區分割爲若干個大小固定可是大小又不相等的分區,好比說像這個樣子。那這兩種方式各有各的特色,若是說採用的是分區大小相等的這種策略的話,很顯然會缺少靈活性。好比說一些小的進程它可能只須要佔用很小的一部份內存空間,可是因爲每一個分區只能裝入一道做業,因此一個很小的進程又會佔用一個比較大的、不少餘的一個分區。那若是說一個有一個比較大的進程進入的話,那麼若是這些分區的大小都不能知足這個大進程的需求,那麼這個大進程就不能被裝入這個系統,或者說只能採用覆蓋技術,在邏輯上來拓展各個分區的大小。但這又顯然又會增長一些系統開銷。因此說分區大小相等的這種狀況是比較缺少靈活性的,不過這種策略即便在現代也是有很普遍的用途的。那因爲這n個鍊鋼爐原本就是相同的對象,因此對這些相同的對象進行控制的程序固然也是相同的程序。因此若是採用這種把它分割爲n個大小相等的區域來分別存放n個控制程序,讓這n個控制程序併發執行,併發地控制各個鍊鋼爐的話,那在這種場景下的應用就是很適合的。那若是分區大小不等的話,靈活性會有所增長。好比說小的進程咱們能夠給它分配一個小的分區,而大的進程能夠給它分配一個大的分區。那通常來講能夠先估計一下系統當中會出現的大做業、小做業分別到底有多少。而後再根據大小做業的比例來對這些大小分區的數量進行一個劃分。好比說能夠劃分多個小分區,適量的中等分區、而後少許的大分區。
那接下來咱們再考慮下一個問題,操做系統應該怎麼記錄內存當中各個分區的空閒或者分配的這些狀況呢?那通常來講咱們能夠創建一個叫作分區說明表的一個數據結構,用這個數據結構對各個分區進行管理。好比說若是系統當中內存的狀況是這個樣子,那麼咱們能夠給它創建一個對應的分區說明表。那每個表項會對應其中的某一個分區,那每個表項須要包含當前這個分區的大小還有這個分區的起始地址還有這個分區是否已經被分配的這種狀態。那像這樣一張表其實咱們能夠創建一個數據結構,數據結構當中有這樣一些屬性,而後把這個用這個數據結構組成一個數組或者組成一個鏈表來表示這樣一個表。那若是學過數據結構的同窗這兒應該不難理解。那操做系統根據這個數據結構就能夠知道各個分區的使用狀況,若是說一個用戶程序想要裝入內存的話,操做系統就能夠來檢索這個表,而後找到一個大小可以知足而且沒有被分配出去的分區,而後把這個分區分配給用戶程序。以後再把這個分區對應的狀態改爲已分配的狀態就能夠了。那麼固定分區分配實現起來其實也不算複雜,而且使用這種方式也不會產生外部碎片。那麼外部碎片這個概念我們再日後拖一拖,下一個分配方式當中會進行講解。可是這種方式也有很明顯的缺點。若是說一個用戶程序太大了,大到沒有任何一個分區能夠直接知足它的大小的話,那麼咱們只能經過覆蓋技術來解決這個分區大小不夠的問題。可是若是採用了覆蓋技術,那就意味着須要付出必定的代價,會下降整個系統的性能。另外,這種分配方式很顯然也會產生內部碎片,好比說有一個用戶程序它所須要的內存空間是10MB,那麼掃描了這個表以後會發現,只有分區6能夠知足10MB這麼大的需求,因此這個用戶程序就會被裝到分區6裏。可是因爲這個用戶程序會獨佔整個分區6,因此分區6總共有12MB,那麼就有兩兆字節的空間是分配給了這個程序,那這個程序又用不到的。那這一部分就是所謂的內部碎片。因此固定分區分配是會產生內部碎片的,所以它的內存利用率也不是特別高。
動態動態
那麼,爲了解決這個問題人們又提出了動態分區分配的策略。動態分區分配又能夠稱做可變分區分配,這種分配方式並不會像以前固定分區分配那樣預先劃份內存區域。而是在進程裝入內存的時候纔會根據進程的大小動態地創建分區。而每個分區的大小會正好適合進程所須要的那個大小。因此和固定分區分配不一樣,若是採用動態分區分配的話,系統當中內存分區的大小和數目是能夠改變的。那咱們來看一個例子。好比說一個計算機的內存大小總共是64MB字節,而後系統區會佔8MB字節,那用戶區就是56MB字節。剛開始一個用戶進程1到達,它總共佔用了20MB字節的分區,以後一個用戶進程2到達,佔用了14MB字節的分區,用戶進程3到達,佔用了18MB字節的分區。那麼56MB字節的用戶區總共只會佔4MB字節的空閒分區。那麼系統中這些分區的大小和數量是可變的,而且有些分區是已經被分出去的,有些分區又是沒有被分出去的。操做系統應該用什麼樣的數據結構來記錄這個內存的使用狀況呢?這是咱們以後要探討的第一個問題。那再來看第二個問題,
若是此時佔有14MB字節的進程2已經運行結束了,而且被移出了內存,那麼內存當中就會有這樣一片14MB字節的空閒區間,那此時若是有一個新進程到達,而且這個進程須要4兆字節的內存空間。那這一片空閒區間是14MB,這一片空閒區間是4MB。那到底應該放這一片仍是放下面這一片呢?這又是第二個問題。當咱們的內存當中有不少個空閒分區均可以知足進程的需求的時候,應該把哪一個空閒區間分配給那個進程呢?這是第二個問題。
第三個問題,假設此時佔18MB字節的進程三運行結束,而且被撤離了內存。那麼內存當中就會出現18MB字節的一個新的空閒分區。那這個空閒分區應該怎麼處理?是否應該和與它相鄰的這些分區進行合併呢?這就是第三個問題,咱們應該如何進行分區的分配和回收的操做。那接下來咱們依次對這三個問題進行探討。
先來看第一個問題,操做系統應該用什麼樣的數據結構記錄內存的使用狀況?那通常來講會採用兩種經常使用的數據結構,要麼是空閒分區表,或者採用空閒分區鏈。好比某一個時刻系統當中內存的使用狀況是這個樣子。總共有三個空閒分區,那麼若是採用空閒分區表的話,這個表就會有三個對應的表項,每個表項會對應一個空閒分區,而且每個表項都須要記錄與這個表項相對應的空閒分區的大小是多少,起始地址是多少等等一系列的信息。那若是說沒有在空閒分區表當中記錄的那些分區固然就是已經被分配出去的。再來看第二種數據結構,空閒分區鏈。若是採用這種方式的話,那麼每個分區的起始部分和末尾部分,都會分別設置一個指向前面一個空閒分區和指向後面一個空閒分區的指針,就像這個樣子。因此就會把這些空閒分區用一個相似於鏈表的方式把它們連接起來。那每個空閒分區的大小,還有空閒分區的起始地址,結尾地址等等這些信息,能夠統一地把它們放在各個空閒分區的起始部分。因此這是咱們能夠採用的兩種數據結構——空閒分區表和空閒分區鏈。
那再來看第二個問題,當有不少空閒分區均可以知足需求的時候,到底應該選擇哪一個空閒分區進行分配呢?假如此時有一個進程5它只須要4兆字節的空間,那麼這個空閒分區、這個分區還有這個分區這三個空閒分區均可以知足它這個需求。那咱們應該用哪一個分區進行分配呢?那由這個問題咱們能夠引出動態分區分配算法相關的問題。那所謂的動態分區分配算法,就是要從空閒分區表,或者空閒分區鏈當中,按照某一種規則,選擇出一個合適的分區把它分配給此時請求的這個進程或者說做業。那因爲這個分配算法對系統性能形成的影響是很大的,因此人們對於這個問題進行了不少的研究。那這個問題咱們如今暫時不展開處理,會在下一個小節進行詳細的介紹。
接下來咱們再來看第三個問題,如何進行分區的分配與回收?那假設咱們採用的是空閒分區表的這種數據結構的話,進行分配的時候須要作一些什麼操做呢?那這個地方咱們只以空閒分區表爲例,其實空閒分區鏈的操做也是大同小異的。那假如說此時系統當中有這樣三塊空閒的分區,若是此時有一個進程須要申請四兆字節的內存空間,那假設咱們採用了某一種算法,最後決定從這20MB的空閒分區當中摘出四兆分配給這個進程5,
就像這樣。那麼咱們須要對這個空閒分區表進行必定的處理,那因爲這個空閒分區的大小原本就是比這次申請的這塊內存區域的大小要更大的。因此即便咱們從這個分區當中摘出一部分進行了分配,那麼分區的數量依然是不會改變的。因此咱們只須要在這個分區對應的那個表項當中,修改一下它的分區大小還有起始地址就能夠了。那這是第一種狀況。
再來看第二種狀況。仍是相同的地址,有一個進程5須要4MB字節。那若是說咱們採用了某種分配算法,最後決定把這4MB字節的空閒分區分配給這個進程5,
那麼原本這個空閒分區的大小就和這次申請的這個內存空間大小是相同的,因此若是把這個分區、空閒分區所有分配給這個進程的話,那麼顯然空閒分區的數量會減1,因此咱們須要把這個分區對應的這個表項給刪除。那若是說咱們採用的是空閒分區鏈的話,那咱們就只須要把其中的某一個空閒分區鏈的結點給刪掉,那這是分配的時候可能會遇到的兩種狀況。
接下來咱們再來看進行回收的時候可能會須要作一些什麼樣的處理?假設此時系統內存中的狀況是這樣的。那若是採用「空閒分區表」這種數據結構的話,那這個表應該是由兩個表項分別對應一個10MB的空閒分區和一個4MB的空閒分區。那假設此時進程4已經運行結束了,咱們能夠把進程4佔用的這4MB字節的空間給回收。那麼此時這塊回收的區域的後面,有一個相鄰的空閒分區,也就是10MB的這塊分區,
所以咱們把這塊內存分區回收以後,咱們須要把空閒分區表當中對應的那個表項的分區大小和起始地址也進行一個更新。因此能夠看到,若是兩個空閒分區相鄰的話,那麼咱們須要把這兩個空閒分區進行合二爲一的操做。
再來看第二種狀況。假設此時進程三已經運行結束了,那麼當進程三佔用的這一塊分區被回收以後,在它的前面也有一個相鄰的空閒分區,
因此參照剛纔的那種思路,咱們也須要把這兩塊相鄰的空閒分區進行合二爲一的操做。那這和以前的那種狀況實際上是很相似的。
再看第三種狀況。假設此時進程四已經運行結束,須要把這四兆字節給回收,那麼進程四的前面和後面都會有一個相鄰的空閒分區。因此原本咱們的空閒分區表有三個表項,也就是有三個空閒分區,
可是當進程四的這塊空間被回收以後,須要把這一整塊的空間都進行一個合併。因此原本系統中有三個空閒分區,但若是把進程四回收以後就會合併爲兩個空閒分區。那固然咱們也須要修改相應表項的這些分區大小、起始地址等等這一系列的信息。那這第三種狀況須要把三個相鄰的空閒分區合併爲一個。
再來看第四種狀況。假如回收區的先後都沒有相鄰的空閒分區的話,應該怎麼處理。假設此時進程2已經運行結束,那麼當進程2的這塊內存區間被回收以後,
系統當中就出現了兩塊空閒分區。因此相應的咱們固然也須要增長一個空閒分區表的表項。那經過剛纔的這一系列講解,你們可能會發現,咱們對空閒分區表的這種順序通常來講是採用這種按照起始地址的前後順序來進行排列的。可是這個並不必定,各個表項的排序咱們通常來講須要根據咱們採用哪一種分區分配算法來肯定。好比說有的時候咱們按照分區從大到小的順序排列會比較方便,有的時候咱們按照分區大小從小到大進行排列比較方便。固然也有的時候咱們就像如今這樣按照起始地址的前後順序來進行排列會比較方便。那這個地方會到下一個小節進行進一步的解釋。那到這個地方,咱們就回答了以前提出的三個問題,第一個問題咱們須要用什麼樣的數據結構來記錄內存的使用狀況。通常來講會使用兩種數據結構——空閒分區表或者空閒分區鏈。那第二個問題涉及到動態分區分配算法就會在下一個小節中進行進一步的解釋。第三個問題咱們討論了怎麼對內存的空間進行分配與回收。進行分配與回收的時候須要對這些數據結構進行什麼處理。那特別須要注意的是,在回收的過程當中,咱們有可能會遇到四種狀況。不過本質上咱們能夠用一句話來進行總結,在進行內存分區回收的時候若是說回收了以後發現有一些空閒分區是相鄰的,那麼咱們就須要把這些相鄰的空閒分區所有給合併。
那接下來咱們再來討論一下動態分區分配關於內部碎片和外部碎片的問題。這兒咱們給出了內部碎片和外部碎片的完整的定義,內部碎片是指分配給某個進程的內存區域當中,若是說有些部分沒有用上,那麼這些部分就是所謂的內部碎片。注意是分配給這個進程可是這個進程沒有用上的那些部分。而外部碎片是指內存當中的某些空閒分區因爲過小而難以利用。那由於各個進程須要的都是一整片連續的內存區域,因此若是這些空閒的分區過小的話那麼任何一個空閒分區都不能知足進程的需求,那這種空閒分區就是所謂的外部碎片。
好比說咱們系統當中依次進入了進程一、進程二、進程3它們的大小分別是這樣。而後這個時候內存當中只剩下一片空閒的內部區域,就是4M字節這麼大。那麼此時若是進程2暫時不能運行,
咱們能夠暫時把它換出到外存當中。那因而這塊就有14M字節的空閒區域。
那接下來進程4到達佔用4M字節,
那這一塊就應該是10M字節的大小。以後若是進程1也暫時不能運行,那麼咱們能夠把進程1暫時換出外存。
因而這個地方能夠空出20M字節的連續的空閒區間。
那接下來若是進程2又能夠恢復運行了,它再回到內存當中,它又佔用了其中的14M字節。
因而這一塊就只剩下6M字節。
那接下來若是說進程1也就是20M字節的這個進程又能夠執行了又想回到內存的話,那麼此時會發現內存當中的任何一個區域都已經不能知足進程1的這個需求了。因此這就產生了所謂的外部碎片。這些空閒區間是暫時沒有分配給任何一個進程的,可是因爲它們都過小了太零碎了因此沒辦法知足這種大進程的需求。那像這種狀況下,其實內存當中總共剩餘的內存區間實際上是6+10+4,也就是總共有20M字節。也就是說內存當中空閒區間的總和實際上是能夠知足進程1的需求的。因此在這種狀況下,咱們能夠採用緊湊技術或者是拼湊技術來解決外部碎片的問題。那緊湊技術很簡單,
其實就是把各個進程挪位,
把它們所有攢到一塊兒,
而後挪出一個更大的空閒、連續的空閒區間出來。
這樣的話,這塊空閒區間就能夠知足進程1的需求了。那這個地方你們也能夠停下來回憶一下我們剛纔提到的換入換出技術和中級調度相關的一些概念,這是我們以前講過的內容。那顯然我們以前介紹的三種裝入方式當中,動態重定位的方式實際上是最方便實現這些程序或者說進程在內存當中移動位置這件事情的,因此咱們採用的應該是動態重定位的方式。另外,緊湊以後咱們須要把各個進程的起始地址給修改掉。那進程的起始地址這個信息通常來講是存放在進程對應的PCB當中。當進程要上CPU運行以前,會把進程的起始地址那個信息放到重定位寄存器裏,或者叫基址寄存器裏。那你們對這些概念還有沒有印象呢?
那這個小節咱們介紹了三種連續分配管理的分配方式。連續分配的特色就是爲用戶進程分配的必須是一個連續的內存空間。那麼咱們分別介紹了單一連續分配、固定分區分配和動態分區分配這三種分配方式。
那以前我們留下了一個問題,單一連續分配和固定分區分配都不會產生外部碎片。那因爲採用這兩種分配方式的狀況下,不會出現那種暫時沒有被分配出去可是又因爲這個空閒區間過小而沒有辦法利用的這種狀況,因此這兩種分配方式是不會產生外部碎片的。那對因而否有外部碎片仍是內部碎片這個知識點常常在選擇題當中進行考查,你們千萬不能死記硬背,必定要在理解了各類分配方式的規則的這種狀況下,可以本身分析到底有沒有外部碎片,有沒有內部碎片。另外,動態分區分配方式當中對外部碎片的處理「緊湊」技術也是曾經做爲選擇題的選項進行考查過,這個地方也須要有一些印象。那在回收內存分區的時候咱們可能會遇到的這四種狀況也是曾經在真題當中考查過因此這個點也須要注意。不過只須要抓住一個它的本質,相鄰的空閒區間是須要合併的,咱們只要知道這一點就能夠了。另外呢咱們也須要對空閒分區表和空閒分區鏈這兩種數據結構相關的概念還有它們的原理也要有一個印象。
在這個小節中咱們會學習動態分區分配算法相關的知識點,
那這是咱們上小節遺留下來的問題。在動態分區分配方式當中,若是有不少個空閒分區都可以知足進程的需求,那麼咱們應該選擇哪一個分區進行分配呢?這是動態分區分配算法須要解決的問題。那考試當中,要求咱們掌握的有這樣四種算法,首次適應、最佳適應、最壞適應、鄰近適應這四種,咱們會按從上至下的順序依次講解。
首先來看首次適應算法。這種算法的思想很簡單,就是每次從低地址部分開始查找,找到第一個可以知足大小的空閒分區。因此按照這種思想,咱們能夠把空閒分區按照地址遞增的次序進行排列,而每一次分配內存的時候咱們就能夠順序地查找空閒分區鏈或者空閒分區表,找到第一個大小可以知足要求的空閒分區進行分配。那這個地方提到了空閒分區鏈和空閒分區表,這是兩種經常使用於表示動態分區分配算法當中內存分配狀況的數據結構。那若是咱們此時系統當中內存的使用狀況是這樣的,那採用空閒分區表的話,咱們就能夠獲得一個這樣的表。每個空閒分區塊都會對應一個空閒分區表的表項,那這些空閒分區塊是按地址從低到高的順序依次進行排列的。那若是採用空閒分區鏈的話,其實也相似,也是按照地址從低到高的順序把這些空閒分區塊依次地連接起來。那這個算法對這兩種數據結構的操做實際上是很相似的,無非就是從頭至尾依次檢索,而後找到第一個可以知足要求的分區。因此這個地方咱們就以空閒分區鏈爲例子。空閒分區表的操做其實也相似。
那按照首次適應算法的規則,那若是說此時有一個進程要求15M字節的空閒分區,那麼咱們會從空閒分區鏈的鏈頭開始,依次查找找到第一個可以知足大小的分區。那通過檢查發現第一個20M字節的這個空閒分區,已經能夠知足這個要求。
因此咱們會從20M字節的空閒分區當中,摘出15M分配給進程5,因而這個地方會剩餘5M字節的空閒分區。
那相應的,咱們須要把空閒分區鏈的對應結點的這些數據包括分區的大小還有分區的起始地址等等這一系列的數據都進行修改。
那麼此時若是還有一個進程到來,它須要8M字節的內存空間。那咱們依然仍是會從空閒分區鏈的鏈頭開始依次檢索,
那通過一系列的檢索會發現,
第二個空閒分區的大小是足夠的,因而咱們會從第二個空閒分區10M字節當中,
摘出8M分配給進程6。那這個地方會剩餘2M字節的空閒分區。因此咱們和剛纔同樣,也須要修改空閒分區鏈當中相應的分區大小還有分區的起始地址這一系列的信息。那這個地方就再也不展開贅述。因此這就是首次適應算法的一個規則,咱們按照空閒分區以地址遞增的次序進行排列,而且每一次分配內存的時候咱們都會從鏈頭開始依次日後尋找,找到第一個可以知足要求的空閒分區進行分配。
接下來來看最佳適應算法,這種算法的思想其實也很好理解。因爲動態分區分配算法是一種連續分配的方式,那既然是連續分配就意味着咱們系統爲各個進程分配的空間必須是連續的一整片區域。因此咱們爲了保證大進程到來的時候有大片的連續空間能夠供大進程使用,因此咱們能夠嘗試儘量多地留下大片的空閒區間。那也就是說,咱們能夠優先地使用更小的那些空閒區間。因此最佳適應算法會把空閒分區按照容量遞增的次序依次連接。那每次分配內存的時候會從頭開始依次查找空閒分區鏈或者空閒分區表,找到大小可以知足要求的第一個空閒分區。那因爲這個空閒分區是按容量遞增的次序排序排列的,因此咱們找到的第一個可以知足的空閒分區,必定是可以知足可是大小又最小的空閒分區。那這樣的話咱們就能夠儘量多地留下大片的空閒分區了。那這個地方仍是同樣,咱們就以空閒分區鏈做爲例子,空閒分區表的操做其實也相似。若是說系統當中的內存使用狀況是這個樣子,那麼咱們按照空閒分區塊的大小從小到大也就是遞增的次序連接的話,那應該是四、十、20這樣的順序連接。若是說此時有一個新的進程到達,那這個進程須要9M字節的內存空間的話,按照最佳適應算法的規則,咱們會從鏈頭開始依次日後檢索,找到第一個可以知足要求的空閒分區也就是10M字節。
因而咱們會從這10M字節當中摘出其中的9M分配給這個進程,那這個地方就要只剩下1M字節的大小。可是因爲最佳適應算法要求咱們空閒分區必須按照容量遞增的次序進行連接,因此這個地方變成了1M以後咱們就須要對這個整個空閒分區鏈進行從新排序,
那最後會更新爲這個樣子,也就是把更小的這個空閒分區挪到這個鏈的鏈頭的位置。那以後若是還有另一個進程須要到達它須要3M字節的空閒分區的話,那一樣的咱們也須要從鏈頭開始依次查找,因而發現這個分區是能夠知足的。
那麼第二個進程3M字節咱們就能夠從4M當中摘出3M給它分配,那這個地方也會變成只有1M字節的空閒分區。那咱們以後就須要把這個結點對應的那些空閒分區大小、空閒分區的起始地址這些信息進行更新。那這個地方進行更新以後,整個空閒分區鏈依然是按照容量遞增的次序進行連接的,因此咱們不須要像剛纔那樣進行從新排列。那這個地方就再也不展開細聊了。那從剛纔的這個例子當中咱們會發現最佳適應算法有一個很明顯的缺點,因爲咱們每一次選擇的都是最小的可以知足要求的空閒分區進行分配,因此咱們會留下愈來愈多很小的、很難以利用的內存塊。好比說這個地方有1M字節這個地方又有1M字節,那假如咱們全部的進程都是兩M字節以上,那這兩個地方的碎片就是咱們難以利用的,因此採用這種算法的話是會產生不少不少的外部碎片的。那這是最佳適應算法的一個缺點。
那因而爲了解決這個問題,人們又提出了最壞適應算法。它的算法思想和最佳適應恰好相反,因爲最佳適應算法留下了太多難以利用的小碎片,因此咱們能夠考慮在每次分配的時候優先使用最大的那些連續空閒區,這樣的話咱們進行分配以後,剩餘的那些空閒區就不會過小,因此若是採用最壞適應算法的話,咱們能夠把空閒分區按照容量遞減的次序進行排列。而每一次分配內存的時候就順序地查找空閒分區鏈,找出大小可以知足要求的第一個空閒分區。那因爲這個地方空閒分區是按容量遞減的次序進行排列的,因此鏈頭第一個位置的那個空閒分區確定是可以知足要求的。若是第一個都知足不了要求,那剩下的後面的那些空閒分區,確定都比第一個空閒分區更小,那別的那些空閒分區確定也不會知足。那仍是來看一個具體的例子。假設此時系統當中內存使用狀況是這樣。那咱們採用空閒分區表和空閒分區鏈能夠表示出此時的這些空閒分區的狀況。那按照最壞適應算法的規則,咱們須要按照容量遞減的次序依次把這些空閒分區進行排列,也就是20、十、4。那此時假若有個進程它須要3M大小的內存空間,那因爲鏈頭的第一個空閒分區就能夠知足,因此咱們會從其中摘出3M進行分配,
那這個地方就變成了還剩17M。那接下來還有一個進程也到達,它須要9M內存,
那一樣的咱們也是從這鏈頭的這17M當中摘出其中的9M分配給進程6,因而進行數據的更新。那更新了以後咱們會發現,
此時這個空閒分區鏈,已經不是按照容量遞減的次序進行排列的,因此咱們須要把這個空閒分區鏈進行從新排序,也就是變成這個樣子,十、八、4,依然保持按容量遞減的次序進行連接,那若是有下一個進程到達的話,那咱們第一個須要檢查的就是10這個空閒分區。那從這個例子當中能夠看到,最壞適應算法確實解決了剛纔最佳適應算法留下了太多難以利用的碎片的問題。可是最壞適應算法又形成了一個新的問題,因爲咱們每次都是選擇最大的分區進行分配,因此這就會致使咱們的那些大分區會不斷不斷地被分割爲一個一個小分區。那若是以後有一個大進程到達的話就沒有連續的大分區可用了。好比說此時來了一個20M的大進程,那這個大進程就無處安放。因此這是最壞適應算法的一個明顯的缺點。
那接下來咱們再來看第四種,鄰近適應算法,這種算法的思想實際上是爲了解決首次適應算法當中存在的一個問題。首次適應算法每一次都會從鏈頭開始查找,這有可能會致使低地址部分會出現不少很小的難以利用的空閒分區,也就是碎片。可是因爲首次適應算法又必須按照地址從低到高的次序來排列這些空閒分區,因此咱們在每次分配查找的時候都須要通過低地址部分那些很小的分區,這樣的話就有可能會增長查找的一個開銷。因此若是咱們可以從每次都從上一次查找結束的位置開始日後檢索的話,是否是就能夠夠解決以前所說的這個問題了呢?因此鄰近適應算法和首次適應算法很像,它也是把空閒分區按照地址遞增的順序進行排列,固然咱們能夠把它排成一個循環鏈表,這樣的話比較方便咱們檢索。那每一次分配內存的時候都是從上次結束的位置開始日後查找,找到大小可以知足的第一個空閒分區。那假如說此時系統當中的內存使用狀況是這樣,那咱們能夠把這些空閒分區按照地址遞增的次序依次進行排列,排成一個循環鏈表。那剛開始若是說有一個進程到達,它須要5M字節的內存空間,剛開始咱們會從鏈頭的位置開始查找,
那第一個不知足,
那第二個6M是知足的。
因而咱們會從6M當中摘出5M分配給它,
那這個地方就還剩餘1M字節。因而咱們須要更新這個分區鏈當中對應的結點,包括分區的大小還有分區的起始地址。可是有沒有發現,採用鄰近適應算法還有首次適應算法,咱們只須要按照地址依次遞增的次序來進行排列,因此即便這個地方內存分區的大小發生了一個比較大的變化,可是咱們依然不須要對整個鏈表進行從新排列,因此這也是鄰近適應算法還有首次適應算法比最佳適應算法和最壞適應算法更好的一個地方。算法的開銷會比較小,不須要咱們再花額外的時間對這個鏈表進行從新排列。
那假如此時有一個新的進程到達,它須要5M字節的空間。那按照鄰近適應算法的規則,咱們只須要從上一次查找到的這個位置依次再日後查找就能夠了,
因此這個不知足,
那咱們看下一個,10M是知足的,
因而會從10M當中摘出5M進行分配,
而後更新相應的這些數據結構。那這個地方你們有沒有發現,若是此時咱們採用的是首次適應算法的話,若是此時須要分配5M的內存空間,那麼咱們依然會從鏈首的位置開始日後查找,因此第一個4M不知足,第二個1M不知足,第三個10M才能知足,那就會有三次查找。那若是說咱們採用的是鄰近適應算法的話,咱們只須要從這個位置開始日後查找,也就是查兩次就能夠了,因此這是鄰近適應算法比首次適應算法更優秀的一個地方。首次適應算法會致使低地址部分留下一些比較小的碎片,可是咱們每一次開始檢索都須要從低地址部分的這些小碎片開始日後檢索,因此這就會致使首次適應算法在查找的時候可能會多花一些時間,不過這並不意味着鄰近適應算法就比首次適應算法更優秀不少。
其實鄰近適應算法又形成了一個新的問題。在首次適應算法當中,咱們每次都須要從低地址部分的那些小分區開始依次日後檢索,可是這種規則也決定了,若是說在低地址部分有更小的分區能夠知足咱們的需求的時候,咱們就會優先地使用低地址部分的那些小分區,這樣的話就意味着高地址部分的那些大分區就有更大的可能性被保留下來。因此其實首次適應算法當中也隱含了一點最佳適應算法的優勢。那若是咱們採用的是鄰近適應算法的話,因爲咱們每一次都是從上一次檢查的位置開始日後檢查,因此咱們不管是低地址部分仍是高地址部分的空閒分區,其實都是有相同的機率被使用到的,因此這就致使了和首次適應算法相比,高地址部分的那些大分區,更有可能被使用被劃分紅小分區,這樣的話高地址部分的那些大分區也頗有可能被咱們用完,那以後若是有大進程到達的話就沒有那種連續的空閒分區能夠進行分配了。因此其實鄰近適應算法的這種策略也隱含了一點最大適應算法的缺點。因此綜合來看,其實剛纔介紹的這四種適應算法當中,反而首次適應算法的效果是最好的。
好的那麼這個小節咱們介紹了四種動態分區分配算法,分別是首次適應、最佳適應、最壞適應和鄰近適應。那這個小節的內容很容易做爲選擇題進行考查,甚至有可能做爲大題進行考查。其實咱們只須要理解各個算法的算法核心思想就能夠分析出這些算法的這些空閒分區應該怎麼排列,它們的優勢是什麼,缺點是什麼。那這幾個算法當中,比較不容易理解的實際上是鄰近適應算法的優勢和缺點,可是剛纔我們也進行了詳細的分析這兒就再也不重複了。那這個地方你們會發現,各個算法提到的算法開銷的大小問題,那這個地方的算法開銷指的是爲了保證咱們的空閒分區是按照咱們規定的這種次序排列的,在最佳適應和最壞適應這兩種算法當中,咱們可能須要常常對整個空閒分區鏈進行從新排序,因此這就致使了算法開銷更大的問題。而首次適應和鄰近適應咱們並不須要對整個空閒分區鏈進行順序地檢查和排序,因此這兩種算法的開銷是要更小的。那麼這些算法你們還須要經過課後習題的動手實踐來進行進一步的鞏固。
在這個小節中咱們會學習一個很重要的高頻考點,同時也是這門課的難點,叫作分頁存儲管理。
那在以前的小節中咱們學習了幾種連續分配存儲管理方式,所謂的連續分配就是指,操做系統給用戶進程分配的是一片連續的內存區域,而非連續分配就是指,它給用戶進程分配的能夠是一些離散的、不連續的內存區域。那這個小節咱們會首先學習第一種,非連續的分配管理方式,叫作基本分頁存儲管理。
那首先來認識一下什麼叫分頁存儲。那若是一個系統支持分頁存儲的話,那麼系統會把內存分爲一個一個大小相等的區域,好比說一個區域的大小是4KB,那這樣的一個區域稱爲一個頁框或者叫一個頁幀,固然它還有別的一些名詞,不一樣的教材或者不一樣的題目上你們可能會看到各類各樣的名詞出現,不過須要知道它們指的都是頁框。那系統會給每一個頁框一個編號,而且這個編號是從零開始的,這個編號就叫作頁框號,或者叫頁幀號、內存塊號、物理塊號、物理頁號。那接下來咱們思考一下,內存裏邊它存放的其實無非就是各個進程的數據對吧,包括進程的代碼啊、進程的指令啊等等這些數據,因此爲了把各個進程的這些數據把它放到各個頁框當中,所以操做系統也會把各個進程的這些邏輯地址空間把它分爲與這個頁框大小相等的一個一個的部分。好比說咱們這個地方舉的例子進程A,它的邏輯地址空間是0-16K-1,也就是16K,因此這個進程的大小應該是16KB這麼多。把它分爲與頁框大小相等的一個一個部分,所以每一個部分就是4KB這麼多。而且系統也會給進程的各個頁進行一個編號,這個編號就稱做爲頁號或者叫頁面號。
那進程的各個頁會被放到內存的各個頁框當中,因此進程的頁面和內存的頁框是有一一對應、一一映射的關係的。那這個地方建議你們暫停,好好地來區分一下這幾個很容易混淆的概念,特別是頁、頁面、頁框和頁幀。這四個術語在剛開始學習的時候,很容易認爲它們指的是同一個東西。但其實不是,頁框和頁幀它指的是內存在物理上被劃分爲的這樣一個一個的部分,這個叫頁框。而頁和頁面指的是進程在邏輯上被劃分爲的一個一個的部分。那除了頁框頁幀以外,有的教材當中也會把頁框稱爲內存塊、物理塊或者叫物理頁面,而且在咱們的課後習題當中,這些名詞都有可能出現,因此這個地方建議你們特別注意一下這些很容易混淆的概念。那到這兒咱們就初步瞭解了什麼叫分頁存儲。接下來要思考的問題是這樣的,剛纔咱們不是說進程的頁面和內存的這個頁框它有一一對應的關係嗎?那操做系統是怎麼記錄這種一一對應關係的呢?
這就涉及到一個很重要的數據結構,叫作頁表。操做系統會給每個進程都創建一張頁表。而且這個頁表通常是存放在內存的控制塊當中的,也就是PCB當中。那剛纔咱們說過,進程的邏輯地址空間會被分爲一個一個的頁面,那每個頁面就會對應頁表當中的一個頁表項。所謂的頁表項,你們能夠理解爲就是這個頁表當中的一行。那頁表項當中包含了頁號和塊號這樣的兩個數據,因此這樣的一個頁表就能夠記錄下來這個進程的各個頁面和實際存放的內存塊之間的映射關係。注意內存塊其實就是頁框,只不過內存塊這個術語可能更不容易讓人混淆一些,因此咱們在接下來的講解當中更多地會使用的是內存塊這樣的表述方式。不過你們本身答題的時候,建議使用頁框這個術語。由於去看英文書的話,其實這個術語它的英文叫作page frame,因此大部分的教材其實習慣翻譯成頁框。所以,建議你們答題的時候使用的是頁框這個術語。好的,那麼再回到頁表這個數據結構,從剛纔的分析當中咱們知道,頁表它由這樣一個一個的頁表項組成。那接下來咱們要思考的問題是這樣的,首先,這些頁表項是存在內存裏的,那每個頁表項須要佔幾個字節的空間呢?第二個問題是操做系統要怎麼利用頁表來實現邏輯地址到物理地址的轉換。
那首先咱們來分析第一個問題,直接結合一個例子來理解。不過呢計算機分配存儲空間它是以字節爲單位分配,而不是以比特爲單位分配。
1GB=2^10MB=2^20KB=2^30B 4GB=2^32B 1KB=2^10B 4KB=2^12B 20bit<3B
那接下來咱們再來看一下這個頁號又須要佔多少個字節呢?直接告訴你們答案。頁號是不須要佔存儲空間的。由於各個頁表項在內存中連續存放,因此頁號能夠是隱含的。什麼意思呢?那剛纔咱們得出的結果是一個塊號它至少須要佔用三個字節,而且這些頁表項在內存當中都是連續存放的。那若是在內存中只存儲塊號而沒有存儲頁號的話,那咱們又怎麼找到頁號爲i的這個頁面對應的頁表項呢?其實很簡單,只要咱們知道了這個頁表它在內存當中存放的起始地址X,咱們就能夠用X+3*I就得出這個i號頁表項它的存放地址了。那學過數據結構的線性表,相信這個地方並不難理解。其實就至關因而一個數組,對於普通的數組而言,數組的下標咱們也不須要花存儲空間來存放對吧。所以咱們得出結論,頁表當中的這個頁號能夠是隱含的,它並不佔用存儲空間。那結合以前的結論咱們知道,一個頁表項它在邏輯上實際上是包含了頁號和塊號這樣的兩個信息,可是在物理上它其實只須要存放塊號的這個信息,只有塊號須要佔用存儲空間。那若是這個進程它的頁號是0-n號,也就是說它總共有n+1個頁面的話,那麼存儲這個進程的頁表就至少須要3*(n+1)這麼多個字節。那咱們經過頁表能夠知道各個頁面它存放在哪一個內存塊當中。
可是須要注意、須要強調的是,這個地方它記錄的只是內存的塊號,而不是具體的內存塊的起始地址。若是咱們要計算一個內存塊的起始地址的話,咱們須要用這個塊號再乘之內存塊的大小。這個地方你們須要特別地注意體會一下,否則作題的時候很容易出錯。好的那麼到這兒咱們就弄清楚了第一個問題。
接下來要探索的是第二個問題,如何實現地址的轉換,也就是邏輯地址轉換到物理地址。那咱們先來回憶一下,咱們以前在講連續存放那種方式的時候,操做系統是怎麼實現這種地址的轉換的呢?若是一個進程它在內存當中連續存放,那麼咱們只須要知道這個進程它的起始地址,而後把接下來要訪問的那個邏輯地址和起始地址相加就能夠獲得它最終的物理地址了,那這是連續存放的時候。那這個邏輯地址咱們能夠把它理解爲是一種偏移量,也就是說相對於它的起始地址而言日後偏移了多少。
那若是採用分頁存儲的話,那這個地址轉換要怎麼進行呢?
這個進程會被放到內存的各個位置當中,不過有這樣的一個特色,雖然進程的各個頁面在內存中是離散的存放的,可是各個頁面的內部它都是連續的。注意體會這個特色。那基於這個特色,咱們來看一下,若是要訪問邏輯地址A,應該怎麼來進行呢?首先咱們能夠肯定這個邏輯地址A,它應該對應的是進程的哪一個頁面。也就是說要肯定這個邏輯地址A它所對應的頁號。接下來操做系統就能夠用這個頁號去查詢頁表,而後找到這個頁面它存放在內存當中的什麼位置。那第三步咱們要肯定的是,邏輯地址A它相對於這個頁面的起始位置而言的「偏移量」是多少。由於各個頁面內部都是連續存放的嘛,因此咱們只須要把這個邏輯地址A它所對應的頁面在內存當中的起始地址,再加上這個邏輯地址的頁內偏移量W,就能夠獲得這個邏輯地址A所對應的物理地址了。那這個就是實現地址變換的一個基本的思路。那在以前的講解當中咱們瞭解了怎麼利用頁表來找到一個頁面在內存當中的起始地址。
那接下來咱們要探討的就是怎麼肯定邏輯地址所對應的頁號和頁內偏移量。
仍是結合一個例子來理解。
那在這個例子當中,一個頁面的大小是50個字節。那熟悉二進制乘法或者無符號左移、無符號右移這些操做的同窗,可能很容易理解這個原理。但對於跨考的同窗來講也許會以爲它比較神奇但不知道爲何會這樣。那若是想要了解呈現這種規律背後的原理的話,建議能夠去看一下無符號左移、無符號右移和二進制的乘法、二進制的除法之間的一個聯繫。好的扯遠了,回到咱們的這個主題上來。
那除此以外它還有另一個優勢。咱們剛纔講頁表的時候強調過一個問題,頁表當中記錄的是內存塊號而不是內存塊的起始地址,因此若是咱們要計算一個內存塊的起始地址的話,須要進行一個這樣的乘法運算。可是若是內存塊的大小恰好是2的整數冪,計算起來就沒有那麼麻煩。咱們假設1號頁面它存放的內存塊號是9,若是用二進制表示的話9這個數就應該是1001。那這麼完美的特性其實就是由於頁面大小、內存塊的大小恰好是2的整數次冪,因此在地址轉換的過程中,咱們只要查到頁表當中存放的這個內存塊號,再把這個內存塊號和邏輯地址的頁內偏移量進行一個拼接其實就能夠獲得最終的物理地址了。若是不是2的整數次冪的話,頁面在內存中的起始地址必須用這樣的乘法的方式來進行,這也會致使硬件的效率下降。
那通過剛纔的這兩個例子咱們能夠看到,頁面大小是2的整數次冪有這樣的兩個好處。這個地方你們再結合文字好好體會一下就能夠了,就再也不重複。
那若是頁面大小是2的整數次冪的話,咱們能夠把邏輯地址把它分爲這樣的兩個部分,分別是頁號和頁內偏移量。總之呢,只要知道頁內偏移量的位數就能夠推出頁面大小,一樣的知道頁面大小也能夠反推出頁內偏移量應該佔多少位,從而就能夠肯定邏輯地址的結構,這一點也是考題當中很是很是高頻的一個考點,你們在作題的時候會常常遇到。固然,有的題目當中它的頁面大小有可能不是2的整數次冪,那對於這種題目來講咱們要計算頁號和頁內偏移量,仍是隻能用最原始的那種算法,用除法來獲得頁號,用取餘獲得頁內偏移量。
系統會把進程分頁,會把各個頁面離散地放到各個內存塊當中,或者說放到各個頁框當中。那因爲各個頁面會依次放到各個內存塊當中,因此須要記錄這種頁面和內存塊之間的映射關係,所以須要有一個很重要的數據結構叫作頁表。頁表由一個一個的頁表項組成,而且頁表項在內存中是連續存放的,各個頁表項大小相等。注意,頁號是隱含的,不須要佔用存儲空間。那咱們只須要知道頁表在內存當中存放的起始地址而且知道頁號和頁表項的大小就能夠算出i號頁表項存放在什麼位置了。那最後咱們還介紹了分頁存儲的邏輯地址結構,能夠分爲頁號和頁內偏移量這樣兩個部分。若是頁面的大小恰好是2的整數次冪,那麼硬件在拆分邏輯地址,在進行物理地址的計算的時候,都會更快。因此通常來講,頁面大小都是2的整數次冪。固然,這個小節中咱們還介紹了在分頁存儲這種管理方式當中,怎麼實現邏輯地址到物理地址的轉換,具體的轉換過程你們如今只須要有個大致的印象就能夠。下個小節當中咱們還會結合一些硬件的細節,再進一步地闡述地址轉換的過程。
那這個小節的內容也屬於基本分頁存儲管理。其實所謂的基本地址變換機構,就是在基本分頁存儲管理當中用於實現邏輯地址到物理地址轉換的一組硬件機構。那咱們在學習這個小節的過程中,須要重點掌握這些變換機構的工做原理還有流程,這個小節的內容十分重要,既有可能做爲選擇題也有可能結合大題進行考查。
那經過上個小節的講解咱們知道,在分頁存儲管理當中,若是要把邏輯地址轉換成物理地址的話,總共須要作四件事,第一,要知道邏輯地址對應的頁號。第二,還須要知道邏輯地址對應的頁內偏移量,第三咱們須要知道邏輯地址對應的頁面在內存當中存放的位置究竟是多少。第四,咱們再根據這個頁面在內存當中的起始位置和頁內偏移量就能夠獲得最終的物理地址了。那爲了實現這個地址轉換的功能,系統當中會設置一個頁表寄存器,用來存放頁表在內存當中的起始地址還有頁表的長度這兩個信息。在進程沒有上處理機運行的時候,頁表的起始地址還有頁表長度這兩個信息是放在進程控制塊裏的。只有當進程被調度,須要上處理機的時候,操做系統內核纔會把這兩個數據放到頁表寄存器當中。那咱們接下來用一個動畫的形式看一下從邏輯地址到物理地址的轉換應該是什麼樣一個過程。
咱們知道操做系統會把內存分爲系統區和用戶區,那在系統區當中會存放着一些操做系統對整個計算機軟硬件進行管理的一些相關的數據結構,包括進程控制塊PCB也是存放在系統區當中的。那若是說一個進程被調度,它須要上處理機運行的話,進程切換相關的那些內核程序就會把這個進程的運行環境給恢復,那這些進程運行環境相關的信息原本是保存在PCB當中的。那以後這個內核程序會把這些信息把它放到相應的一系列寄存器當中,包括頁表寄存器。頁表寄存器當中存放着這個進程的頁表的起始地址還有頁表的長度,另外呢像程序計數器PC也是須要恢復的。程序計數器是指向這個進程下一條須要執行的指令的邏輯地址,邏輯地址A。那麼接下來咱們來看一下怎麼把這個邏輯地址轉換成實際的物理地址,也就是說CPU怎麼在內存當中找到接下來要執行的這條指令。
那從上個小節的講解中咱們知道,採用分頁存儲管理方式的這種系統當中,邏輯地址結構確定是固定不變的。在一個邏輯地址當中,頁號有多少位,頁內偏移量有多少位這些操做系統都是知道的。因此只要知道了邏輯地址A,那麼就能夠很快地切分出頁號和頁內偏移量這樣的兩個部分。那接下來會對頁號的合法性進行一個檢查。一個進程的頁表長度M指的是這個進程的頁表當中有M個頁表項,也就意味着這個進程的頁面總共有M頁。因此若是此時想要訪問的頁號已經超出了這個進程的頁面數量的話,那麼就會認爲此時想要訪問的這個邏輯地址是非法的,這樣就須要拋出一個越界中斷。那若是說這個頁號是合法的,
那麼接下來會用這個頁號和頁表始址來進行計算,找到這個頁號對應的頁表項究竟是多少。那經過上個小節的講解咱們知道,頁表當中的每個頁表項的長度實際上是相同的,因此其實只要咱們知道了頁號還有頁表起始地址,再知道咱們每個頁表項的長度,咱們就能夠算出咱們想要訪問的頁號對應的頁表項所存放的位置。那既然知道了它存放的內存塊號,咱們就能夠再用內存塊號結合內存偏移量獲得最終的物理地址,而後就能夠順利地訪問邏輯地址A所對應的那個內存單元了。因此整個過程作了這樣幾件事,第一是根據邏輯地址算出了頁號和頁內偏移量。第二須要檢查這個頁號是否越界,是否合法。第三,若是這個頁號是合法的,那麼咱們會根據頁號還有頁表始址來計算出這個頁號對應的頁表項應該是在什麼地方,而後找到相應的頁表項。第四,在咱們得知了這個頁面存放的內存塊號以後,咱們就能夠用內存塊號還有頁內偏移量來計算出最終的物理地址。而後最後再對這個物理地址進行訪問。那在考試當中,常常會給出一個邏輯地址還有頁表而後讓咱們計算對應的物理地址,因此你們須要對上面所說的這些過程都很是熟悉。
那接下來咱們再用文字的方式再給出一個描述,雖說這個內容比較重複,可是也是由於這個部分的內容極其重要,因此想多讓你們過幾遍。特別是頁表長度還有頁表項長度這兩個概念必定要着重注意一下。
那這個地方的驗證這兒就暫時不展開,你們下去動手嘗試一下。
頁號2對應的內存塊號b=8,也就是2號頁面應該存在內存塊號爲8的地方。按字節尋址就意味着這個系統當中每一個地址對應的是一個字節。邏輯地址結構中,頁內偏移量佔10位,這個信息很重要,頁內偏移量的位數其實就直接決定了一個頁面的大小是多少。那麼偏移量佔10位的話,那麼就說明一個頁面的大小是2的10次方個字節,也就是1KB。因此這種說法和上面這種說法實際上是等價的,在作題的時候必定要注意這個頁內偏移量還有頁面大小之間的這種對應關係。那進行地址的轉換第一步咱們應該根據這個條件算出頁號和頁內偏移量。因爲題目當中給出的是這種十進制表示的邏輯地址,因此咱們用除法還有取餘操做這樣的方式來計算會更方便一些。而根據題目當中給出的條件,頁號2對應的內存塊號b=8,也就說明,頁號爲2的頁表項是存在的,所以頁號2確定沒有越界。而且查詢頁表以後已經知道這個頁面應該是存放在內存塊號爲8的地方。那第三步,咱們知道了內存塊號、知道了頁號、頁內偏移量咱們就能夠計算物理地址。物理地址=內存塊號*每一個頁面的大小(或者說每個內存塊的大小)+頁內偏移量。其實在分頁存儲管理(頁式存儲管理)的系統當中,只要咱們肯定了每一個頁面的大小是多少,那麼邏輯地址的結構確定就已經肯定了。因此頁式管理當中的地址是一維的,咱們並不須要告訴系統除了邏輯地址之外的別的信息,不須要顯式地告訴它頁內偏移量佔多少,頁號佔多少。由於這些信息都是肯定的,因此在頁式管理當中,咱們想要讓系統把邏輯地址轉換成物理地址,只須要告訴系統一個信息,也就是邏輯地址的值,不須要再告訴系統別的任何信息。那由於只須要告訴它一個信息,所以這個地址是一維的。那這就是咱們手動地模擬基本地址變換機構轉換地址的一個過程。不少初學者會忽略的是,對頁號進行越界檢查的這一步操做,因此這個地方須要留個心眼。
可是1365個頁表項並不能佔滿整個頁框。這個頁框還會剩餘一個字節的頁內碎片。那因爲這個地方只剩一個字節的空閒區域了,因此下一個頁表項只能存放在下一個頁框當中,它不能跨頁框地存儲。+1就是爲了消除這一字節剩餘的偏差。因此說能夠發現,若是說咱們的這些頁表項並不能裝滿整個頁框的話,那在查找頁表項的時候實際上是會形成一些麻煩的。因此爲了解決這個問題,咱們能夠把每一個頁表項的長度再拓展一下,把它拓展到四個字節。這樣的話咱們就能夠保證每一個頁框恰好能夠存放整數個1024個頁表項,而且不會有任何的這種頁內碎片,
就像這個樣子。這樣的話,咱們要查詢1024號的頁表項,咱們就不須要像上面這麼麻煩了。由於這個頁框當中不會有任何的頁內碎片,因此在理論上來講,頁表項的長度最短三個字節就能夠表示全部的這些內存塊號的範圍。但實際的應用當中,爲了方便頁表的查詢,常常會讓一個頁表項佔更多的字節,使得每一個頁面剛好能夠裝得下整數個頁表項。不過即便這個頁表項長度是3個字節,其實也沒問題,只不過在查詢頁表的時候可能會須要作一些更麻煩的處理。若是在題目當中要咱們算頁表項的長度最小應該是多少,那咱們按照3字節這樣的思路來處理就能夠了。四個字節這樣的處理只是實際應用當中爲了方便而採用的一種策略。那通過剛纔的這個例子你們有沒有發現,一個進程若是它的頁表太大,也就是頁表項太多的話,那麼這個進程的頁表通常來講裝到內存裏也是會盡量地讓它裝在連續的一些內存塊當中。由於這樣的話咱們均可以用一個統一的計算方式就能夠獲得咱們想要獲得的那個頁表項所存儲的位置。
好的,那麼在這個小節當中咱們學習瞭如何使用基本地址變換機構這一系列的硬件來實現地址轉換的一個過程。那基本地址變換機構當中,最重要的硬件就是頁表寄存器。你們須要知道頁表寄存器有什麼做用。那這個小節中,最重要的是要掌握地址變換的整個過程。咱們要知道計算機是怎麼一步一步實現這些地址變換的,而且還要能用手動的方式、手算的方式來模擬出整個地址變換的過程。那這一部分是大題和小題的極高頻的出題點。那除了地址變換過程以外,咱們在講解的過程當中,也補充了一些小的細節。好比說頁內偏移量的位數和頁面大小之間是有一個對應關係的。那若是說題目當中給出了頁內偏移量的位數,你們須要可以推出頁面的大小。一樣的,若是告知咱們頁面大小,也要可以推出頁內偏移量的位數。若是知道地址、邏輯地址的總位數的話,咱們還要可以寫出整個邏輯地址的地址結構。那這個小知識點在計算題當中是很容易用到的。那除了這個以外,頁式管理的地址是一維的。這一點也常常在選擇題當中進行考查。那你們要理解什麼叫一維,所謂的一維就是說,咱們要讓CPU幫咱們找到某一個邏輯地址對應的物理地址的話,咱們只須要告訴CPU一個信息,也就是邏輯地址的值,並不須要再告訴它其餘的任何信息,因此這是一維的含義。那另外的兩個小細節只是爲了可以讓你們更充分地瞭解這種頁式管理的這種機制才補充的,固然考試當中通常來講不會考查。那除了這些內容以外,咱們還須要注意一個很重要的知識點。在CPU獲得一個想要訪問的邏輯地址以後,一直到實際訪問的這個邏輯地址對應的內存單元的整個過程中,總共須要進行兩次訪問內存的操做。第一次訪問內存是在查詢頁表的時候進行的,第二次訪問內存是在實際訪問目標內存單元的時候進行的。那在下個小節當中咱們會探討一種新的地址變換機構,是否能用一種別的地址變換機構來減小訪問內存的次數,從而加快整個地址變換還有訪問的過程呢?那這是下個小節想要探討的問題。
在這個小節中咱們會學習具備快表的地址變換機構。
那上個小節中咱們學了基本地址變換機構,還有邏輯地址到物理地址轉換的一個過程。那在基本地址變換機構的基礎上,若是引入了快表的話,就可讓這個地址變換的過程更快,因此這個小節中咱們首先會介紹什麼是快表,而且會介紹引入了快表以後,地址變換的過程有什麼區別。最後咱們會解釋爲何引入快表以後,可讓計算機的總體效率、總體性能都獲得很高的提高。
注意TLB它不是內存,它是一種高速緩存。那快表中存放的是最近咱們訪問過的一些頁表項的副本,這樣的設計可讓地址變換速度更快。頁表實際上是存放在內存當中的,在引入了快表以後,咱們能夠把存放在內存中的頁表稱爲慢表。由於訪問內存中的這個頁表的速度更慢,而訪問快表當中存放的這些頁表項的速度會更快,因此這是快表和慢表名字的由來。可是因爲硬盤的讀寫速度很慢,而CPU處理數據的速度又很快,由於硬盤速度慢而拖累CPU的速度,致使系統總體性能的下降。內存的速度要比硬盤快好幾十倍,因此咱們把CPU要訪問的那些數據先放到內存中就能夠緩和CPU和硬盤之間的速度矛盾。把內存當中最近有可能會被頻繁訪問到的東西放到高速緩存裏,進一步地緩和CPU和存儲設備之間的一個速度矛盾。高速緩存它本質上也是用於存取數據的一個硬件設備。緩存並非內存,CPU訪問高速緩存的速度要比訪問內存的速度要快的多。所以若是咱們能夠把最近想要訪問的那些頁表項的副本把它存到這個快表這種高速緩存當中,那麼CPU在地址變換的時候查詢頁表的這個速度就會快的多了。快表TLB它和咱們平時所說的那種狹義上的高速緩存,狹義上的Cache其實也是有區別的。快表的查詢速度要比慢表快不少。
那接下來咱們要探討的問題是,既然快表的查詢速度快那麼多,那能不能把整個頁表都放在快表當中呢?其實這個緣由不難理解,由於快表這種存儲硬件的造價更貴,所以在成本相同的狀況下,快表能夠存的東西確定沒有那麼多。因此咱們系統當中存儲分級的這個思想和咱們這兒提到的這個例子實際上是如出一轍的。
因此爲了兼顧系統總體的運行效率,同時也要考慮這個造價成本,所以才採用了這種多級的存儲設備。好的那麼剛纔咱們從硬件的角度理解了快表爲何要比慢表更快,那接下來咱們再從這個操做系統的角度來看一下快表到底有什麼做用。
咱們來看這樣的一個例子,(0,0)、(0,4)、(0,8)這樣的幾個邏輯地址,那前面的這個是指頁號,後面的這個指的是頁內偏移量。這個進程的頁表存放在內存當中,是這個樣子。那當這個進程上處理機運行的時候,系統會清空快表的內容。注意啊,快表是一個專門的硬件,當進程切換的時候,快表的內容也須要被清除。
那咱們假設訪問快表、訪問TLB只須要1微秒的時間,而訪問內存須要100微秒的時間。接下來咱們來看一下快表是如何工做的。
那首先這個進程它想要訪問的邏輯地址是頁號爲0、頁內偏移量也爲0的這個邏輯地址。首先這個頁號須要和頁表寄存器當中的頁表長度進行比對,進行越界異常的檢查,而後發現這個頁號並無越界。接下來就會查詢快表,可是因爲這個進程剛上處理機運行,所以快表此時的內容是空的。在快表中找不到頁號爲0所對應的頁表項,所以快表沒有命中。那因爲快表沒有命中,所以接下來就不得不去訪問內存當中存放的慢表,因此接下來經過頁表始址還有頁號計算出對應的頁表項存放的位置。因而,在查詢完慢表以後就能夠知道,0號頁面它所存放的內存塊號是600。注意,在訪問了這個頁表項以後,同時也會把這個頁表項把它複製一份放到快表當中。同時,剛纔不是已經查到這個頁面所對應的內存塊號了嗎?那麼經過這個內存塊號和頁內偏移量就能夠獲得最終的物理地址。最後,就能夠訪問這個邏輯地址所對應的內存單元了。那這是進程訪問的第一個地址。
接下來這個進程想要訪問的地址是頁號爲0、頁內偏移量爲4的這個地址。那一樣的,剛開始會進行一個越界異常的判斷,發現沒有越界。因此接下來會根據頁號來查詢快表,須要確認一下這個頁號所對應的頁表項是否在快表當中。那因爲剛纔咱們已經把它複製到了快表當中,所以這一次的查詢就能夠命中。
而快表命中以後,系統就能夠直接知道,0號頁面它存放的內存塊號是600,所以接下來它就不須要再查詢內存當中的慢表而是直接用這個內存塊號和頁內偏移量獲得最終想要訪問的物理地址,而後進行訪存。
所以,若是快表命中的話,就不須要再訪問內存中的慢表了。
那最後的這個地址其實也是同樣的。也是會先進行越界的檢查,
而後查詢快表結果快表命中。因而系統能夠直接根據查詢快表的結果,獲得最終的這個物理地址,而後訪問最終須要訪問的這個內存單元。那若是這個系統中沒有快表的話,每一次地址變換的過程確定都須要查詢內存中的慢表,而訪問一次內存須要100微秒的時間,所以每一次地址變換都須要花100微秒。而若是說引入了快表的話,那隻要快表命中,咱們的地址變換過程就只須要花費1微秒的時間,因此這也是爲何快表可以加快地址變換的一個緣由。
那須要注意的是,快表中存放的是進程頁表當中的一部分副本。由於以前咱們已經說了,快表雖然速度更快,可是造價其實也要比內存高不少,所以爲了控制成本,快表的容量就不會特別大,因此快表當中只有可能存放慢表中的一部分頁表項的副本,不過這已經可讓系統的效率有很大的提高了,這個咱們以後還會繼續細聊。
那接下來咱們用文字的方式來總結一遍,引入了快表機構以後,地址變換的過程。首先經過這個邏輯地址,咱們能夠獲得頁號和頁內偏移量,而後進行了越界判斷以後,會把這個頁號和快表當中全部的這些頁號進行對比。只不過查詢快表的速度要比查詢慢表的速度快不少。若是慢表命中,也就是找到了這個頁號對應的表項的話,那麼就能夠直接經過快表當中存放的那些信息,直接獲得最終的物理地址,最終再訪問咱們想要訪問的那個內存單元。因此在引入了快表機構以後,若是快表命中的話,咱們訪問一個邏輯地址,只須要一次訪存。也就是訪問咱們最終想要訪問的那個地址單元的時候才須要訪存,而地址轉換的過程中,不須要訪存。固然,若是快表沒有命中的話,那麼咱們依然須要訪問內存當中的頁表,因此在這種狀況下,咱們要訪問一個邏輯地址就須要兩次訪存。第一次訪存是查詢內存當中存放的頁表,第二次訪存是訪問咱們最終想要訪問的那個內存單元。那須要注意的是,在咱們查詢慢表以後,同時也須要把慢表當中的頁表項給它複製到快表當中。而若是快表已經存滿了,那麼咱們須要按照必定的算法,淘汰快表當中的某一些頁表項進行替換。那這個是咱們以後置換算法當中會學習的一個內容,這兒就暫時不展開。總之在引入了快表以後,系統在進行地址變換的時候,它會優先查詢快表。只有快表沒有命中的時候,它纔會去查詢內存當中的頁表。那因爲查詢快表的速度要比查詢慢表的速度快不少,因此這就能夠使這個系統的總體效能獲得提高。基於局部性原理,通常來講快表的命中率能夠達到90%以上。什麼是局部性原理,咱們一下子再解釋。咱們先來看一下假設快表的命中率能夠達到90%的話,它到底可讓這個系統性能提高多少。那根據上面的分析咱們知道,系統在訪問一個邏輯地址的時候,它首先會查詢快表,會消耗1微秒的時間。若是快表命中的話,那麼系統就能夠直接獲得最終想要訪問的物理地址而且訪問這個物理地址對應的內存單元。那訪問這個內存單元總共須要100微秒的時間。因此若是快表命中的狀況下,訪問這樣的一個地址總共就須要耗費1+100這麼多的時間。那再來看第二種狀況,若是快表沒有命中的話,首先系統會查詢快表消耗1微秒的時間,接下來因爲快表沒有命中,因此係統須要訪問內存當中的慢表。那查詢慢表其實就須要訪問一次內存,因此這兒就須要消耗100微秒的時間。那獲得最終的物理地址以後,還須要訪問最終想要訪問的內存單元,由於這兒還須要加上100微秒。那發生這種狀況的機率是10%,因此咱們給它乘上0.1的權重。那若是這個系統沒有快表機構的話,那每一次訪問邏輯地址確定都須要先查詢內存中的慢表,而後最終再訪問咱們的目標內存單元。總之你們在作題的時候,須要注意的點就是,題目當中有沒有告訴你快表和慢表是同時查找的。仍是說,只有快表查詢未命中的時候,再查詢慢表。那無論怎樣,在引入了快表以後,確定這個地址變換的過程都快了不少,系統效能獲得了大幅度的提高。
那接下來咱們來解釋一下剛纔所說的這個快表和慢表同時查找究竟是什麼意思。咱們的第一個例子當中咱們是默認了系統先查詢快表,也就是先消耗了1微秒的時間。當快表查詢未命中的時候,它纔會開始查詢慢表。那查詢慢表的過程又須要消耗100微秒的時間,而若是快表和慢表同時查詢的話,狀況就會變成這樣。快表和慢表是同時開始查詢的,而在1微秒的時候系統發現,這個快表查詢未命中。可是在這個時刻,其實慢表也已經查了一微秒的時間,所以接下來再消耗99微秒就能夠獲得這個慢表的查詢結果。那經過這個甘特圖相信並不難理解,什麼叫快表和慢表同時查找,什麼叫先查快表,快表未命中的時候再查慢表。這是作題的時候你們須要注意的一個小細節。那接下來咱們來思考一個問題,爲何TLB當中只存放了頁表中的一部分就可讓系統的效能提高那麼多呢?
這實際上是由於著名的局部性原理。程序當中的變量,數組還有變量i,這些變量是存放在23號頁面當中的。由於10號頁面當中,存放的是它的這些代碼指令。而這個數組在內存中實際上是連續地存放的。那因爲局部性原理,也就是說這個程序在某段時間內可能會頻繁連續地訪問某幾個特定的頁面,所以在地址變換的過程當中,只要它訪問的是同一個頁面,那麼它查詢頁表的時候其實查到的也都是同一個頁表項。因此只要咱們把慢表當中的頁表項把它複製到快表當中,那這樣就可讓地址變換的速度快不少了,由於就不須要每次查詢慢表。那這就是爲何快表機構可以大幅度地提高系統效能的一個緣由。
在沒有引入快表以前,咱們訪問一個邏輯地址至少須要兩次訪存。第一次訪存是查詢內存當中的頁表,第二次訪存纔是訪問咱們最終想要訪問的那個內存單元。而在引入了快表以後,若是快表命中的話,那麼就只須要一次訪存。若是快表未命中的話,咱們仍然須要兩次訪存,仍然須要查詢內存中的慢表。TLB當中咱們只存有頁表項的副本,存放的是頁表項的副本,而普通的高速緩存當中存放的是其餘數據的副本。因此TLB和Cache仍是有區別的,不能混爲一談。
介紹兩級頁表相關的一系列知識點。
最後咱們還會強調幾個兩級頁表問題在考試當中有可能會做爲考點的一個很重要的幾個細節。那咱們會按照從上至下的順序依次講解。
首先來看我們以前介紹過的單級頁表機制存在什麼問題?而咱們知道每個頁面須要對應一個頁表項,那麼這麼多的頁面就須要對應同等的2的20次方個頁表項。而每一個頁表項的大小是4個字節,因此總共就須要2的22次方個字節來存儲這個進程的頁表。那這麼多的字節,總共就是2的10次方個頁框,也就是1024個頁框。可是以前我們講過,爲了實現經過頁號查詢對應的頁表項這件事情,那麼通常來講整個頁表都是須要連續地存放在內存當中的。所以在這個系統當中,一個進程光它的頁表就有可能須要佔用連續的1024個頁框來存放。那要爲一個進程分配這麼多的連續的內存空間,這顯然是比較吃力的,而且這已經喪失了咱們離散分配這種存儲管理方式的最大的一個優勢,因此這是單級頁表存在的第一個很明顯的缺陷、問題。
那第二個問題,由以前咱們介紹過的局部性原理咱們能夠知道,不少時候其實進程在一段時間內只須要訪問某幾個特定的頁面就能夠正常地運行了。所以,咱們沒有必要讓進程的整個頁表都常駐內存,咱們只須要讓進程此時會用到的那些頁面對應的頁表項在內存當中保存就能夠了,因此這是單級頁表存在的第二個問題。
那麼從剛纔的分析當中咱們知道,單級頁表存在兩個明顯的問題。第一個問題就是頁表必須連續地存放,因此若是頁表很大的話,那麼光頁表就須要佔用連續的不少個頁框。那這和咱們離散分配存儲管理的這種思想實際上是相悖的,因此咱們要嘗試解決這個問題。那第二個問題就是,咱們沒有必要讓整個頁表都常駐內存,由於進程在一段時間內可能只須要訪問某幾個特定的頁面就能夠順利地執行了,那這是基於局部性原理得出的一個結論。那咱們首先討論第一個問題應該怎麼解決。其實咱們能夠參考一下咱們以前解決進程在內存當中必須連續存儲的這個問題的時候,提出的那種思路。那咱們以前的作法其實很簡單,就是把進程的地址空間進行分頁,而後再爲進程創建一張頁表,用來記錄它的各個頁面之間的順序,還有保存的位置這些信息。那一樣的思路其實咱們也能夠用來解決一個頁表必須連續存儲、連續佔用多個頁框的問題。那咱們能夠把這個很長的頁表進行分組,讓每個內存塊恰好能夠放入一個分組。那爲了保證咱們把這些分組離散地放到各個內存塊以後,還可以知道這些分組之間的前後順序,所以咱們依然是像須要模仿以前的這種思路,爲這些分組再創建一個頁表,而後這個頁表就稱爲頁目錄表,或者叫外層頁表,或者叫頂層頁表。固然408的真題當中比較喜歡用的是頁目錄表這個名詞。那這個地方觀看這些文字描述會比較抽象,咱們直接結合圖像來進行進一步的理解。
那既然咱們的頁號有20位,就意味着在這個系統當中,一個進程最多有可能會有2^20次方個頁面,那相應的也會有2^20次方個頁表項。若是用十進制表示的話,這些頁表項的編號應該是0-1048575(這其實就是2^20-1這麼一個數)。那如今因爲這個頁表的長度過大,因此咱們按照以前所說的那種思路,咱們能夠把這麼大的一個長長的頁表,把它拆分紅一個一個的小分組,那每一個小分組的大小可讓它恰好可以裝入一個內存塊。那咱們每一個內存塊或者說每一個頁面的大小是4KB,而頁表項的大小是4B,因此一個內存塊、一個頁面能夠存放4K/4=1K個頁表項,那麼換算成十進制,就應該是1024個頁表項。所以,咱們能夠把這麼大的頁表,拆分紅一個一個的小分組,
每個分組的頁表項有1024個,就像這個樣子。另外,咱們能夠給這些小頁表進行編號。那進行這樣的拆分以後,最後總共就會造成1024個一個一個的小頁表。那這個地方能夠稍微注意一下的是,之前在這個大頁表當中,編號爲1024的這個頁表項在進行拆分之後,應該是變成了第二個小頁表當中的第一個頁表項,因此能夠看到這個頁表項和這個頁表項的這個塊號是同樣的,只不過頁號變爲了從0開始。
那咱們繼續往下分析,在把大頁表拆分這樣的一個一個的小頁表以後,因爲每一個小頁表的大小都是4KB,所以每一個小頁表均可以依次放到不一樣的內存塊當中。因此爲了記錄這些小頁表之間的相對順序,還有它們在內存當中存放的塊號、位置,
那咱們須要爲這些小頁表再創建上一級的頁表,這一級的頁表就叫作頁目錄表或者叫頂級頁表、外層頁表。
那相應的,這一層的小頁表咱們能夠把它稱爲二級頁表。那從這個圖當中也能夠很直觀地看到,頁目錄表實際上是創建了二級頁表的頁號,還有二級頁表在內存當中存放的塊號之間的一個映射的關係。因此若是此時咱們想要找到0號頁表的話,那麼咱們能夠經過頁目錄表就能夠知道0號頁表是存放在3號內存塊裏的,因此只要在3號內存塊這個地方來找0號頁表就能夠了。那在採用了這樣的兩級頁表結構以後,邏輯地址的結構也須要發生相應的變化。咱們能夠把之前的20位的頁號,拆分紅兩個部分。第一個部分是10位的二進制,用來表示一級頁號,第二部分也是10位二進制,用來表示二級頁號。
那10位的二進制你們會發現,恰好是能夠表示0-1023這麼一個範圍,
因此用一級頁號來表示這個範圍是恰好的。
那相應的二級頁號這十個二進制位,就是用來表示二級頁表當中的這些頁號。
那接下來咱們再結合這個例子來看一下咱們應該怎麼實現地址的變換?那麼要進行這個地址變換,咱們要作第一件事情就是根據咱們的地址結構把邏輯地址拆分紅三個部分,也就是一級頁號,二級頁號還有頁內偏移量這麼三個部分。那第二步,咱們能夠從PCB當中知道咱們的頁目錄表在內存當中存放的位置究竟是哪裏。
那這樣的話咱們就能夠根據一級頁號來查詢頁目錄表了。那一級頁號是0,因此咱們查到的表項應該是這個表項。那從這個頁表項當中咱們能夠知道,0號的二級頁表存放在內存塊號爲3號的地方,也就是這個位置。
因此咱們能夠從這個位置讀出二級的頁表,而後開始用二級頁號來再進行查詢。那二級頁號是1,因此咱們查詢到的頁表項應該是這一項。那經過這個頁表項咱們就能夠知道,最終咱們想要訪問的地址應該是在4號內存塊裏的。
因此接下來咱們就能夠根據最終要訪問的內存塊號和頁內偏移量得出咱們最終的物理地址了。
那因爲咱們想要訪問的是4號內存塊,而且每一個內存塊的大小是4KB,也就是4096個字節,因此4號內存塊的起始地址應該是4*4096就等於16384。另外,頁內偏移量把它轉換爲十進制以後,應該是1023。因此咱們能夠用內存塊的起始地址再加上頁內偏移量的這個數字就能夠獲得最終的物理地址,17407了。
那通過剛纔的一系列分析咱們就解決了咱們以前提出的第一個問題。當頁表很大的時候,其實咱們能夠採用兩級頁表的這種結構來解決這個頁表必須連續地佔用多個頁框的問題。那接下來咱們再來看一下第二個問題應該怎麼解決。其實若是說不讓整個頁表常駐內存的話,那麼咱們能夠在須要訪問頁面的時候才把頁面調入內存。其實這是我們以後會介紹的虛擬存儲技術。這個在以後的小節當中會有更詳細的介紹,這兒只是先簡單地提一下它的思想。
那咱們能夠給每個頁表項增長一個標誌位,用來表示這個頁表項對應的頁面到底有沒有調入內存。
那若是說此時想要訪問那個頁面暫時尚未調入內存的話,那麼就會產生一個缺頁中斷。而後操做系統負責把咱們想要訪問的那個目標頁面從外存調入內存。那缺頁中斷確定是咱們在執行某一條指令,這個指令想要訪問到某一個暫時尚未調入的頁面的時候產生的,因此這個中斷信號和當前執行的指令有關,所以這種中斷應該是屬於內中斷。那這個部分的內容我們在以後的小節當中還會有更詳細的介紹。
那接下來咱們再來強調幾個在考試當中須要特別注意的小細節。第一個,若是咱們採用的是多級頁表機構的話,那麼各級頁表的大小不能超過一個頁面。那這個限制的條件咱們在作題的時候應該怎麼應用呢?咱們直接來看一個例子。那因爲採用多級頁表的時候,各級頁表的大小不能超過一個頁面,因此說各級頁表當中頁表項最多不能超過2^10個。那相應的,各級頁號所佔的位數也不能超過10位。因此28位的頁號咱們能夠把它分紅3個部分,一級頁號佔8位,二級頁號10位,三級頁號也佔10位。那相應的,這樣的話咱們就須要再創建更高一級的頁表,最終會造成三級頁表的一個結構。那三級頁表的原理,和兩級頁表的原理實際上是如出一轍的,這個地方就再也不展開贅述。那這個地方假如說咱們只是採用了兩級頁表的結構的話,那麼第一級的頁號就會佔18位,也就是說在頁目錄表中,最多有可能會有2^18個頁表項。那這麼多的頁表項,顯然是不能放在一個頁面裏的,因此這就違背了採用多級頁表的時候,各級頁表的大小不能超過一個頁面這樣的一個條件,所以,若是咱們只把它分紅兩級是不夠的。那這就是咱們須要注意的第一個細節,這個頗有可能做爲考點在選擇題甚至是結合大題來進行考查。
那第二個咱們須要注意的點是,兩級頁表的訪存次數的分析。假設咱們沒有采用快表機制的話,那麼第一次訪存應該是訪問內存當中的頁目錄表,也就是頂級頁表。第二次訪存應該是訪問內存當中的二級頁表。第三次訪存纔是訪問最終的目標內存單元。因此採用兩級頁表結構的話,咱們要訪問一個邏輯地址須要進行三次訪存。那還記得咱們分析的單級頁表的訪存次數問題嗎?若是採用的是單級頁表結構的話,那麼第一次訪存就是查詢頁表,第二次訪存就是訪問咱們最終想要訪問的內存單元。因此單級頁表在訪問一個邏輯地址的時候,只須要進行兩次訪存。所以,兩級頁表雖然解決了咱們以前提出的單級頁表的那兩大問題,可是這種內存空間的利用率的上升,付出的代價就是,邏輯地址變換的時候,須要進行更多一次的訪存,這樣的話就會致使咱們要訪問某一個邏輯地址的時候,須要花費更長的時間,因此這是兩級頁表相比於單級頁表來講的一個很明顯的缺點。那若是咱們繼續分析三級頁表、四級頁表結構當中的訪存次數的話,會發現三級頁表訪問一個邏輯地址須要訪存四次,四級頁表須要訪存五次,五級頁表須要訪存六次。因此實際上是有一個規律,若是沒有快表機構的話,那麼N級頁表在訪問一個邏輯地址的時候,訪存次數應該是N+1次。那這就是咱們須要注意的兩個很重要的小細節。
好的那麼這個小節當中咱們介紹了兩級頁表相關的知識點。咱們從單級頁表存在的兩個問題出發,來依次探討了這兩個問題應該怎麼解決。特別是第一個。那採用了兩級頁表結構以後,咱們就能夠解決第一個問題。但第二個問題的解決須要採用虛擬存儲技術,這個我們會在以後的小節進行更詳細的講解。那在本節當中,咱們須要重點理解兩級頁表的邏輯地址結構。還須要注意頁目錄表、外層頁表、頂級頁表這幾個說法,不過在408當中,最經常使用的是頁目錄表這個術語。另外,你們也須要理解採用了兩級頁表以後,如何實現邏輯地址到物理地址的轉換。那這個轉換過程其實和我們以前介紹的單級頁表並無太大的差別,無非就是還須要多查一級的頁表而已。那這個過程須要可以本身分析。那最後,咱們強調了兩個咱們須要注意的小細節,第一個小細節,多級頁表當中,各級頁表的大小不能超過一個頁面。因此說,若是兩級頁表不夠的話,那麼咱們能夠進行更多的分級。第二個小細節,咱們要須要本身可以分析多級頁表的訪存次數,那N級頁表訪問一個邏輯地址是須要N+1次訪存的。
那另外,你們還須要可以根據題目給出的邏輯地址位數,頁面大小,頁表項大小這幾個條件來肯定多級頁表的邏輯地址結構。那這些內容還須要你們結合課後習題來進行鞏固和消化。
在這個小節中咱們會學習另外一種離散分配的存儲管理方方式,叫基本分段存儲管理。
那這種管理方式,和我們以前學習的分頁存儲最大的區別其實就是,離散分配的時候,所分配的地址空間的基本單位是不一樣的。那這個小節中,咱們會首先介紹什麼是分段。那分段的這個概念、思想其實有點相似於咱們分頁存儲管理當中的分頁。而以後咱們會介紹什麼是段表。段表就有點相似於分頁存儲管理當中的頁表。另外,在離散分配存儲管理方式當中,我們避免不了必定要談的問題是怎麼實現地址變換。最後,咱們會對分段和分頁這兩種管理方式進行一個對比。那咱們會按照從上至下的順序依次講解。
那首先來看一下什麼是分段。每個段就表明一個完整的邏輯模塊。好比說0號段的段名叫MAIN,而後0號段存放的就是main函數相關的一些東西。而後1號段存放的是某一個子函數。2號段存放的是進程A當中某些局部變量的這些信息。那能夠看到,每個段都會有一個段名。這個段名是程序員在編程的時候使用的。另外呢,每一個段的地址都是從0開始編址的。因此,進程A原本是有16KB的地址空間。那分段以後,第一個段,0號段,它的地址空間就是0-7KB-1,總共的大小就是7KB。而後1號段是0-3K-1,總共的大小是3KB,2號段也同樣。那操做系統在爲用戶進程分配內存空間的時候,是以段爲單位進行分配的。每一個段在內存當中會佔據一些連續的內存空間,而且各段之間能夠不相鄰。好比說0號段佔據的是從80K這個地址開始的連續的4KB的內存空間,而1號段佔據的是從120K這個地址開始連續的3KB的地址空間。那因爲分段存儲管理當中,是按照邏輯功能來劃分各個段的,因此用戶編程會更加方便,而且程序的可讀性會更高。好比說用戶能夠用低級語言、彙編語言寫這樣兩條指令。那第一條指令是把分段D當中的A單元內的值讀到寄存器1當中。第二個指令是把寄存器1當中的內容存到X分段當中的B單元當中。那因爲各個分段是按邏輯功能模塊來劃分的,而且這些段名也是用戶本身定義的,因此用戶在讀這個程序的時候就知道這兩句代碼作的事情,就是把某個全局變量的值賦給X這個子函數當中的某一個變量。所以對於用戶來講採用了分段機制以後,程序的可讀性仍是很高的。那在用戶編程的時候,使用的是段名來操做各個段。可是在CPU具體執行的時候,其實使用的是段號這個參數,
因此在編譯程序其實會把這些段名轉換成與它們各自相對應的這些一個一個段號,而後CPU在執行這些指令的時候,是根據段號來區分各個段的。
那在採用了分段機制以後,邏輯地址結構就變成了這個樣子。由段號和段內地址(或者叫段內偏移量)組成。好比說像這個例子當中,段內地址是佔了0-15總共16位,而後段號是16-31,總共佔的也是16位。那在考試當中咱們須要注意的一個很高頻的考點就是,段號的位數決定了每一個進程最多能夠分多少個段。而段內地址的位數決定了每一個段的最大長度是多少。那咱們以這個例子爲例來看一下16位的段號和16位的段內地址,最大能夠支持幾個分段,每一個段的最大長度又是多少。那咱們假設這個系統是按字節編址的,也就是說一個地址對應的是一個字節的大小。那段號佔16位,因此在這個系統當中,每一個進程最多能夠有2^16個段,也就是64K個段。由於16位的二進制數,最多也就能用來表示這樣一個範圍的數字。那一樣的,段內地址也是佔16位,而且這個系統是按字節編址的,因此每一個段的最大長度應該是2的16次方也就是64KB這樣的一個大小。那剛纔咱們提到的這兩句用匯編語言寫的指令,
在通過編譯程序編譯以後,段名會被編譯程序翻譯成對應的段號。而這裏提到的A單元、B單元這樣的助記符,會被編譯程序翻譯成段內地址,也就是這個第二個部分。就像這個樣子,每一個段名會被翻譯成與它們對應的各個段號,另外,各個段之間的這些用助記符表示的內存單元,會被最終翻譯爲這個段當中的段內地址。那這就是分段相關的一些最基本的概念。
那接下來咱們再來看下一個問題。既然咱們的程序被分爲了多個段,而且各個段是離散地存儲在內存當中的。
爲了保證程序可以正常地運行,因此操做系統必須可以保證要能從物理內存當中找到各個邏輯段存放的位置。所以,爲了記錄各個段的存放位置,
操做系統會創建一張段映射表,簡稱「段表」,就像這個樣子。
那用段表記錄了各個邏輯段在內存當中的存放的位置。那這個地方你們會發現,段表的做用其實和我們以前學習的頁表的做用是比較相似的。頁表是創建了各個邏輯頁面到實際的物理頁框之間的映射關係,而段表是記錄了各個邏輯段到實際的物理內存存放位置之間的映射關係。那每一個段表由段號、段長和段基址組成。這個段基址其實就是段在內存當中的存放的起始位置,那從這個圖當中咱們也能很直觀地看到,每一個段會對應一個段表項。那相比於頁表來講,段表當中多了一個更不一樣的信息就是段長,由於每一個分段的長度多是不同的。而咱們在分頁存儲管理當中,每一個頁面的長度確定都是同樣的。因此在分頁內存管理當中,頁長是不須要這樣顯式地記錄的。可是在分段存儲管理當中,段的長度是須要這樣顯式地記錄在段表當中。
那第二點咱們須要注意的是,咱們的各個段表項的長度實際上是相同的。也就是說,這些一行一行的段表項,在內存當中所佔的空間,是大小是相同的。好比說,這個系統按照字節尋址,而且採用分段存儲管理方式。邏輯地址結構,段內地址是16位,段的長度不可能超過2的16次方字節。因此在各個段表項當中,用16位就確定能夠表示這個段的最大段長了。那假設這個系統的物理內存大小是4GB,那也就是2的32次方個字節。那這麼大的物理內存的地址空間,能夠用32位的二進制來表示,因此對於基址,也就是內存的某一個地址這個數據,咱們只須要用32個二進制位就能夠表示了。所以每一個段的段表項,其實只須要16+32位也就是48位總共6個字節就能夠表示一個段表項。所以在這個系統當中,操做系統能夠規定每個段表項的長度就是固定的6個字節。前兩個字節表示的是段長,然後面四個字節表示的是這個段存放的在內存當中的起始地址。
因此和頁表相似,這個地方的頁號能夠是隱含的,頁號並不佔存儲空間。那咱們在查詢段表的時候,只要咱們可以知道段表在內存當中的起始地址M,那咱們想要查詢K號段對應的段表項,那咱們只須要用段表的起始地址M,再加上K乘以每一個段表項的大小6個字節,那就能夠獲得咱們想要找到的那個段對應的段表項在內存當中的什麼位置了。因此即便這個段號是隱含的,沒有顯式地給出。可是咱們依然能夠根據段號來查詢這個段表。
那接下來咱們再來看一下采用了分段存儲管理以後,地址變換的過程是什麼樣的。那仍是以剛纔提到的這個指令爲例,這個用匯編語言寫的指令通過編譯程序編譯以後,會造成一條等價的機器指令。好比說這條機器指令就是告訴CPU,從段號爲2,段內地址爲1024的這個內存單元當中取出內容,放到寄存器1當中。不過在計算機硬件看來,段號、段內地址這些邏輯地址實際上是用二進制表示,好比說是這個樣子。那前面的紅色的這16位表示的是段號,然後面的黑色的這16位表示的是段內地址。因此CPU在執行指令的時候,或者說在訪問某一個邏輯地址的時候,須要把這個邏輯地址變換爲物理地址。
那咱們看一下具體的變換過程。在內存的系統區當中,存放着不少用於管理系統當中的軟硬件資源的數據結構,包括進程控制塊PCB也是存放在系統當中的。那當一個進程要上處理機運行以前,進程切換相關的那些內核程序會把進程的運行環境給恢復,那這就包括一個很重要的硬件寄存器當中的數據的恢復。這個寄存器叫作段表寄存器,用於存放這個進程對應的段表在內存當中的起始地址還有這個進程的段表長度究竟是多少。所以段表存放的位置還有段表長度這兩個信息在進程沒有上處理機運行的時候是存放在進程的PCB當中的。那當進程上處理機運行的時候,這兩個信息會被放到很快的段表寄存器當中。那當知道了段表的起始地址以後,就能夠知道段表是存放在內存當中的什麼地方。
那接下來這個進程的運行過程中,避免不了要訪問一些邏輯地址。
好比說要訪問邏輯地址A。那麼系統會根據邏輯地址獲得段號S和段內地址W,這是第一步要作的事。第二步,知道了段號以後,須要用段號和段表長度進行一個對比來判斷一下段號是否產生了越界。若是段號大於等於段表長度的話,就會產生越界中斷。那麼接下來就會由中斷處理程序來負責處理這個中斷。若是沒有產生中斷的話,就會繼續執行下去。這個地方稍微注意一下,段號是從0開始的,段表長度至少是1,因此當S=M的時候,其實也是會產生越界中斷的。那在肯定這個段號是合法的沒有越界以後,就會根據段號還有段表始址來查詢段表,找到這個段號對應的段表項。那以前我們提過,因爲各個段表項的大小是相同的,因此用段表始址+段號*段表項的長度就能夠找到咱們要找的目標段對應的段表項在內存中的位置了,那接下來就能夠讀出這個段表項的內容。第四步,在找到了這個段號對應的段表項以後,系統還會對這個邏輯地址當中的段內地址W進行一個檢查,看看它是否已經超過了這個段的最大段長,那若是段內地址大於等於這個段的段長的話,就會產生一個越界中斷,不然繼續執行。那這一步也是和咱們頁式管理當中區別最大的一個步驟。由於在頁式管理當中,每一個頁面的頁長確定是同樣的,因此係統並不須要檢查頁內偏移量是否超過了頁面的長度。可是在分段存儲管理方式當中又不一樣,各個段的長度不同,因此必定須要對段內地址進行一個越界的檢查,因此這一步是須要着重注意的。那咱們繼續往下,由於咱們此時已經找到了目標段的段表項,
因此咱們就知道目標段存放在內存當中的什麼地方。那最後咱們根據這個段的基址,也就是這個段在內存當中的起始地址,再加上這個最終要訪問的段內地址就能夠獲得咱們最終想要的物理地址了。
那咱們以以前提到的這個邏輯地址爲例,進行一次完整的分析。若是說此時要訪問的邏輯地址的段號是2,而後段內地址是1024的話,那首先須要用段號2和段表長度M進行一個檢查,那顯然此時這個進程的段表長度應該是3,由於它有3個段,因此段號是小於段表長度的,所以段號合法,因此就能夠進行下一步,用段號和段表始址查到這個段號對應的段表項,那這樣的話就找到了2號段對應的段表項。那接下來須要對段內地址的合法性進行一個檢查。段內地址和段長進行對比,發現2號段的段長是6K,而段內地址是1024,也就是1K,因此段內地址是小於段長的,所以在這個地方並不會產生越界中斷,能夠繼續進行下去。那接下來經過這個段表項咱們知道了這個段在內存當中存放的起始地址是40K,因此用這個段的起始地址40K再加上段內地址W也就是1024,那這樣的話咱們就獲得了最終想要訪問的目標內存單元,也就是A那個變量存放的位置,那這樣的話就完成了對這個邏輯地址的一個訪問。那分段存儲管理當中的這個地址變換的過程,須要和分頁存儲管理的過程進行一個對比記憶。那其實你們着重須要關注的是,分段和分頁最大的區別就在於,在分頁當中,每一個頁面的長度是相同的,而分段當中每一個段的長度是不一樣的,因此在分頁管理當中,並不須要對頁內偏移量(頁內地址)進行越界的檢查。可是在分段管理當中,咱們必定須要對段內地址也就是段內偏移量和段長進行一個對比檢查,那這就是分段和分頁這兩種存儲管理方式當中進行地址變換過程時候最大的一個區別。
那接下來咱們再把分段和分頁這兩種管理方式進行一個統一的對比。在分頁的時候只考慮各個信息頁面的物理大小,好比說每一個頁面是4KB。可是在分段的時候必須考慮到信息的這些邏輯關係,好比說某一個具備完整邏輯功能的模塊,單獨地劃分紅一個段。那另外,分段的主要目的是爲了實現離散分配,提升內存利用率。可是分段的主要目的是爲了更好地知足用戶需求,方便用戶編程。因此分頁其實僅僅只是系統管理上的須要,它只是一個系統行爲,對用戶是不可見的。也就是說,用戶是並不知道本身的進程究竟是分爲了幾個頁面,甚至不知道本身的進程是否是被分頁了,但相比之下分段對於用戶是可見的,用戶在編程的時候就須要顯式地給出段名。因此用戶實際上是知道本身的程序會被分段,甚至知道會被分爲幾個段,每一個段的段名是多少。另外,頁的大小是固定的,而且這個頁面的大小是由系統決定的。但段的長度卻不固定,取決於用戶編寫的程序究竟是什麼樣一個結構。
那從地址空間的角度來講,分頁的用戶進程,地址空間是一維的。好比說,一個用戶進程的大小總共是16KB,那麼在用戶看來,它的整個進程的邏輯地址空間,應該是從0-16K-1。那用戶在編程的時候,只須要用一個記憶符就能夠表示一個地址,好比說用一個記憶符A來表示某個頁面當中的某一個內存單元。
但若是系統採用的是分段存儲管理的話,那麼用戶進程的地址空間是二維的,用戶本身也知道本身的進程會被分爲0、一、2這麼幾個段,而且每一個段的這個邏輯地址都是從0開始的,
因此在分段管理的這種系統當中,用戶編程的時候既須要給出段名,也須要給出段內地址。
好比說我們以前提到的這個彙編語言指令,用戶須要顯式地給出段名還有段內地址。那所以,在分頁管理當中,在用戶本身看來,本身的這個進程的地址空間是連續的,可是在分段存儲管理當中,用戶本身也知道本身的進程地址空間是被分爲了一個一個的段,而且每一個段會佔據一連串的連續的地址空間。所以,分頁當中進程的地址空間是一維的,而分段的時候,進程的地址空間是二維的。那這個點在選擇題當中仍是很容易進行考查的。
那除了以前所說的那些不一樣以外,分段相比於分頁來講最大的一個優勢應該是它更容易實現信息的共享和保護。好比說一個生產者進程,總共是16KB這麼大,
那麼它可能會被分爲這樣的三個段。其中一號段是用來實現判斷緩衝區此時是否能夠訪問這樣一個功能,那其實除了這個生產者進程以外,其餘的生產者進程消費者進程它們也須要判斷緩衝區此時是否能夠訪問。所以,這個段當中的代碼,應該容許各個生產者進程、消費者進程共享地訪問。那怎麼實現共享地使用這個段呢?
假設咱們的這個生產者進程它有這樣的一個段表。它的1號段也就是判斷緩衝區的那個段,是存放在內存的120K這個地址開始的這個內存空間當中的。
那若是說消費者進程想要和它共享地使用這個1號段的話,那麼很簡單,可讓消費者進程的某一個段表項一樣是指向這個段存放的起始地址的。因此若是咱們想要實現共享的話,就要讓各個進程的某一個段表項指向同一個段就能夠了。
那這個地方須要注意的是,只有純代碼或者叫可重入代碼也就是不能被修改的代碼,能夠被共享地訪問。那這種代碼不屬於臨界資源,各個進程即便併發地訪問這一系列的代碼也不會由於併發產生問題。
好比說有一個代碼段只是簡單地輸出「Hello World!」這麼一個字符串,那麼全部的進程併發地訪問這個代碼段那顯然是不會出問題的。可是對於可修改的代碼段來講,是不能夠共享的。所以,對於代碼來講,只有純代碼這種不屬於臨界資源的代碼能夠被共享地訪問。那這是在分段存儲管理方式當中實現共享的一個很簡單的方式。
那接下來咱們再來看一下爲何分頁管理當中不方便實現這種信息的共享。假設咱們把這個消費者進程進行分頁的話,那麼第一個頁是0號段當中的前半部分的位置佔4KB,那第二個頁它會包含0號段當中的3KB和1號段當中的1KB,那這兩個總共組成了4KB的頁面。那相似於的,第三個頁面也會包含一半1號段的內容,還有另外一半是2號段的內容。
因此若是採用分頁這種方式的話,那麼咱們若是讓消費者的某一個頁表項也指向這個生產者進程的分頁的話,那麼顯然是不合理的。由於生產者進程的這個分頁當中,只有綠色部分是容許被消費者進程共享的,可是橙色部分不該該被消費者進程所共享。
所以,因爲頁面它並非按照邏輯模塊來進行劃分的,因此咱們就很難實現共享,並不像分段那麼方便。
那其實對於信息的保護,原理也是相似的。好比說在生產者進程當中,1號段應該是容許被其餘進程訪問的。那咱們只須要把這個段標記爲容許其餘進程訪問,其餘的那些段標記爲不容許其餘進程訪問。那這就很簡單地就實現了對於各個段的保護。
可是若是採用分頁存儲管理的話,1號頁和2號頁當中只有一部分也就是綠色這些部分是容許其餘進程訪問的,而其餘的橙色和紫色的部分,不該該容許被其餘進程訪問。因此這樣的話咱們其實不太方便對各個頁面進行標記究竟是否容許被其餘進程訪問。所以,採用分頁存儲的時候,更不容易實現對信息的保護和共享這兩個功能。
那這是關於信息的共享和保護,經過剛纔的講解,相信不難理解。那接下來咱們再來探討咱們在分段和分頁這兩種方式當中,訪問一個邏輯地址須要幾回訪存。若是咱們採用的是單級頁表的分頁存儲管理的話,那麼第一次訪存應該是查詢內存當中的頁表,第二次訪存纔是查詢最終的目標內存單元。那這個過程我們在以前已經分析過不少次,就再也不展開。因此採用單級頁表的分頁存儲管理,總共須要兩次訪存。
那若是採用分段的話,第一次訪存是查詢內存當中的段表,第二次訪存是訪問目標內存單元。因此採用分段的時候,也是總共須要兩次訪存。那在分頁存儲管理當中咱們知道,咱們能夠引入快表機構來減小在進行地址轉換的時候訪問內存的次數。因此其實在分段管理當中也相似,咱們也能夠引入快表機構,而後能夠把近期訪問過的段表項放到快表當中,那這樣的話只要快表可以命中,那麼咱們就不須要再到內存當中查詢段表,咱們就能夠少一次訪存。那這就是分段和分頁管理的一個對比。
那在學習了分頁存儲管理以後,這個小節的內容其實並不難理解。咱們介紹了什麼是分段,在分段存儲管理當中,邏輯地址結構是什麼樣的。另外,咱們介紹了和頁表很相似的段表,只不過對於段表來講,你們須要着重注意的是,每一個段表項當中,必定會記錄這個段的段長是多少。那在分頁存儲管理當中,每一個頁面的長度是不須要顯式地在頁表當中記錄的。由於各個頁面的長度同樣,而在分段存儲當中,各個段的長度是不同的。因此這是它們倆之間的一個最明顯的一個區別。那因爲各個段的段長不同的,因此在地址變換的時候你們也須要注意,在找到了對應的段表項以後,還須要對段長和段內地址進行一個對比的檢查,看一下段內地址是否越界。那除了這個步驟以外,其餘的那些步驟其實和頁式管理當中,地址變換的過程也是大同小異的。那分段和分頁的對比這些知識點,是很容易在選擇題當中進行考查的。因此你們仍是須要理解這些點。那這個小節的內容還須要你們經過課後的習題再進行進一步的實踐鞏固,也須要可以根據題目當中給出的信息來手動地完成這個地址變換的過程。
那段頁式管理實際上是分段和分頁這兩種管理方式的一個結合。那以後咱們會介紹分段和分頁這兩種方式、這兩種思想的一種結合,從而引出了段頁式管理方式。那以後咱們還會介紹在段頁式管理當中,段表和頁表與分段、分頁管理當中的段表、頁表有什麼相同和不一樣的地方。那最後咱們還會介紹怎麼實現從邏輯地址到物理地址的變換。那咱們會按照從上至下的順序依次講解。
因爲分頁是按照信息的物理結構來進行劃分的,因此咱們不太方便按照邏輯模塊、邏輯結構來實現對信息的共享和保護。分段是按照信息的邏輯結構來進行劃分的,所以採用這種方式就很方便按照邏輯模塊實現信息的共享和保護。不過缺點呢,若是說咱們的段很長的話,就須要爲這個段分配很長很大的連續空間,那不少時候分配很大的連續空間會不太方便。那另外,段式管理是hi會產生外部碎片的,它產生外部碎片的原理其實和動態分區分配很相似。好比說一個系統的內存原本是空的,
那麼前後來了三個分段,它們都須要佔用連續的這種存儲空間。
那這個地方有4M字節的空閒區間,
那以後這個分段用完了,因而把它撤離內存。
那接下來又來了一個分段,佔4M字節。
若是它佔用了這個分區的話,那這個地方就會產生10M字節的一個空間。
那接下來若是上面這個段也撤離了,
那接下來再來了一個分段,也是佔14M字節,
那這個地方就會產生6M字節的空閒的區間。
那在接下來若是還有一個分段到來,它總共須要佔20M字節的這種連續的內存區間。那因爲此時這些空閒區間並不連續,因此雖然它們的大小總和是20M字節,
可是這個分段是放不進內存當中的,由於分段必須連續地存放。因此很顯然,段式管理是會產生這些難以利用的外部碎片的。
不過,對於外部碎片的解決,其實和我們以前介紹的那種動態分區分配也同樣,
能夠經過這種緊湊的方式,
來創造出更大的一片連續的空間。
可是緊湊技術須要付出比較大的時間代價,因此顯然這種處理方式也並非一個很完美的解決方式。因此基於分頁管理和分段管理的這些優缺點,人們又提出了分段和分頁這兩種思想的一個結合,因而產生了段頁式管理,段頁式管理就具有了分頁管理和分段管理的各自的優勢。
在採用段頁式管理的系統當中,一個進程會按照邏輯模塊進行分段。以後各個段還會進行分頁,好比說每一個頁面的大小是4KB,那麼0號段原本是7KB它會被分爲4KB和3KB這樣兩個頁面。
那對於內存來講,內存空間也會被分爲大小相等的內存塊,或者叫頁框、頁幀、物理塊。那每個內存塊的大小和系統當中頁面的大小是同樣的,也就是4KB。那最後,進程的這些頁面會被依次放到內存當中的各個內存塊當中。
那咱們在上個小節中學過,若是採用的是分段管理的話,那麼邏輯地址結構是由段號和段內地址組成的。而在段頁式管理當中咱們會發現,一個進程被分段以後,各個段還會被再次分頁,因此對於段頁式管理來講,它的邏輯地址結構,應該是由段號、頁號還有頁內偏移量組成。那這個地方的頁號和頁內偏移量其實就是分段管理當中的段內地址進行再拆分的一個結果。
那在考試當中須要的注意的是,段號的位數決定了咱們一個進程最多能夠分幾個段,而頁號的位數決定了每一個段最大會有多少頁,頁內偏移量的位數又決定了頁面的大小和內存塊的大小。
因此若是一個系統當中它的地址結構是這樣的,而且這個系統是按字節尋址的話,那麼段號佔16位,因此這個系統當中每一個進程最多能夠有2^16也就是64K個段。而頁號佔4位,因此每一個段最多會有2^4=16頁。另外頁內偏移量佔12位,因此每一個頁面/每一個內存塊的大小是2^12=4096=4KB。
那在段頁式管理當中,分段這個過程對用戶來講是可見的,程序員在編程的時候須要顯式地給出段號和段內地址這樣兩個信息。可是把各個段進行分頁的這個過程,對用戶來講是不可見的,這只是一個系統的行爲。系統會把段內地址自動地劃分爲頁號和頁內偏移量這樣兩個部分。因此對於用戶來講,他在編程的時候,只須要關心段號和段內地址這兩個信息,而剩下的分頁是由操做系統完成的。所以段頁式管理的地址結構是二維的。那與此相對的,段式管理當中地址結構也是二維的。而頁式管理當中,地址結構是一維的。
那與以前我們介紹的分頁和分段管理當中的思想相同,對進程分段再分頁以後,咱們也須要記錄各個段、各個頁面存放的一個位置。因此係統會爲每一個進程創建一個段表,進程當中的各個段會對應段表當中的一個段表項。而每一個段表項由段號、頁表長度和頁表存放塊號組成。那因爲每一個物理塊的大小是固定的,因此只要知道頁表存放的物理塊號,其實就能夠知道頁表存放的實際的物理地址究竟是多少了。那好比說咱們要查找0號段對應的頁表,
那麼咱們知道這個頁表存放在內存爲1號塊的地方,也就是這個位置。因而就能夠從這個內存塊當中讀出0號段對應的頁表。
那因爲0號段長度是7KB,而每一個頁面大小是4KB,因此它會被分紅兩個頁面,相應的這兩個頁面就會依次對應頁表當中一個頁表項。每個頁表項記錄了每個頁面存放的內存塊號究竟是多少。
因此經過剛纔的講解你們會發現,在段頁式管理當中,段表的這個結構和段式管理當中的段表是不同的。段式管理當中的段表記錄的是段號還有段的長度,還有段的起始地址這麼三個信息。而段頁式管理當中,記錄的是段號、頁表長度、頁表存放塊號這麼三個信息,也就是後面的這兩個信息不太同樣。而對於頁表來講,段頁式管理和分頁管理的頁表結構基本上都是相同的,都是記錄了頁號到物理塊號的一個映射關係。那各個段表項的長度是相等的,因此段號能夠是隱含的。各個頁表項的長度也是相等的,因此頁號也是能夠隱含的。那這兩點我們在以前的小節有詳細地介紹過,這兒就再也不展開。那從這個分析當中咱們會發現,一個進程只會對應一個段表,可是每一個段會對應一個頁表,所以一個進程有可能會對應多個頁表。再重複一遍,一個進程會對應一個段表,可是一個進程有可能會對應多個頁表。
那麼接下來咱們再來看一下怎麼實現段頁式管理當中的這種邏輯地址轉換爲物理地址的這個過程。首先須要知道的是系統當中也會有一個段表寄存器這麼一個硬件,而後在這個進程上處理機運行以前,會從PCB當中讀出段表始址還有段表長度這些信息而後放到段表寄存器當中。
那在進行地址轉換的時候,第一步是須要根據邏輯地址獲得段號、頁號還有頁內偏移量這麼三個部分。
那第二步須要把段號和段表長度進行一個對比,檢查段號是否越界,是否合法。若是越界的話就會拋出一箇中斷,以後由中斷處理程序進行處理。若是沒有越界的話,就證實段號合法,就能夠繼續執行。
那接下來一步能夠根據段號還有段表始址來計算出這個段號對應的段表項在內存當中的位置。這樣的話,就找到了咱們想要找的這個段表項。
接下來一步須要注意,因爲各個段的長度是不同的,因此各個段把它們分頁以後,可能分爲數量不等的不一樣的一些頁面。好比說有的段長一些,它就能夠分爲兩個頁面。有的段短一些,只須要用一個頁面。因此因爲各個段分頁以後頁面數量可能不一樣,所以這個地方咱們也須要對頁號的合法性進行一個檢查,看看頁號是否已經越界。若是頁號沒有超出頁表長度的話,那麼就能夠繼續往下執行。那經過這個頁號咱們知道了頁表存放的位置,
因而就能夠從這個位置讀出頁表。因而能夠根據頁號來找到咱們想要找的那個頁表項,那找到這個頁表項以後咱們就知道這個頁面在內存當中存放的位置。
因此最後咱們能夠根據頁表項當中對應的這個內存塊號和頁內偏移量進行二進制的拼接,最終造成要訪問的物理地址。那最終咱們就能夠根據這個物理地址進行訪存,訪問目標內存單元。所以在段頁式管理當中,進行地址轉換的這個過程總共須要三次訪存。
第一次是訪問內存當中的段表,第二次訪存是訪問內存當中的頁表,第三次訪存纔是訪問最終的目標內存單元。那咱們以前也介紹過,在分頁和分段這兩種管理方式當中,能夠用引入快表機構的方式來減小地址轉換過程中訪存的次數。
因此這個地方咱們也能夠用相同的思路。咱們能夠引入快表機制,用段號和頁號做爲快表的查詢的關鍵字。那若是快表命中的話,咱們就能夠知道咱們最終想要訪問的那個頁面到底在什麼位置。所以,只要快表命中,咱們就不須要再查詢段表和頁表了,這樣的話咱們僅須要一次訪存也就是最終訪問目標內存單元這一次。那麼這就是段頁式管理方式當中進行地址變換的一個過程。須要着重注意的是,這一步就是檢查頁號是否越界,那這個段式存儲當中檢查段內地址是否越界是比較相似的。須要檢查的本質緣由就在於各個段的長度多是不相等的,所以須要進行這樣一個合法性的檢查。
段頁式管理當中,邏輯地址結構由段號、頁號和頁內偏移量這麼三個部分構成。可是用戶在編程的時候只須要顯式地給出段號和段內地址,以後會由系統自動地把段內地址拆分爲頁號和頁內偏移量這麼兩個部分。所以因爲用戶只須要提供段號和段內地址這麼兩個信息,所以段頁式管理當中,地址結構是二維的。那顯然分段對於用戶來講是可見的,可是分頁是操做系統管理的一個行爲,對於用戶來講不可見。那麼在這個小節當中咱們還介紹了段表和頁表的結構還有原理。須要注意的是,段頁式管理中的段表,和分段管理當中的段表結構是不太同樣的。段頁式管理當中,段表由段號、頁表長度和頁表存放地址這麼三個信息組成。可是在分段管理當中,由段號、段的長度還有段的起始地址這麼三個信息組成,因此段表是不太同樣的。可是頁表的話和分頁存儲當中的頁表的結構是相同的,都是由頁號還有頁面存放的內存塊號來組成。那以後咱們介紹了地址變換的過程,那比起分頁和分段的地址變換過程來講,段頁式管理須要先查詢段表,以後還須要再查詢頁表,而且在找到段表項以後還須要對頁表長度還有頁號進行一個對比檢查,看看頁號是否已經越界。那同窗們須要理解這個過程,可以本身寫出來它的地址變換過程究竟是什麼樣的。那最後咱們還分析了段頁式管理當中訪問一個邏輯地址所須要的訪存次數。第一次訪存是須要查段表,第二次訪存是查頁表,第三次訪存纔是訪問目標內存單元。那若是咱們引入了快表機構以後,就能夠以段號還有頁號做爲關鍵字去查詢快表,若是快表命中的話,那麼僅須要一次訪存。
請求分頁管理方式相關的一系列知識點。
請求分頁管理方式是在基本分頁管理方式的基礎上進行拓展從而實現的一種虛擬內存管理技術。那相比於基本分頁存儲管理,操做系統還須要新增兩個最主要的功能。
第一個功能就是請求調頁的功能。系統須要判斷一個頁面是否已經調入內存,若是說尚未調入內存,也就是頁面缺失的話,那麼還須要將頁面從外存調到內存當中,那這是請求調頁功能。
第二個須要提供的功能是頁面置換功能。就是當內存暫時不夠用的時候,須要決定把哪些頁面換出到外存。
那針對於這兩個功能如何實現,咱們會介紹在請求分頁管理方式當中頁表機制與基本分頁存儲管理方式當中有哪些相同和不一樣的地方。另外,爲了實現請求調頁的功能,那請求分頁管理系統當中引入了缺頁中斷機構,
最後咱們會介紹在這種管理方式當中,地址變換究竟是什麼樣一個過程。那在學習這個小節的時候,須要注意和基本分頁存儲管理方式進行一個對比。那首先咱們來看一下這種管理方式和基本分頁管理方式的頁表機制有哪些相同和不一樣的地方。那咱們仍是從如何實現頁面置換和請求調頁這兩個功能的角度出發,來分析頁表機制應該怎麼設計。
因此爲了知道這些信息,那麼確定須要把這些信息記錄在某種數據結構當中。那頁表其實就是一個很好的地方。
另外,爲了實現頁面置換功能,那麼操做系統確定須要經過某種規則來決定究竟是把哪一個頁面換出到外存,因此咱們須要記錄每一個頁面的一些指標,而後操做系統根據這個指標來決定到底換出哪一頁。另外呢,若是說一個頁面在內存當中沒有被修改過,那麼這個頁面其實換出外存的時候不用浪費時間再寫回外存。由於它沒有修改過,因此外存當中保存的那個副本其實和內存當中的這個數據是如出一轍的。那只有頁面修改過的時候才須要把它換到外存當中,把之前舊的那個數據覆蓋。因此操做系統也須要記錄各個頁面是否被修改這樣的信息。
所以,相比於基本分頁的頁表來講,請求分頁存儲管理的頁表增長了這樣的四個字段,
第一個是狀態位,狀態位就是用於表示此時這個頁面究竟是不是已經調入內存了。好比說在這個表當中,0號頁面的狀態位是0,表示0號頁面暫時尚未調入內存,那1號頁面的狀態位是1,表示1號頁面此時已經在內存當中了。
第二個新增的數據是訪問字段。操做系統在置換頁面的時候,能夠根據訪問字段的這些數據來決定到底要換出哪個頁面。因此咱們能夠在訪問字段當中記錄每一個頁面最近被訪問過幾回,咱們能夠選擇把訪問次數更少的那些頁面換出外存。或者咱們也能夠在訪問字段當中記錄咱們上一次訪問這個頁面的時間,那這樣的話咱們能夠實現優先地換出好久沒有使用的頁面這樣的事情。因此這是訪問字段的功能。
那第三個新增的數據是修改位,就是用來標記這個頁面在調入內存以後是否被修改過。由於沒有被修改過的頁面是不須要再寫回外存的。那不寫回外存的話就能夠節省時間。
第四個須要增長的數據就是各個頁面在外存當中存放的位置。
那這是請求分頁存儲管理方式當中的頁表新增的四個字段。
那在有的地方也會把這個頁表稱爲請求頁表,而後這個頁表稱爲基本頁表或者簡稱頁表。那這是請求分頁存儲管理當中頁表機制產生的一些變化,新增的一些東西。那這也是實現請求調頁和頁面置換的一個數據結構的基礎。
那爲了實現請求調頁功能,系統當中須要引入缺頁中斷機構。咱們直接來結合一個例子來理解缺頁中斷機構工做的一個流程。假設在一個請求分頁的系統當中,要訪問一個邏輯地址,頁號爲0,頁內偏移量爲1024。那麼爲了訪問這個邏輯地址,須要查詢頁表。那缺頁中斷機構,會根據對應的頁表項來判斷此時這個頁面是否已經在內存當中。若是說沒有在內存當中,也就是這個狀態位爲0的話,那麼會產生一個缺頁中斷信號,以後操做系統的缺頁中斷處理程序會負責處理這個中斷。那因爲中斷處理的過程須要I/O操做,把頁面從外存調入內存,因此在等待I/O操做完成的這個過程中,以前發生缺頁的這個進程應該被阻塞,放入到阻塞隊列當中。只有調頁的事情完成以後,才把它再喚醒,從新放回就緒隊列。
那經過這個頁表項就能夠知道這個頁面在外存當中存放在什麼地方。
那若是說此時的內存當中有空閒的塊,好比說a號塊空閒的話,那就能夠把這個空閒塊分配給此時缺頁的這個進程,再把目標頁面從外存放到內存當中。
那相應的也須要修改頁表項當中對應的一些數據,那這是第一種狀況,就是有空閒的內存塊的狀況。
第二種狀況,若是說此時內存中沒有空閒塊的話,那麼須要由頁面置換算法經過某種規則來選擇要淘汰一個頁面。
好比說頁面置換算法選中了要淘汰2號頁面,那因爲2號頁面的內容是被修改過的,因此2號頁面的內容須要從內存再寫回外存,把外存當中的那個舊數據給覆蓋掉。那這樣的話,2號頁面之前佔有的c號塊就能夠空出來讓0號頁面使用了。
因而,能夠把0號頁面從外存調入內存當中的c號塊。
那相應的,咱們也須要把換出外存的頁面還有換入外存的頁面相應的那些數據給更改,那這是第二種狀況。就是內存當中沒有空閒塊的時候,須要用頁面置換算法淘汰一個頁面。
那缺頁中斷的發生確定和當前執行的指令是有關的。因爲這個指令想要訪問某一個邏輯地址,而系統又發現這個邏輯地址對應的頁面尚未調入內存,所以才發生了缺頁中斷。那因爲它和當前執行的指令有關,所以缺頁中斷是屬於內中斷的。
還記得我們以前講的內中斷和外中斷的分類嗎?內中斷能夠分爲陷阱、故障還有終止這樣三種類型。其中故障這種內中斷類型是指有可能被故障處理程序修復的,好比說缺頁中斷這種異常的狀況是有可能被操做系統修復的,所以它是屬於故障這種分類。
另外呢咱們須要注意的是,一條指令在執行的過程中,有可能會產生屢次缺頁中斷。由於一條指令當中可能會訪問多個內存單元,好比說把邏輯地址A當中的數據複製到邏輯地址B當中。那若是說這兩個邏輯地址屬於不一樣的頁面,而且這兩個頁面都沒有調入內存的話,那麼在執行這一條指令的時候就有可能會產生兩次中斷。那經過以前的講解咱們會發現,引入了缺頁中斷機構以後,系統才能實現請求調頁這樣的事情。
那接下來咱們再來看一下請求分頁存儲管理與基本分頁存儲管理相比,在地址變換的時候須要再多作一些什麼事情。
第一個事情,在查找到頁面對應的頁表項的時候,必定是須要對這個頁面是否在內存這個狀態進行一個判斷。
第二個事情,在地址變換的過程中,若是說咱們發現此時想要訪問的頁面暫時沒有調入內存,可是此時內存當中又沒有空閒的內存塊的時候,那麼在這個地址變換的過程中,也須要進行頁面置換的工做,換出某一些頁面來騰出內存空間。
第三個與基本分頁存儲管理不一樣的就是,當頁面調入或者調出,或者頁面被訪問的時候,須要對與它對應的這些頁表項進行一個數據的修改。因此咱們在理解和記憶請求分頁存儲管理當中地址變換過程的時候,須要重點關注這三件事情須要在何時進行。
那與基本分頁存儲管理相同,請求分頁存儲管理在邏輯地址變換爲物理地址的過程中,須要作的第一件事情一樣是檢查頁號的合法性,看一下頁號是否越界。那若是頁號沒有越界的話,就會查詢此時在快表當中有沒有這個頁號對應的頁表項,那若是快表命中,就能夠直接獲得最終的物理地址。若是快表沒有命中的話,就須要查詢內存當中的慢表。
那在找到對應的頁表項以後,須要檢查此時這個頁面是否已經在內存當中。若是說這個頁面此時沒有在內存當中的話,那缺頁中斷機構會產生一個缺頁中斷的信號,以後就會由操做系統的缺頁中斷處理程序進行處理包括請求調頁還有頁面置換那一系列的事情。那固然,當頁面調入以後也須要修改這個頁表項對應的一些數據。
那這個地方注意一個細節。在請求分頁管理方式當中,若是說可以在快表當中找到某一個頁面對應的頁表項。那麼就說明這個頁面此時確定是在內存當中的,若是一個頁面被換出了外存的話,那麼快表項當中對應的這些頁表項也應該被刪除。因此只要快表命中,那麼就能夠直接根據這個內存塊號還有頁內偏移量獲得最終的物理地址了,這個頁面確定是在內存當中的。那這個地方並無像基本分頁管理方式當中那樣,一步一步很仔細地分析。那其實你們只須要關注請求分頁管理方式與基本分頁管理方式相比,不一樣的這些步驟就能夠了。
那其實課本當中給出了一個很完整的請求分頁管理方式當中,地址變換的一個流程圖。你們須要重點關注的是這兩個紅框部分當中的內容。這些內容就是請求分頁管理方式與基本分頁管理方式相比增長的一些步驟和內容。
那這兒根據這個圖補充幾個你們可能注意不到的細節。第一個地方,經過這個圖,特別是這個步驟,你們有可能會發現,彷佛只要訪問了某一個頁面,那麼這個頁面相關的修改位是否是就須要修改呢?其實並非。只有執行寫指令的時候,纔會改變這個頁面的內容。若是說執行的是讀指令,那麼就其實不須要修改這個頁面對應的修改位。而且通常來講,在訪問了某一個頁面以後,只須要把這個頁面在快表當中對應的表項的那些數據修改了就能夠了。那只有它所對應的那些表項要從快表當中刪除的時候,才須要把這些數據從快表再複製回慢表當中。那採起這樣的策略的話就能夠減小訪問內存當中慢表的次數,能夠提高系統的性能。
第二個須要注意的地方是,在產生了缺頁中斷以後,缺頁中斷處理程序也會保存CPU現場。那這個地方其實和普通的中斷處理是同樣的。在中斷處理的時候,須要保存CPU的現場,而後讓這個進程暫時進入阻塞態。那只有這個進程再從新回處理機運行的時候,才須要再恢復它的CPU現場。
第三個須要注意的地方是,內存滿的時候須要選擇一個頁面換出。那到底換出哪一個頁面,這是頁面置換算法要解決的問題,也是我們下個小節當中會詳細介紹的內容。
第四個須要注意的點是,若是咱們要把頁面寫回外存,或者要把頁面從外存調入內存的話,那麼須要啓動I/O硬件。因此其實把頁面換入換出的工做都是須要進行慢速的I/O操做的。所以,若是換入換出操做太頻繁的話,那系統會有不少的時間是在等待慢速的I/O操做完成的。所以頁面的換入換出不該該太頻繁。
第五個須要注意的地方。當咱們把一個頁面從外存調入內存以後,須要修改內存當中的頁表,可是其實咱們同時也須要把這個頁表項複製到快表當中。
因此因爲新調入的頁面在快表當中是有對應的頁表項的,所以在訪問一個邏輯地址的時候,若是發生了缺頁,那麼地址變換變換的步驟應該是這樣的:第一步首先是查詢快表,若是快表沒有命中的話,纔會查詢內存當中的慢表。而後經過慢表會發現此時頁面並無調入內存當中。以後系統會進行調頁相關的操做,那在頁面調入以後,不只要修改內存當中的慢表,也須要把這個頁表項同時加入到快表當中。因而以後能夠直接從快表當中獲得這個頁面存放的位置,而不須要再查詢慢表。
那這是地址變換過程中你們須要注意的幾個點。那其餘的流程其實並不難理解,右半部分的這些流程其實和基本分頁存儲管理方式進行地址變換的這個過程是大同小異的,只不過是增長了修改這個頁表項相應的內容這樣一個步驟。而後左半部分是新增的一系列處理,那要作的無非也就是兩件事。第一件事就是當咱們發現所要訪問的頁面沒有在內存當中的時候,須要把頁面從外存調入內存。那若是說內存此時已經滿了,那須要作頁面置換的工做。那當調頁還有頁面置換這些工做完成以後,也須要對頁表還有快表當中的對應的一些數據進行修改。因此其實只要理解了咱們應該在何時請求調頁,又應該在何時進行頁面置換,當調頁和頁面置換完成以後,又須要對哪些數據結構進行修改。只要知道這三個事情,那就能夠掌握請求分頁管理方式地址變換的這些精髓了。
與基本分頁管理方式相比,請求分頁管理方式在頁表當中增長了狀態位、訪問字段、修改位還有外存地址這樣幾個數據。那你們須要理解這幾個數據分別有什麼做用。那以後咱們介紹了缺頁中斷機構,那在引入了缺頁中斷機構以後,若是一個頁面暫時沒有調入內存,那就會產生一個缺頁中斷信號,而後以後系統會對這個缺頁中斷進行一系列的處理。另外呢,你們須要注意的是缺頁中斷它是一個內中斷,它和當前執行的指令有關。而且一條指令在執行的過程中有可能會訪問到多個內存單元,而這些內存單元有多是在不一樣的頁面當中的,所以一條指令執行的過程中有可能會產生屢次缺頁中斷。那最後咱們介紹了請求分頁管理方式的地址變換機構,其實咱們只須要重點關注與基本分頁方式不一樣的那些地方。第一,在找到頁表項的時候須要檢查頁面是否在內存當中,由此來判斷此時是否是須要請求調頁。那在調頁的過程中若是發現此時內存當中已經沒有空閒塊,那咱們還須要進行換出頁面的操做。另外,在調入和換出了一些頁面以後,咱們也須要修改與這些頁面對應的那些頁表項。那除了這些步驟之外,其餘的其實和基本分頁存儲管理當中地址變換的過程並無太大的區別。那這個小節的內容在於理解,不須要死記硬背。你們還須要經過課後習題進行進一步的鞏固和理解。
在這個小節中咱們會學習請求分頁存儲管理當中很重要的一個知識點考點————頁面置換算法。
那麼經過以前的學習咱們知道,在請求分頁存儲管理當中,若是說內存空間不夠的話,那麼操做系統會負責把內存當中暫時用不到的那些信息先換出外存。那頁面置換算法其實就是用於選擇到底要把哪一個頁面換出外存。
那經過以前的學習咱們知道,頁面的換入換出實際上是須要啓動磁盤的I/O的,所以它是會形成比較大的時間開銷。因此一個好的頁面置換算法應該儘量地追求更少的缺頁率,也就是讓換入換出的次數儘量地少。那這個小節中,咱們會介紹考試中要求咱們掌握的五種頁面置換算法,分別是最佳置換、先進先出、最近最久未使用還有時鐘置換、改進型的時鐘置換這樣五種。那除了注意它們的中文名字以外,你們注意也須要可以區分它們的英文縮寫到底分別是什麼。
那咱們按從上至下的順序依次介紹。首先來看什麼是最佳置換算法,其實最佳置換算法的思想很簡單。因爲置換算法須要追求儘量少的缺頁率,那爲了追求最低的缺頁率,最佳置換算法在每次淘汰頁面的時候選擇的都是那些之後永遠不會被使用到的頁面,或者在以後最長的時間內不可能再被訪問的頁面。
那根據最佳置換算法的規則,咱們要選擇的是在從此最長時間內不會被使用到的頁面,因此其實咱們在手動作題的時候,能夠看一下它的這個序列。
咱們從當前訪問的這個頁號開始日後尋找,看一下此時在內存當中的0、一、7這三個頁面出現的順序究竟是什麼。那最後一個出現的序號確定就是在以後最長時間內不會再被訪問的頁面。
因此從這兒日後看,0號頁面是最早出現的,
而後一直到這個位置咱們發現1號頁面也開始出現了。因此0、一、7這三個頁面當中0號和1號會在以後依次被使用,可是7號頁面是在以後最長的時間內不會再被訪問到的頁面。所以咱們會選擇淘汰7號頁面,而後讓2號頁面放入到7號頁面原先佔有的內存塊也就是內存塊1當中,所以2號頁面是放在這個位置的。
那接下來要訪問的0號頁面已經在內存當中了,因此此時不會發生缺頁,能夠正常地訪問。
再以後訪問3號頁面,也會發現,此時3號頁面並無在內存當中,因此咱們依然須要用這個置換算法選擇淘汰一個頁面。
那和剛纔同樣,咱們從這個位置開始日後尋找,看一下此時內存當中存放的二、0、1這三個頁面出現的前後順序。那麼咱們會發現,二、0、1當中,那麼1號頁面就是最後一個出現的,所以1號頁面是在從此最長時間內不會再被訪問的頁面,因此咱們會選擇把二、0、1這三個頁面當中的1號頁面給淘汰,先換出外存,而後3號頁面再換入1號頁面之前佔有的那個內存塊,也就是內存塊3當中,因此3號頁面是放在這個地方的。那對於以後的這些頁面序號的訪問咱們就再也不細細地分析了,你們能夠本身嘗試着去完善一下這個表。
那最終咱們會發現整個訪問這些頁面的過程中,缺頁中斷髮生了9次,也就是這兒打勾的這些位置發生缺頁中斷,可是頁面置換隻發生了6次。因此你們必定須要注意,缺頁中斷以後未必發生頁面置換。只有內存塊已經都滿了的時候才發生頁面置換。所以剛開始訪問七、0、1這三個頁面的時候,雖然它們都沒有在內存當中,可是因爲剛開始內置有空閒的內存塊,雖然發生了缺頁中斷,雖然會發生調頁,可是並不會發生頁面置換這件事情。那只有全部的內存塊都已經佔滿了以後,再發生缺頁的話那才須要進行頁面置換這件事情。所以缺頁中斷總共發生了9次,但頁面置換隻發生了6次,前面的3次只是發生了缺頁,可是並無頁面置換。那缺頁率的計算也很簡單,咱們只須要把缺頁中斷次數再除以咱們總共訪問了多少次的頁面就能夠獲得缺頁率是45%。那這是最佳置換算法。
那其實頁面置換執行的前提條件是咱們必需要知道以後會依次訪問的頁面序列究竟是哪些。
不過在實際應用當中,只有在進程執行的過程中,才能一步一步地知道接下來會訪問到的究竟是哪個頁面。因此操做系統其實根本不可能提早預判各個頁面的訪問序列,因此最佳置換算法它只是一種理想化的算法,在實際應用當中是沒法實現的。
那接下來咱們再來看第二種————先進先出置換算法。這種算法的思想很簡單,每次選擇淘汰的頁面,是最先進入內存的頁面。因此在具體實現的時候,能夠把調入內存的這些頁面根據調入的前後順序來排成一個隊列,當系統發現須要換出一個頁面的時候,只須要把隊頭的那個頁面淘汰就能夠了。那須要注意的是,這個隊列有一個最大長度的限制,那這個最大長度取決於系統爲進程分配了多少個內存塊。
那咱們仍是來看一個例子。
爲一個進程分配的內存塊越多,那這個進程的缺頁次數應該越少纔對啊。因此像這個地方咱們發現的這種現象,就是爲進程分配物理塊增大的時候,缺頁次數不增反減的這種現象就稱做爲Belady異常。
那在咱們要學習的全部的這些算法當中,只有先進先出算法會產生這種Belady異常。因此雖然先進先出算法實現起來很簡單,可是先進先出的這種規則其實並無考慮到進程實際運行時候的一些規律。由於先進入內存的頁面其實在以後也有可能會被常常訪問到,因此只是簡單粗暴地讓先進入的頁面淘汰的話,那顯然這是不太科學的,因此先進先出置換算法的算法性能是不好的。
那接下來咱們再來看第三個————最近最久未使用置換算法,英文縮寫是LRU(least recently used),你們也須要記住它的這個英文縮寫。不少題目出題的時候就直接用這個LRU來表示置換算法。那這個算法的規則就像它的名字同樣,就是要選擇淘汰最近最久沒有使用的頁面。
因此爲了實現這件事,咱們能夠在每一個頁面的頁表項當中的訪問字段這兒,記錄這個頁面自從上一次被訪問開始,到如今爲止所經歷的時間t。那咱們須要淘汰一個頁面的時候,只須要選擇這個t值最大的,也就是最久沒有被訪問到的那個頁面進行淘汰就能夠了。那咱們依然仍是結合一個例子。若是一個系統爲進程分配了四個內存塊,而後有這樣的一系列的內存訪問序列。
那首先要訪問的是1號頁。此時有內存塊空閒,因此1號頁放到內存塊1當中。
而後第二個訪問8號頁面,也能夠直接放到空閒的內存塊2當中。
那一直到後面訪問到這個3號頁面的時候,因爲此時給這個進程分配的4個內存塊都已經用完了,因此必須選擇淘汰其中的某個頁面。那若是咱們在手動作題的時候,能夠從這個頁號開始逆向地往前檢查此時在內存當中擁有的一、八、七、2這幾個頁號從逆向掃描來看,出現的前後順序是什麼樣的。那最後一個出現的頁號,那確定就是最近最久沒有使用的頁面了。
那一樣的,在以後的這一系列訪問當中都不會發生缺頁,一直到訪問到7號頁的時候,又發生了一次缺頁,而且須要選擇淘汰一個頁面。
那和以前同樣,咱們從這個地方開始逆向地往前檢查,看一下這幾個頁號出現的順序分別是什麼樣的。
那最近最久未使用置換算法在實際的應用當中實際上是須要專門的硬件的支持的,因此這個算法雖然性能很好,可是實現起來會很困難,而且開銷很大。那在咱們學習的這幾個算法當中,最近最久未使用這個算法的性能是最接近最佳置換算法的。
那接下來咱們再來看第四種————時鐘置換算法。以前咱們學到的這幾種算法當中,最佳置換算法性能是最好的,可是沒法實現。先進先出算法雖然實現簡單,可是算法的性能不好,而且也會出現Belady異常。那最近最久未使用置換算法雖然性能也很好,可是實現起來開銷會比較大。因此以前提到的那些算法都不能作到算法效果還有實際開銷的一個平衡,所以人們就提出了時鐘置換算法。它是一種性能和開銷比較均衡的算法,又稱爲CLOCK算法,或者叫最近未用算法(NRU,Not Recently Used),英文縮寫是NRU。
那在考試中咱們須要掌握兩種時鐘置換算法,分別是簡單的時鐘置換算法還有改進型的時鐘置換算法。那咱們先來看簡單的這種算法。首先咱們要爲每一個頁面設置一個訪問位,訪問位爲1的時候就表示這個頁面最近被訪問過,訪問位爲0的時候表示這個頁面最近沒有被訪問過。所以若是說訪問了某個頁面的話,那須要把這個頁面的訪問位變爲1。那內存中的這些頁面須要經過連接指針的方式把它們連接成一個循環隊列。那當須要淘汰某個頁面的時候,須要掃描這個循環隊列,找到一個最近沒有被訪問過的頁面,也就是訪問位爲0的頁面。可是在掃描的過程當中,須要把訪問位爲1的這些頁面的訪問位再從新置爲0,因此這個算法有可能會通過兩輪的掃描。若是說全部的頁面訪問位都是1的話,那第一輪掃描這個循環隊列就並不會找到任何一個訪問位爲0的頁面。只不過在第一輪掃描當中,會把全部的頁面的訪問位都置爲0,因此第二輪掃描的時候就確定能夠找到一個訪問位爲0的頁面。因此這個算法在淘汰一個頁面的時候最多會經歷兩輪的掃描。
那光看這個文字的描述其實還很抽象的,咱們直接來看一個例子。那剛開始因爲有5個空閒的內存塊,因此前五個訪問的這個頁號一、三、四、二、5均可以順利地放入內存當中。只有在訪問到6號頁的時候,才須要考慮淘汰某個頁面。
那麼在內存當中的一、三、四、二、5這幾個頁面會經過連接指針的方式連接成一個這樣的循環隊列。
時鐘置換算法的一個運行的過程,而且經過剛纔的這個例子你們會發現,這個掃描的過程有點像一個時鐘的那個時針在不斷地轉圈的一個過程。因此爲何這個算法要叫作時鐘置換算法,它實際上是一個很形象的比喻。那其實通過剛纔的分析,咱們也很容易理解,爲何它還稱做最近未用算法。由於咱們會爲各個頁面設置一個訪問位,訪問位爲1的時候表示最近用過,訪問位爲0的時候表示最近沒有用過。可是咱們在選擇淘汰一個頁面的時候,是選擇那種最近沒有被訪問過也就是訪問位爲0的頁面,所以這種算法也能夠稱做爲最近未用算法。
那接下來咱們再來學習改進型的時鐘置換算法。其實在以前學習的這個簡單的時鐘置換算法當中,只是很簡單地考慮到了一個頁面最近是否被訪問過。但經過以前的講解咱們知道,若是一個被淘汰的頁面沒有被修改過的話,那麼是不須要執行I/O操做而把它寫回外存的。因此若是說咱們可以優先淘汰沒有被修改過的頁面的話,那麼實際上就能夠減小這些I/O操做的次數,
從而讓這個置換算法的性能獲得進一步的提高。那這就是改進型的時鐘置換算法的一個思想。
因此爲了實現這件事,咱們還須要爲各個頁面增長一個修改位,修改位爲0的時候表示這個頁面在內存當中沒有被修改過,那修改位爲1的時候表示頁面被修改過。那咱們在接下來的討論當中,會用訪問位、修改位這樣二元組的形式來標識各個頁面的狀態。好比說訪問位爲一、修改位也爲1的話那就表示這個頁面近期被訪問過,而且也曾經被修改過。
那和簡單的時鐘置換算法同樣,咱們也須要把全部的可能被置換的頁面排成一個循環隊列。
在第一輪掃描的時候,會從當前位置開始日後依次掃描,嘗試找到第一個最近沒有被訪問過而且也沒有修改過的頁面,對它進行淘汰,那第一輪掃描是不修改任何的標誌位的。那若是第一輪掃描沒有找到(0,0)這樣的頁面的話,就須要進行第二輪的掃描。第二輪的掃描會嘗試找到第一個最近沒有被訪問過可是被修改過的這個頁面進行替換。而且被掃描過的那些頁面的訪問位,都會被設置爲0。那若是第二輪掃描失敗,須要進行第三輪掃描。第三輪掃描會嘗試找到第一個訪問位和修改位都爲0的這個頁面,進行淘汰。而且第三輪掃描並不會修改任何的標誌位。
那若是第三輪掃描失敗的話,還須要進行第四輪掃描。找到第一個(0,1)的頁幀用於替換。那因爲第二輪的掃描已經把全部的訪問位都設爲了0了,因此通過第三輪、第四輪的掃描以後,確定是能夠找到一個要被淘汰的頁面的。因此改進型的這種時鐘置換算法,選擇一個淘汰頁面,最多會進行四輪掃描。
那其實這個過程光看文字描述也是很抽象的,不太容易理解。假設系統爲一個進程分配了5個內存塊,那當這個內存塊被佔滿以後,各個頁面會用這種連接的方式連成一個循環的隊列。
那此時若是要淘汰一個頁面的話,須要從這個隊列的隊頭開始依次地掃描。
改進型的時鐘置換算法在選擇一個淘汰頁面的時候最多會進行四輪掃描,而簡單的時鐘置換算法在淘汰一個頁面的時候最多隻會進行兩輪掃描。
這個小節咱們介紹了五種頁面置換算法,分別是最佳置換OPT、先進先出FIFO、最近最久未使用LRU、簡單型的時鐘置換(最近未用)NRU、改進型的時鐘置換(最近未用)NRU。那這個小節的內容重點須要理解各個算法的算法規則,若是題目中給出一個頁面的訪問序列,那須要本身可以用手動的方式來模擬各個算法運行的一個過程。那除了算法規則以外,各個算法的優缺點有可能在選擇題當中進行考查。那須要重點注意的是,最佳置換算法在現實當中是沒法實現的,而後先進先出算法它的性能差,而且是惟一一個有可能出現Belady異常的算法。
在這個小節中咱們會學習頁面分配策略相關的一系列知識點。
什麼是駐留集。考試中須要掌握的三種頁面分配、置換的策略。另外,頁面應該在何時被調入,應該從什麼地方調入,應該調出到什麼位置,這些也是咱們以後會探討的問題。什麼是進程抖動(進程顛簸)這種現象,那爲了解決進程抖動(進程顛簸)現象,又引入了工做集這個概念,那咱們會按照從上至下的順序依次講解。
駐留集是指請求分頁存儲管理當中給進程分配的物理塊(內存塊、頁框、頁幀)的集合。那在採用了虛擬存儲技術的系統當中,爲了從邏輯上提高內存,而且提升內存的利用率,那駐留集的大小通常是要小於進程的總大小的。
駐留集過小致使進程缺頁頻繁,系統就須要花大量的時間處理缺頁,而實際用於進程推動、進程運行的時間又不多。駐留集太大,致使多道程序併發度降低,使系統的某些資源利用率不高。像系統的CPU和I/O設備這兩種資源,理論上是能夠並行地工做的。那若是多道程序併發度降低,就意味着CPU和I/O設備這兩種資源並行工做的概率就會小不少,因此資源的利用率固然就會下降。因此係統應該爲進程選擇一個合適的駐留集大小。
那針對於駐留集的大小是否可變這個問題,人們提出了固定分配和可變分配這兩種分配策略。固定分配指駐留集的大小是剛開始就決定了,以後就再也不改變了。可變分配其實指的就是駐留集的大小是能夠動態地改變,能夠調整的。
另外,當頁面置換的時候,置換的範圍應該是什麼?根據這個問題,人們又提出了局部置換和全局置換這兩種置換範圍的策略。因此局部置換和全局置換的區別就在於,當某個進程發生缺頁,而且須要置換出某個頁面的時候,那這個置換出的頁面是否是隻能是本身了。
那把這兩種分配和置換的策略兩兩結合,能夠獲得這樣的三種分配和置換的策略。分別是固定分配-局部置換,可變分配-局部置換和可變分配-全局置換。
那你們會發現,其實並不存在固定分配-全局置換這種策略。由於從全局置換的這個規則咱們也能夠知道,若是使用的是全局置換的話,就意味着一個進程所擁有的物理塊是必然會改變的。而固定分配又規定着進程的駐留集大小不變,也就是進程所擁有的物理塊數是不能改變的。因此固定分配-全局置換這種分配置換策略是不存在的,那接下來咱們依次介紹存在的這三種分配和置換的策略。
不過在實際應用中,通常若是說採用這種固定分配-局部置換策略的系統,它會根據進程大小、進程優先級或者是程序員提出的一些參數來肯定到底要給每一個進程分配多少個物理塊,不過這個數量通常來講是不太合理的。那由於駐留集的大小不可變,因此固定分配局部置換這種策略的靈活性相對來講是比較低的。
那第二種叫作可變分配全局置換。由於是可變分配,因此說系統剛開始會爲進程分配必定數量的物理塊,可是以後在進程運行期間,這個物理塊的數量是能夠改變的,那操做系統會保持一個空閒物理塊的隊列。若是說一個進程發生缺頁的時候,就會從這個空閒物理塊當中取出一個分配給進程。那若是說此時這個空閒物理塊都已經用完了,那就能夠選擇一個系統當中未鎖定的頁面換出外存,再把這個物理塊分配給缺頁的這個進程。
那這個地方所謂的未鎖定的頁面指的是什麼呢?其實系統會鎖定一些很重要的就是不容許被換出外存、須要常駐內存的頁面,好比說系統當中的某些很重要的內核數據,就有多是被鎖定的。那另一些能夠被置換出外存的頁面,就是所謂的「未鎖定」的頁面。固然這個地方只是作一個拓展,在考試當中應該不會考查。
那經過剛纔對這個策略的描述你們也會發現,在這種策略當中,只要進程發生缺頁的話,那它一定會得到一個新的物理塊。若是說空閒物理塊沒有用完,那這個新的物理塊就會從空閒物理塊隊列當中選擇一個給它分配。那若是說空閒物理塊用完了,系統纔會選擇一個未鎖定的頁面換出外存,但這個未鎖定的頁面有多是任何一個進程的頁面。因此這個進程的頁面被換出的話,那麼它所擁有的物理塊就會減小,它的缺頁率就會有所增長。那顯然,只要進程發生了缺頁,就給它分配一個新的物理塊,這種方式其實也是不太合理的。
因此以後人們又提出了可變分配局部置換的策略。那在剛開始會給進程分配必定數量的物理塊,由於是可變分配,因此以後這個物理塊的數量也是會改變的。那因爲是局部置換,因此當進程發生缺頁的時候,只容許這個進程從本身的物理塊當中選出一個進行換出。那若是說操做系統在進程運行的過程當中發現它頻繁地缺頁,那就會給這個進程多分配幾個物理塊,直到這個進程的缺頁率回到一個適當的程度。那相反的,若是一個進程在運行當中缺頁率特別低的話,那系統會適當地減小給這個進程所分配的物理塊。那這樣的話,就可讓系統的多道程序併發度也保持在一個相對理想的位置。
那這三種策略當中,最難分辨的是可變分配全局置換和可變分配局部置換。你們須要抓住它們倆最大的一個區別,若是採用的是全局置換策略的話,那麼只要缺頁就會給這個進程分配新的物理塊。那若是說採用的是這種局部置換的策略的話,系統會根據缺頁的頻率來動態地增長或者減小一個進程所擁有的物理塊。那這是三種咱們須要掌握的頁面分配策略,有可能在選擇題當中進行考查。
那接下來咱們再來討論下一個問題。咱們應該在何時調入所須要的頁面呢?那通常來講有這樣的兩種策略。第一種叫作預調頁策略。
根據咱們以前學習的局部性原理,特別是空間局部性的原理。咱們知道,若是說當前訪問了某一個內存單元的話,那麼頗有可能在以後不久的未來會接着訪問與這個內存單元相鄰的那些內存單元。因此根據這個思想咱們天然而然的也會想到,若是說咱們訪問了某一個頁面的話,那麼是否是在不久的以後就也有可能會訪問與它相鄰的那些頁面呢?所以,基於這個方面的考慮,若是咱們可以一次調入若干個相鄰的頁面,那麼可能會比一次調入一個頁面會更加高效。由於咱們一次調入一堆頁面的話,那麼咱們啓動磁盤I/O的次數確定就會少不少,這樣的話就能夠提高調頁的效率。不過另外一個方面,若是說咱們提早調入的這些頁面在以後沒有被訪問過的話,那麼這個預調頁就是一種很低效的行爲。因此咱們能夠用某種方法預測不久以後可能會訪問到的頁面,將這些頁面預先地調入內存。固然目前預測的成功率不高,只有50%左右。因此在實際應用當中,預調頁策略主要是用於進程首次調入的時候,由程序員指出哪些部分應該是先調入內存的。
好比說我能夠告訴系統把main函數相關的那些部分先調入內存,因此預調頁策略是在進程運行前就進行調入的一種策略。
那第二種就是請求調頁策略,這也是我們以前一直在學習的請求調頁方式。只有在進程期間發現缺頁的時候,纔會把所缺的頁面調入內存。因此這種策略其實在進程運行期間才進行頁面的調入。而且被調入的頁面確定在以後是會被訪問到的。可是每次只能調入一個頁面,因此每次調頁都要啓動磁盤I/O操做,所以I/O開銷是比較大的。那在實際應用當中,預調頁策略和請求調頁策略都會結合着來使用。預調頁用於運行前的調入,而請求調頁策略是在進程運行期間使用的。那這是調入頁面的實際問題。
那接下來咱們再來看一下咱們應該從什麼地方調入頁面。以前咱們有簡單地介紹過,磁盤當中的存儲區域分爲對換區和文件區這樣兩個部分。其中對換區採用的是連續分配的方式,讀寫的速度更快,而文件區的讀寫速度是更慢的,它採用的是離散分配的方式。那什麼是離散分配什麼是連續分配,這個是我們在以後的章節會學習的內容,這個地方先不用管,有個印象就能夠。在本章中,你們只須要知道對換區的讀寫速度更快,而文件區的讀寫速度更慢就能夠了。那通常來講文件區的大小要比對換區要更大,那平時咱們指的程序在沒有運行的時候,相關的數據都是存放在文件區的。
那因爲對換區的讀寫速度更快,因此若是說系統擁有足夠的對換區空間的話,那麼頁面的調入調出都是內存與對換區之間進行的。
因此係統中若是有足夠的對換區空間,那剛開始在運行以前會把咱們的進程相關的那些數據從文件區先複製到對換區,
以後把這些須要的頁面從對換區調入內存。
那相應的,若是內存空間不夠的話,能夠把內存中的某些頁面調出到對換區當中。頁面的調入調出都是內存和對換區這個更高速的區域進行的。那這是在對換區大小足夠的狀況下使用的一種方案。
那若是說系統中缺乏足夠的對換區空間的話,凡是不會被修改的數據都會從文件區直接調入內存。那因爲這些數據是不會被修改的,因此當調出這些數據的時候並不須要從新寫回磁盤。
那若是說某些頁面被修改過的話,把它調出的時候就須要寫回到對換區,而不是寫回到文件區,由於文件區的讀寫速度更慢。
那相應的,若是以後還須要再使用到這些被修改的頁面的話,那就從對換區再換入內存。
第三種,UNIX使用的是這樣的一種方式。若是說一個頁面尚未被使用過,也就是這個頁面第一次被使用的話,
那麼它是從文件區直接調入內存。
那以後若是內存空間不夠,須要把某些頁面換出外存的話,那麼是換出到這個對換區當中。
那若是這個頁面須要再次被使用的話,就是要從對換區再換回內存。這是UNIX系統採用的一種方式。
那接下來咱們再來介紹一個很常考的一個概念,叫作抖動(或者叫顛簸)現象。那若是說發生了抖動現象的話,系統會用大量的時間來處理這個進程頁面的換入換出。而實際用於進程執行的時間就變得不多,因此咱們要儘可能避免抖動現象的發生。
那爲了防止抖動的發生,就須要爲進程分配足夠的物理塊。但若是說物理塊分配的太多的話,又會下降系統總體的併發度,下降某些資源的利用率。
因此爲了研究應該爲每一個進程分配多少個物理塊,有的科學家在196幾年提出了進程工做集的概念。
工做集和駐留集實際上是有區別的。駐留集是指在請求分頁存儲管理當中,給進程分配的內存塊的集合。
咱們直接來看一個例子。通常來講操做系統會設置一個所謂的「窗口尺寸」來算出工做集。那假設一個進程對頁面的訪問序列是這樣的一個序列。
工做集的大小可能會小於窗口的尺寸。在實際應用當中,窗口尺寸通常會設置的更大一些,好比說設置十、50、100這樣的數字。那對於一些局部性很好的進程來講,工做集的大小通常是要比窗口尺寸的大小要更小的。因此係統能夠根據檢測工做集的大小來決定到底要給這個進程分配多少個內存塊。換一個說法就是,根據工做集的大小,來肯定駐留集的大小是多少。
那通常來講,駐留集的大小不能小於工做集的大小。若是說更小的話,那就有可能會發生頻繁的缺頁,也就是發生抖動現象。
另外,在有的系統當中,也會根據工做集的概念來設計一種頁面置換算法。好比說若是說這個進程須要置換出某個頁面的話,那徹底就能夠選擇一個不在工做集當中的頁面進行淘汰。那這些知識點只是做爲一個拓展,你們只要有個印象就能夠了。
須要特別注意駐留集這個概念。在以前我們講過的那些內容當中,常常會遇到某些題目告訴咱們一個條件就是說,系統爲某個進程分配了N個物理塊,那這種說法其實也能夠改變一種等價的表述方式,就是也能夠說成是某個進程的駐留集大小是N。那若是說題目中的條件是用駐留集大小這種方式給出的話,你們也須要知道它所表述的究竟是什麼意思。那另外你們須要注意這三種分配置換策略在真題當中是進行考查過的。而且在有的大題當中有可能會告訴你們,一個進程採用固定分配局部置換的策略,那這個條件就是爲了告訴你們,系統爲一個進程分配的物理塊數是不會改變的,你們在作課後習題的時候能夠注意一下,不少大題都會給出這樣的一個條件。那咱們須要知道這個條件背後隱含的一系列的信息。那這個地方還須要注意,並不存在固定分配全局置換這種策略。由於全局置換意味着一個進程所擁有的物理塊數確定是會改變的,而固定分配又要求一個進程擁有的物理塊數是不能改變的。因此固定分配和全局置換這兩個條件自己就是相互矛盾的,所以並不存在固定分配全局置換這種方式。那以後介紹的內容,什麼時候調入頁面,應該給從何處調入頁面能有個印象就能夠了。最後你們還須要重點關注抖動(顛簸)這個現象。那產生抖動的主要緣由是分配給進程的物理塊不夠,因此若是要解決抖動問題的話,那麼確定就是用某種方法給這個進程分配更多的物理塊。那這一點在我們的課後習題當中也會遇到。那咱們還對工做集的概念作了一系列的拓展,不過通常來講工做集這個概念不太容易進行考查。可是你們須要注意的是駐留集大小通常來講不能小於工做集的大小,若是更小的話那就會產生抖動現象。那這個小節的內容通常來講只會在選擇題當中進行考查。可是在考試當中也有可能會用某些概念做爲大題當中的一個條件進行給出,因此你們還須要經過課後習題進行進一步的鞏固。