其實内存它的做用就是用來存放數據。我們的程序本來是放在外存、放在磁盤當中的,可是磁盤的讀寫速度很慢,而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。那若是说题目中的条件是用驻留集大小这种方式给出的话,你们也须要知道它所表述的究竟是什么意思。那另外你们须要注意这三种分配置换策略在真题当中是进行考查过的。而且在有的大题当中有可能会告诉你们,一个进程采用固定分配局部置换的策略,那这个条件就是为了告诉你们,系统为一个进程分配的物理块数是不会改变的,你们在作课后习题的时候能够注意一下,不少大题都会给出这样的一个条件。那咱们须要知道这个条件背后隐含的一系列的信息。那这个地方还须要注意,并不存在固定分配全局置换这种策略。由于全局置换意味着一个进程所拥有的物理块数确定是会改变的,而固定分配又要求一个进程拥有的物理块数是不能改变的。因此固定分配和全局置换这两个条件自己就是相互矛盾的,所以并不存在固定分配全局置换这种方式。那以后介绍的内容,什么时候调入页面,应该给从何处调入页面能有个印象就能够了。最后你们还须要重点关注抖动(颠簸)这个现象。那产生抖动的主要缘由是分配给进程的物理块不够,因此若是要解决抖动问题的话,那么确定就是用某种方法给这个进程分配更多的物理块。那这一点在我们的课后习题当中也会遇到。那咱们还对工做集的概念作了一系列的拓展,不过通常来讲工做集这个概念不太容易进行考查。可是你们须要注意的是驻留集大小通常来讲不能小于工做集的大小,若是更小的话那就会产生抖动现象。那这个小节的内容通常来讲只会在选择题当中进行考查。可是在考试当中也有可能会用某些概念做为大题当中的一个条件进行给出,因此你们还须要经过课后习题进行进一步的巩固。