《操作系統原理、實現與實踐》筆記


《操作系統原理、實現與實踐》筆記


目錄

第一、二章

馮諾依曼“存儲程序”思想

“存儲程序”的基本含義就是將程序存儲在內存中,其中程序是一段規定CPU如何運算的指令序列;計算機執行時,CPU會將這一段指令逐條載入到寄存器中,依據其描述完成規定的運算(計算機將這個指令序列中的指令逐條取出並解釋執行);“存儲程序”——載入程序——執行程序,是計算機的基本工作模式,即取指-執行

內核態、用戶態、系統調用

內核態是操作系統代碼執行時的狀態,用戶態是應用程序代碼執行時的狀態

操作系統中,無論是內核代碼還是應用程序代碼,都是裝入內存后執行的,因此內核態代碼和用戶態代碼在內存中放置的區域不同

所以也可以這么理解,放置內核代碼的那一段內存區域是“內核態區域”,放置用戶代碼的那段內存區域是“用戶態區域”

系統調用(fork、exec、open、read、write)就像一扇門,意義就在於讓執行在用戶態區域的代碼不能進入內核態區域,比如用戶態代碼不能通過jmp指令跳轉到內核態內存區域中,也不能通過mov訪問內核態內存中的數據

當前特權級(CPL),描述符特權級(DPL),用來檢查訪問權限,操作系統也提供了0X80號中斷讓上層應用進入到操作系統

第三章 多進程——操作系統最核心的視圖

3.1 什么是進程

在講述進程之前,首先需要了解一下CPU的工作原理:不斷地取指執行。但是遇上有IO的情況,效率會非常低下,書上講到寫一個整數的I/O所花費的時間約等於10^6條CPU計算指令的執行時間

因此引入了一種程序執行結構:並發,即多個程序同時出發,交替執行。當CPU執行到程序A的I/O操作時,CPU不是等待A的I/O完成,而是切換到程序B去執行,當程序A的I/O操作完成以后,CPU可以再切換到程序A繼續執行,這樣CPU就可以一直處於忙碌狀態

根據並發的思想,CPU要在多段程序之間來回切換,但是僅僅修改PC指針(即修改寄存器CS:EIP)是不夠的,還需要保存每個程序當前的一些信息。這就引出了進程控制塊(process control block,PCB)的概念,PCB中保存了所屬程序當前執行位置,執行現場等重要信息

進程的概念:進程用來描述一個程序及其執行過程中的信息,即描述一個執行中的程序;或者說:進程描述的是程序以及反映程序執行信息的數據結構的總和

程序是靜態的指令,數據等,而進程是執行起來的程序

3.2 多進程引起的基本問題

3.2.1 多個進程的組織與進程狀態

操作系統管理進程的關鍵就是管理進程對應的PCB數據結構,因此組織多個進程就是用合適的數據結構管理這些PCB;簡單而高效的一種方式就是將這些PCB組織成隊列

但管理進程時需要區分進程位於哪個隊列,由此引入了進程的狀態

進程的狀態:

  1. 運行態,當前占有CPU、正在執行的進程狀態
  2. 就緒態,一個進程具備了所有可以執行的條件,只要獲得CPU就可以開始執行
  3. 阻塞態,也稱睡眠態或等待態,指一個進程因為缺少某些條件,即使分配了CPU也無法執行的狀態

有了這三個狀態,就相應產生了三個PCB隊列:運行隊列、就緒隊列、阻塞隊列(阻塞等待的事件可能有多個,相應的也有多個阻塞隊列)

根據進程的狀態還可以描述一個進程在執行過程中的演化過程,這個過程也被稱為進程的生存周期

3.2.2 多個進程的切換和調度

並發需要回答兩個問題:1.什么時候切換 2.如何切換

什么時候切換

當CPU出現空閑時,進行切換;有很多原因導致CPU空閑,例如當前進程執行了需要CPU等待的指令(如啟動磁盤讀寫)、當前進程執行了exit()退出,等等;這些空閑點也被稱為調度點

如何切換

操作系統調用函數schedule()實現切換

schedule()函數的實現原理很簡單,即從就緒隊列中選出下一個進程的PCB,即pNew,然后用PCB結構pNew中存放的執行現場,去替換CPU中的寄存器,當然為了將來能切換回當前進程,切換之前還應該將CPU里面的“當前進程執行現場”保存在當前進程的PCB結構,即pCur

如何選擇新的pNew很復雜,並不是單純的隊列FIFO,這設計到調度算法

3.2.3 進程間的影響分離

進程的代碼都屬於用戶態內存區域,所有進程的CPL=DPL=3,因此可能產生問題:進程A執行一條修改內存地址100的指令,而內存地址100處存放的是進程2的數據,顯然這會導致進程2發生錯誤

解決這個問題的辦法是地址隔離:每個進程操作的地址不直接是真實的物理內存地址,而是通過一個映射表對應到一個真實物理地址;這就是需要用GDT表和頁來翻譯CS:EIP的根本原因

操作系統給每個進程分配一段只屬於該進程的、互相不重疊的內存區域、因此各個進程的地址空間完全被分離開來,每個進程都可以隨意獨寫任何地址,不用擔心因誤操作影響其他進程,也不用擔心其他進程會影響自己

地址空間隔離措施是操作系統內存管理的核心概念,操作系統內存管理部分論述的主要工作都基於這一基本概念

3.2.4 進程間的通信與合作

進程間的通信方法很多,比如讀寫同一個數據庫、讀寫同一個文件、讀寫一段共享內存,讀寫一段內核態內存等等

進程間的合作機制采用的是生產者-消費者模型,向共享緩存區中寫的進程為生產者進程,從共享緩存區中讀的進程為消費者進程,兩個進程通過共享緩存區進行通信與合作

模型代碼如下:

typedef struct {```} item;
item buffer[BUFFER_SIZE];//緩存區定義
int in = out = counter = 0;
//生產者進程producer
while (true) {
    while (counter == BUFFER_SIZE);//緩存區滿了,自旋等待
    buffer[in] = item;
    in = (in + 1) % BUFFER_SIZE;
    counter++;
}
//消費者進程consumer
while (true) {
    while (counter == 0);//緩存區沒有數據,自旋等待
    item = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    counter--;
}

問題:整個合作機制完全依靠counter,counter的語義必須時刻正確,兩個進程都要修改counter,counter能保證總是修改正確嗎?

解決辦法:counter要么全部修改完成,要么一點也不修改,這就是臨界區的概念,在進程同步一章中會講到

第四章 線程切換與調度——操作系統的發動機

線程切換是進程切換的核心內容:進程切換由資源切換和指令流切換兩部分組成,資源切換是將分配給進程的非CPU以外的資源進行切換,如對當前地址空間的切換;指令流切換是CPU切換,也就是線程切換

資源切換在內存管理,文件系統章節學習

4.1 線程與進程

4.1.1 線程概念的引入

並發是CPU高效工作的基礎,並發的基本含義就是多段程序交替執行;交替執行不一定總存在於兩段“很遠”的代碼之間,例如瀏覽器程序和編譯器程序之間的交替,即使是在同一個可執行文件內,兩個函數之間也可以交替,如在同一個瀏覽器內的函數也可以交替執行,這樣的交替執行就產生了線程的概念

4.1.2 一個多線程實例

一個多線程瀏覽器,內部有GetData、ShowText、ProcessImage、ShowImage四個函數,打開一個網頁時,獲取數據的線程(GetData)被調度開始工作,將網頁的總體布局以及頁面中的文本信息下載下來;然后切換到顯示文本的線程(ShowText)將網頁的基本結構和其中的文本信息顯示在瀏覽器中;接下來再切換到解壓圖片的線程(ProcessImage)執行,解碼完成后再切換到渲染圖片的線程(ShowImage)執行

如果不用線程,只用一個進程來完成上述工作,執行的代碼首先將頁面布局、文本信息、圖片對象等內容全部下載下來,再逐個解碼渲染,最后在將所有信息全部整理好輸出到屏幕上,用戶會等待一段時間,然后看到完整的頁面,體驗不好

為什么說上述四個函數是四個線程:因為此時不需要地址隔離策略將兩個內存緩存區分離在兩個不同的進程中,因此這四個函數是四個並發的指令執行序列,並使用共同地址空間等進程資源

4.1.3 線程與進程

線程 進程 描述
能否並發
切換內容 指令流 指令流+其他資源
切換代價 線程切換時一些資源不需要切換,例如內存映射表
創建速度(資源消耗) 快(小) 慢(大) 創建線程時直接使用進程共有的資源部分
相互影響 操作同一內存 完全分離 多個線程(同進程)使用相同的的地址空間,即同一個映射表
安全性 一個線程中的代碼可以訪問同一進程下其他線程的任意內存位置
隸屬關系 隸屬於一個進程 隸屬於操作系統 線程不能脫離進程存在,沒有進程就不能創建線程

4.2 用戶級線程的切換與創建

由用戶程序自己管理的線程對操作系統透明,操作系統完全不知道這些線程的存在,稱之為用戶級線程;由操作系統管理的線程是內核級線程

4.2.1 用戶級線程的切換

用戶級線程通過Yield()函數切換,Yield()函數也是一個普通的用戶態函數,由用戶自己編寫

  1. 用戶級線程的切換就是在切換位置上調用Yield()函數
  2. Yield()函數的工作:找到下一個線程的TCB,然后根據當前線程的TCB和下一個線程的TCB完成用戶棧的切換
  3. 切換到新棧以后用Yield()函數中的"}"將PC指針切換到下一個線程要執行的指令處
  4. 線程切換時保存和恢復一些執行現場,無非就是保存一些通用寄存器,這些寄存器的值也要放在線程各自的棧中來保存,在棧切換完成后彈棧恢復下一個線程的執行現場

總的來說,切換就是:TCB切換,根據TCB中存儲的棧指針完成用戶棧切換、根據用戶棧中壓入函數返回地址完成PC指針切換

4.3 內核級線程

4.3.1內核級線程的引出

用戶級線程是完全在用戶態內存中創建的一個指令執行序列,即用戶級線程的TCB、棧等內容都是創建在用戶態中的,操作系統完全不知道;內核級線程就是要讓內核態內存和用戶態內存合作創建一個指令執行序列,內核級線程的TCB等信息是創建在操作系統內核中的,操作系統通過這些數據結構可以感知和操縱內核線程

內核級線程優點:提高並發性、有效支持多核處理器

進程優點:以進程為單位分配計算機資源,方便管理,進程之間互相分離,安全性高,可靠性好

用戶級線程優點:用戶在應用程序中隨意創建、創建代價小、靈活性大、具有一定的並發性

用戶級線程、內核級線程、進程三者的關系:

  1. 引出進程是為了管理CPU,即通過執行程序來使用CPU;進程、內核級線程、用戶級線程都是執行一個指令序列,沒有本質區別,三者都屬於CPU管理范疇
  2. 要執行一個指令序列,除了分配棧、創建數據結構記錄執行位置等以外,還要分配內存等資源,這就是進程的概念
  3. 將進程中的資源和執行序列分離以后引出線程概念,進程必須在操作系統內核創建,這是因為進程創建要涉及計算機硬件(內存)資源的分配;因此進程中的那個執行序列實際上就是一個內核級線程
  4. 內核級線程是操作系統在一套進程資源下創建的,可以並發執行的多個執行序列,操作系統為每個這樣的執行序列創建了相應的數據結構來實現對這些內核級線程控制,如切換、調度等
  5. 上層應用程序也可以創建並交替執行多個指令執行序列,因為執行程序所需要的資源已經在創建進程時分配好了;此時啟動多個執行序列所需要的TCB和用戶棧等信息完全可以由應用程序自己編程實現,由應用程序負責操控多個執行序列,對操作系統完全透明

4.3.2 內核級線程之間的切換

總的來說,內核級切換仍然完成三個工作:切換TCB,切換棧與切換PC指針,它與用戶級切換的區別:

  1. 內核級線程的TCB存儲在操作系統內核中,完成TCB切換的程序應該執行在操作系統內核中,通過中斷進入內核
  2. PC指針存放在棧中,利用棧完成切換,這個棧應該是內核棧,和用戶級線程相比,內核級線程切換棧要同時切換用戶棧和內核棧

大概過程:

  1. 中斷進入,核心工作是記錄當前程序在用戶態執行時的信息,如當前使用的用戶棧、當前程序執行位置、當前執行的現場信息等
  2. 調用schedule,切換TCB
  3. 切換內核棧
  4. 中斷返回,將存放在下一個線程的內核棧中的用戶態程序執行現場恢復出來,這個現場是這個線程在切換出去時由中斷入口程序保存的
  5. 用戶棧切換,即切換用戶態程序PC指針以及相應的用戶棧

4.5 CPU調度

如果操作系統支持線程,則線程是CPU調度的基本單位,否則是進程,下述調度方法線程進程都適用,統一稱之為“任務”

PC機上主要考慮三個基本准則:

  1. 任務的周轉時間,即任務從新建進入操作系統到該任務完成離開操作系統所經歷的全部事件
  2. 任務的響應時間,即用戶向某程序發起一個交互操作到該任務響應這個操作之間經歷的時間,例如單擊菜單到菜單彈出的這段時間
  3. 系統吞吐量,即一段時間區域內計算機系統能完成的任務總數

交互式任務更關心響應時間,非交互任務更關心周轉時間

先來先服務調度

選擇就緒隊列頭部的那個任務調度執行,是公平的

最短作業優先調度

按照任務的執行時間從小到大排序,任務按照這個順序依次調度執行

實際環境中,任務不可能是一下子都出現在零時刻,無法進行先排一次序,因此這個調度只具有理論意義

最短剩余時間優先調度

這是一種可搶占式的調度,每次新任務到達時,選擇當前剩余執行時間最短的那個任務調度執行

對於非交互式任務比較好

時間片輪轉

將一段時間等分地分割給每個任務,即給每個任務分配一個執行時間片,當前任務地時間片用完時就切換到下一個任務,下一個任務時間片用完再切換

比較適合於交互式任務

多級隊列調度

系統中同時存在交互式任務和非交互式任務,可以引入兩個隊列,一個為前台任務隊列(交互式任務),一個為后台任務隊列(非交互式任務);兩個隊列分別采用時間片輪轉和最短剩余時間優先調度

還需要定義各個隊列地關系,常用的是定義一個優先關系,通常讓前台隊列具有更高的優先級

多級反饋隊列調度

多級隊列調度存在兩個問題:

  1. 如果采用非搶占式調用,后台任務得到CPU后只能等到任務執行完成釋放CPU,這對於前台任務很不友好;如果采用搶占式任務,則如果一直有前台任務,那么后台任務一直不能執行

    解決辦法:后台任務也應該按照時間片來調度,即使有前台任務,后台任務也能得到一個執行時間片

  2. 怎么判定哪些任務是前台或者后台任務,並且前后台任務可能會相互轉化

    解決辦法:應該根據任務在執行過程中的具體表現動態調整,如果一個任務最近發生了I/O,根據局部性原理,這個任務表現出前台任務特點,因此將該任務放到高優先級隊列中,用記錄阻塞態的方法時別I/O;如果任務一個時間片用完之后,還需要繼續執行,說明沒有發生I/O,也沒有完成,根據局部性原理,可以近似地認為這是一個后台任務,因此可以給它分配更長的時間片,同時降低優先級,並且解決了最短剩余時間優先調度的任務時長預測問題

第五章 進程同步——讓多個進程的退進合理有序

等待和喚醒實現了進程之間的相互依賴

可以給進程同步一個描述性定義:進程同步就是通過對進程的走走停停(等待和喚醒)的控制來讓多個進程步調一致,合理有序地向前推進,完成相互依賴、相互合作

5.1 從信號到信號量

重點看書上關於生產者消費者的應用

信號,其實就是Java中的鎖

信號量:

  1. 一個整型變量,用來記錄和進程同步有關的重要信息
  2. 能讓進程阻塞睡眠在這個信號量上
  3. 需要同步的進程通過操作加減信號量實現進程的阻塞和喚醒,即進程間的同步

Java高並發程序設計中,信號量(Semaphore)是指定最多有N個線程可以訪問某一個資源,注意區別

5.2 臨界區

信號量的作用就是根據信號量數值表達出來的語義來決定進程的停與走,因此多個進程共同修改信號量時,要保護信號量,不能隨意修改

對信號量的保護就是保證每個進程對信號量的修改操作是原子操作,保護信號量的機制就是在修改信號量的代碼基礎上包裹其他代碼來讓信號量的操作稱為原子操作

臨界區就是對信號量操作的代碼,也可以理解為是信號量這個資源;之所以稱之為臨界區,因為一旦進入這段代碼,操作系統的狀態發生改變,現在不能在進程之間隨意切換

有了臨界區的概念,信號量的保護實質就是讓進程中修改信號量的代碼變成臨界區代碼

5.2.1 臨界區的軟件實現

Peterson算法:用用標記法判斷進程是否請求進入臨界區;如果進程想進入臨界區,用輪換法給就進程一個明確的優先排序;

這個算法只能處理兩個進程的臨界區

Lamport面包店算法:用面包店舉例:N個谷歌要進入面包店采購,首先按照次序給每個顧客安排一個號碼,顧客按照其號碼大小從小到大依次購買,完成購買的顧客號碼重置為0,這樣完成購買的故可如果要再次購買,就必須重新排隊

5.2.2 臨界區的硬件實現

禁止中斷

硬件原子指令法

5.3 死鎖

死鎖產生的條件:

  1. 互斥:資源不能共享,一個資源每次只能被一個進程使用
  2. 不可剝奪:進程已獲得的資源,未使用完之前,不能強行剝奪
  3. 請求與保持:一個進程因請求資源阻塞時,對已獲得的資源保持不放
  4. 循環等待:若干進程之間形成一種頭尾相接的循環性資源等待關系

5.3.1 死鎖預防

一次申請所有資源或者按序申請資源

缺點:需要預先計算程序請求的資源,很久之后才使用到的資源要很早預留,資源浪費

5.3.2 死鎖避免

每次資源申請時都判斷是否有死鎖出現的危險,如果有危險就拒絕申請

銀行家算法

對所有進程的資源請求都存在一種調度方案令其滿足,從而都能順利執行完成,這種進程序列稱為安全序列

核心:遇到一個資源請求時,假設允許之后,能找到安全序列,說明此次請求安全,可以分配資源,否賊拒絕資源請求

銀行家算法是保守的算法,本來可能不會導致死鎖的資源請求可能會被拒絕,因為安全序列是不構成死鎖的充分條件,而不是必要條件,因為進程中的資源並不是等待全部資源以后才釋放,是使用完就釋放的,而該算法的解是充分必要的

5.3.3 死鎖檢測/恢復與死鎖忽略

讓資源隨意使用,在出現問題的時候檢測死鎖,並恢復

通過改進銀行家算法可以檢測死鎖

鴕鳥算法:針對死鎖不做任何處理,Window、linux都采用死鎖忽略處理算法

第六章 內存管理—給程序執行提供一個舞台

6.1 程序重定位

指令執行時,通過程序重定位將邏輯地址轉換為物理內存地址,實現的硬件叫做MMU(存儲管理部件)

MMU進行重定位的CPU寄存器只有一個,因此進程的重定位基址都放在其PCB中,進程切換時將其PCB中存放的基址取出來賦給這個CPU寄存器

6.2 分段

6.2.1 段的概念

采用分治的思想,將程序分成多個段,比如代碼段(程序指令形成的段)、數據段(程序使用的數據)、棧段(實現函數調用)等

程序分段后,就不應該將程序作為一個整體載入內存,應該將多個段分別載入內存,需要記錄各個段的基址

6.2.2 分段機制下的地址轉換

采用分段機制后,程序中的邏輯地址會變成“段號:偏移”,段號對應的基址要從LDT(局部描述符表)中查詢

顯然,每個進程都有一個LDT表,用來描述該進程的代碼段、數據段等

操作系統有一個GDT(全局描述符表),描述了操作系統的代碼段、數據段、還有指向各個進程LDT表的表項

6.3 內存分區

6.3.1 可變分區與適配算法

需要先在內存中分割出一段空閑內存,然后才能將程序載入到內存中

分區適配算法有:最佳適配算法(每次只選最小的,能滿足段大小的內存)、最差適配(選最大能滿足的內存)、最先適配(從空閑分區表的最前面開始找)

6.3.2 內存碎片

雖然總空閑內存大小能夠滿足內存請求,但是不是連續的,也無法載入程序,這就是內存碎片

內存碎片:雖然總的空閑內存很大,但是由一堆分散在物理內存多個位置的小區域組成,這些小區域不能滿足進程的段尺寸要求而無法使用,從而造成空間浪費

解決方法:

  1. 內存緊縮,通過移動整理將很多零散的空閑內存碎片合並成一整塊空閑區域;但是缺點很明顯,耗時,並且整個過程所有進程不能執行任何動作
  2. 內存離散化,將內存分割成固定大小的小片,內存請求到達時,根據請求尺寸計算出總共需要的小片個數,然后在內存中(任意位置)找出同樣數量的小片分配給內存請求;這是分頁機制的基本思想

6.4 分頁

6.4.1 分頁機制

分頁機制首先將物理內存分割成大小相等的頁框,然后將請求放入物理內存的數據(比如代碼段)也分割成同樣大小的頁,最后將所有的頁都映射到頁框上,完成物理內存頁框的使用

通常將頁設置為4kb(Mysql中的B+樹索引)

要讓內存中的程序可被取值-執行,內存中的數據可被尋址讀寫,還需要解決重定位問題

頁表:由頁表項組成,記錄邏輯頁放入到哪個物理頁框

映射關系:邏輯地址——>頁號——>頁框號——>物理地址

6.4.2 多級頁表與快表

如果不加處理,頁表占用的內存會非常大,比如32位系統,一個進程需要4MB的頁表,100個進程就要占用400MB,並且實際上整個邏輯頁很少全部被用到,一些程序可能只用到幾個邏輯頁而已

給定一個邏輯頁號,要得到對應的物理頁框號,需要和頁表中存儲的邏輯頁號諸葛比較,即使采用二分查找,時間效率也是不能接受的

多級頁表:在頁表項基礎上建立一個高層結構,稱為頁目錄,每個頁目錄中包含多個頁表項,通常4M區域是一章,其中的每個4K是章中的節(就像書的目錄一樣,就章,節)

如果某些邏輯內存區域,比如8MB-12MB區域沒有映射到物理內存中,就不需要將這個頁目錄下的1024個頁表項存放在內存中,減少了存放頁表的空間浪費

兩級頁表的基本結構:引入頁目錄項,一個頁目錄項下面包括多個頁號連續的頁表項,頁表項映射了一頁內存,而頁目錄項映射了一塊內存;此時有兩個表,由頁目錄項組成的頁目錄表和頁表項組成的頁表,查找一個邏輯頁號時,先查找頁目錄表找到頁目錄項,然后根據頁目錄項中存放的指針找到頁表,再查找頁表找到邏輯號對應的頁表項

多級頁表雖然可以解決空間效率問題,但是也會引出地址轉換的時間效率問題

快表(變換旁查緩沖器):緩存那些常用的邏輯頁映射關系(存放在CPU的高速緩存)

總結:

  1. 將物理內存分成頁並以頁為單位進行內存分配,解決內存碎片問題造成的空間浪費
  2. 一旦分頁以后,存放頁表完成地址轉換過程
  3. 采用多級頁表降低頁表存儲的空間開銷
  4. 采用快表降低多級頁表的時間開銷
  5. 最終形成綜合多級頁表和快表的分頁機制

6.5 段頁式內存管理與虛擬內存

虛擬內存將分段機制和分頁機制結合在了一起

程序如何放到內存?

  1. 在虛擬內存中分割出一些分區,將程序的各個段“放入”,並不是真正的放入,只是建立一個映射關系
  2. 建立段表記錄這個映射關系
  3. 將虛擬內存分割成頁,選擇物理內存中的空閑頁框,將虛擬內存中的“頁內容”放到物理頁框中;因為虛擬內存的中間作用,從用戶出發看到的視圖是程序段被放到一個連續“內存”區域上,即分段效果,操作系統將這個“內存”區域按照分頁方式真正放到物理內存中,實現了分頁機制
  4. 建立頁表記錄虛擬內存頁與物理頁框之間的映射關系

內存中的指令如何執行,以call 40為例

邏輯地址CS:40,假定代碼段是第0段

  1. 先查段表取出基址,算處CS:40在虛擬內存中的位置,1000+40=1040,即虛擬地址
  2. 根據虛擬地址查找頁表找到物理地址,1040對應的虛擬頁號是1040/100=10~40,虛擬頁號是10,頁號偏移40,查找頁表得到物理頁框號5,所以最終的物理地址是540
  3. 在地址總線上放入地址540后進行取指,取出的指令是“mov,[300]”,指令call 40被正確執行

第七章 換入\換出—用磁盤和時間來換取一個規整的虛擬內存

假如一個店面只能放10台車,但是倉庫里還有很多車。顧客來看車,發現沒有馬自達3,於是銷售需要去倉庫調一輛馬自達3,同時把店面的一輛車運回倉庫,以便於給馬自達3騰出位置,這時,顧客就能在店面欣賞馬自達3了

店面就是內存,倉庫是磁盤,而倉庫里所有的車就對應虛擬內存

用戶可以訪問任何地方的虛擬內存,如果虛擬內存沒有和物理內存關聯起來,趕緊從磁盤上讀進來並建立關聯,這稱為“換入”,由於物理內存是有限的,很多時候也需要將物理內存中的部分內容移除到磁盤上,並和虛擬內存區域解除關聯,這稱為“換出”

換入\換出是形成規整虛擬內存的關鍵所在

換入

內存換入的核心,當缺少虛擬頁的時候請求調頁,操作系統實現換入就是實現請求調頁

請求調頁的整個過程從MMU發現虛擬頁面在頁表項中的有效位為0開始,這個時候MMU會向CPU發現缺頁中斷;操作系統的內存換入就是從這個缺頁中斷開始,在這個中斷處理中操作系統會去磁盤(倉庫)上找到這個虛擬頁(商品),並將這個虛擬頁從磁盤上讀進來,當然讀進來之前需要先找到一個空閑的物理內存頁框(店面中的一個櫃台),再將那個虛擬頁讀到空閑頁框之后(將商品放到櫃台上),虛擬頁面就和物理頁框建立關聯了,再更新頁表來記錄這個映射關系

換出

換出的一個核心工作是找到空閑的物理內存頁框,如果沒有空閑的,則通過換出已映射的頁,騰出空閑頁框

頁面換出算法

評價指標:缺頁次數,較少的缺頁次數會提高請求調頁的性能

最優置換OPT(optimal replacement)

選擇未來最遠使用的頁面進行淘汰,但這是不可行的,因為很難知道未來的使用情況

最近最少使用LRU(least recently used)

選擇歷史上最近很長時間沒被訪問的頁面淘汰

LRU算法實現
  1. 給每個頁維護一個時間戳

    不可行,因為有的機器永不關閉,時間戳會溢出,並且頁表長度很大,還有需要通過比較來確定時間戳最小的頁,代價很大

  2. 維護一個頁面棧,可以采用指針鏈表數據結構來實現,這樣頁面修改時時間復雜度為O(1);也不可行,因為每次維護頁面棧而的時候,都會造成幾次指針讀寫,每次頁面訪問都需要維護頁面棧,所以每次地址訪問都需要額外的內存訪問,內存的讀寫效率降低很多

  3. clock算法

    clock算法是對LRU算法的一種近似

    1. 最好能用硬件實現來維護訪問時間信息
    2. 在頁表項中存放一個簡單的數來實現對“最近最少使用”的近似表示,並且用硬件在訪問頁面時自動更新這個數

    對時間戳的近似:

    1. 用一位二進制數0、1來近似表示時間戳,如果頁面訪問了,置為1,所以這一位也稱為訪問位(R位)

      基於R位可以構造出基礎的clock算法,也成為第二次機會置換(second chance replacement)算法:首先將分配給進程的所有頁框組織成環形線性表, 產生缺頁時,就從當前的線性表指針(一直停留在上一次缺頁處理完后的位置)處進行環形掃描,如果掃描到的虛擬頁面R位位1,則修改為0,指針向后移動;如果發現掃描位置R位位0,就將該頁淘汰換出;由於訪問過的頁要從1修改為0以后才被換出,所以給訪問過過的頁多給了1次機會,所以也成為SCR算法

      SCR算法的問題:如果“最近過長”,所有的R都是1,該算法會退化成FIFO算法(轉一圈置0,回到第一個置0的頁框,換出)

    2. 更形象的clock算法

      對“最近”有一個更合適的估計,再引入一個掃描指針,該指針定期掃描所有頁面,並將所有的R置0;發生缺頁時,用換出指針掃描也秒,如果該頁面的R位仍然是0,則淘汰,換出指針只負責換出,不負責更改R;

      該算法對LRU的近似是:如果自從上一次定期掃描以來,頁面一直沒有被訪問過,就認定該頁最近沒有被訪問過,將其淘汰

頁框個數分配與全局置換

分配給進程的所有物理頁框都被用完之后,發生缺頁才會調用clock算法進行頁面淘汰,所以操作系統需要確定給進程分配多少個物理頁框;分配物理頁框方法加上頁面置換算法、頁面換入、換出機制才算完整

系統顛簸:進程數量增加時,CPU利用率下降;原因:分配給進程的物理頁框數量太少,少到無法覆蓋當前進程執行時的“局部”,導致換出去頁面又要訪問,需要換入,又將某個頁面換出,不斷往返

解決系統顛簸的方法:計算出進程當前執行需要覆蓋的局部是多大,操作系統保證分配該進程的物理頁框數量大於其局部,如果系統的空閑內存不足,就將某些進程掛起,將其切換到磁盤上,騰出地方保證分配給每個進程的物理頁框個數足夠覆蓋其局部

工作集模型

是估計當前局部需要多大的數學方法

核心是統計進程在最近一個歷史窗口中訪問了哪些頁,這些頁形成的集合被稱為工作集WS,這個集合的大小被稱為工作集大小,記為WWSi,給進程Pi分配頁框的時候,就分配WWSi個頁框

實際系統中,歷史窗口是自適應調整的:如果最近一段時間系統整體缺頁率較高,則將歷史窗口增大,反之減小,提高並發度

缺點:准確求出局部很困難,在實際操作系統中很難完美工作

全局置換策略

某個進程需要物理頁框時,操作系統會去一個全局空閑物理頁框鏈表中取出一個空閑物理頁框進行分配,同時操作系統定期對分配給所有進程的所有頁進行clock算法掃描,發現最近一段時間內沒有被訪問的頁面,就將其換出到磁盤上,並將對應的物理頁框釋放到空閑頁框鏈表中

缺點:如果一個進程的局部大,將比其他進程獲取更多的內存資源,並且進程可以估計設計編碼搶占內存

第九章 文件系統

9.1 磁盤工作過程

構成

磁盤由多個圓形盤面組成,每個盤面上有多個同心圓環,每個同心圓環稱為磁道,多個磁盤的同一磁道組合新成柱面;每個磁道被分割為多個扇區,扇區是磁盤讀寫的基本單位;每個磁盤面上都有一個磁頭,進行磁盤讀寫時,只有一個磁頭上電,讀取對應扇區的數據

工作過程

  1. 磁頭移動,找到對應的柱面(C)
  2. 從柱面中選擇磁道,給對應磁頭上電
  3. 旋轉磁盤,尋找扇區
  4. 開始讀寫

9.2 生磁盤的使用

9.2.1 第一層抽象-從扇區到磁盤塊

要讀磁盤上的數據,理論上需要知道C(柱面),H(磁頭),S(扇區)等信息,但是這對於用戶來說不友好,因此需要建立一個在C,H,S基礎之上的編制方案

對扇區編號:選取一個0號扇區,那么1號扇區應該對應哪塊位置呢?尋道(移動磁臂)是時間的主要消耗,因此1號扇區應該挨着0號扇區,即同一個盤面同一個磁道,此時讀寫1號扇區不需要尋道和旋轉圓盤(讀取0號扇區完划過整個扇區,有相對運動才能將磁信號變為電信號,所以此時磁頭在1號扇區上)

為了提高速度,引入了磁盤塊的概念

磁盤塊:多個連續的扇區;尋道、旋轉一次讀寫K個扇區的策略,比只讀一個扇區速度提高接近K倍

缺點:會有空間浪費,即使該文件最后一個盤塊沒有用完,也不能給其他文件使用

現在只需要告訴告訴操作系統,需要讀取的磁盤塊號

9.2.2 第二層抽象-多個進程產生的磁盤讀寫請求

多個進程會有多個讀寫磁盤的請求,需要用隊列來組織這些請求

此時讀寫過程:想進行磁盤讀寫的進程首先建立一個磁盤請求數據結構,其中封裝了盤塊號,然后這個數據結構被放入磁盤請求隊列,剩下的交給操作系統處理,對用戶完全透明

步驟:

  1. 從隊列中選擇一個磁盤請求
  2. 取出磁盤塊號
  3. 計算C,H,S
  4. 用out語句向磁盤控制器發出具體指令

這層抽象的核心是磁盤調度算法

影響時間的主要因素是尋道,因此應該優先考慮柱面號這個因素,尋道總距離是評價的一個基本准則

最短尋道時間優先(SSTF)

每次選擇離當前磁頭最近的柱面,這是一個貪心算法,並不是最短尋道距離的離線算法,並且兩端柱面很可能出現飢餓問題

離線算法:算法輸入在算法執行之前已經全部出現

在線算法:輸入在執行過程中仍不斷進入

磁盤調度掃描算法(SCAN)

首先向一個方向進行掃描,處理經過的所有請求,直到這個方向不再有磁盤請求時,向另一個方向掃描,並處理經過的所有磁盤請求

不公平性:位於中間的柱面請求比兩端的請求能夠更快處理

循環掃描算法(CSCAN)

可以解決SCAN中公平性問題

首先向某個方向進行掃描,比如沿着柱面號小的方向掃描,處理經過的所有磁盤請求,直到這個方向不再有磁盤請求時,磁頭迅速復位到另一個方向最大的請求位置,然后沿着同樣的方向(柱面號最小)進行掃描,如此反復

又稱為電梯調度算法

9.2.3 第三層抽象-從磁盤請求到高速緩存

目前為止磁盤處理過程:提取各個進程的磁盤請求,根據磁盤塊號找到相應的扇區位置,將這些扇區位置放入內核態內存中,再由系統調用將存放於內核態內存中的磁盤數據復制到用戶態內存中,用戶態程序操作用戶態內存中的數據

引入磁盤高速緩存:用一個散列表來組織那些已經載入的磁盤塊數據——緩存快;用一個空閑鏈表組織空閑緩存塊

9.3 基於文件的磁盤使用

9.3.1 第四層抽象-引出文件

通過磁盤塊號讀取數據,並不方便

為了更符合人們的習慣,引入了第四層抽象——文件,文件是一個連續的字符流

字符流如何存放在磁盤塊上呢?1.順序存儲結構

引入字符流后,操作系統需要能根據字符流位置找到對應的盤塊號,即需要建立字符流和盤塊號之間的映射關系,這就是FCB(文件控制塊),用於記錄對應的文件名,及其起始塊號,文件長度

這種方式對於查找方便,但是對於文件的修改、追加、刪除很耗費時間,類似於 ArrayList

鏈式存儲結構,類似於LinkedList

文件字符流存放的磁盤塊不要求連續,只需要每個磁盤塊中存放下一個字符流片段所在的磁盤塊號即可

顯然,鏈式存儲結構的文件讀效率很低

索引存儲結構

文件字符被分割成多個邏輯塊,在物理磁盤上尋找一些空閑物理盤塊(無須連續),將這些邏輯塊的內容存放進去,再找一個磁盤塊作為索引塊,按序存放對應的物理磁盤塊號

問題:一個很小的文件,只有一個磁盤塊大小,還需要引入索引塊嗎?當一個文件很大時,存放字符流的磁盤塊號數量太多,多到一個磁盤塊容納不下怎么辦?

索引存儲結構:索引節點

就是文件的FCB,其中存放了直接數據塊號,索引塊號,間接索引塊號;直接數據塊直接指向文件內容,字符流中的前6個邏輯塊對應的磁盤塊號用節點中的直接塊信息獲得

利用索引節點中的索引塊號可以讀出索引塊、根據索引塊中存放的物理塊號可以找到文件的內容,由於讀入一次索引塊,被稱為一階索引;如果是間接索引,首先讀入間接索引塊,其中存放的是下一階索引的索引塊號,下一階索引塊中存放的才是對應邏輯塊的物理塊號,這是二階索引

UNIX,Linux都采用這種文件存儲結構

9.3.2第五層抽象-將整個磁盤抽象成一個文件系統

本節的核心是組織和管理一堆文件

磁盤的抽象就是目錄樹,目錄樹由文件和目錄組成,文件上一節已經講過,因此實現目錄樹的關鍵就是實現目錄

目錄中存放文件名、文件的FCB地址;FCB地址:將磁盤上所有的FCB數據結構連續地放再一個磁盤塊序列上,這個地址就代表FCB數組中地索引

這樣設計后,目錄的內容是[文件名:索引],稱之為目錄項

因此:目錄的內容就是一個目錄項數組

使用位圖來描述磁盤上物理盤塊和FCB數組的使用情況,1表示占用,0表示空閑

舉例:如果要新建一個文件,首先用FCB位圖找到一個空閑的FCB,將這個FCB分配給新建文件,並修改FCB位圖;有了FCB后,修改文件所在目錄,添加目錄項;新建文件存放內容時,用空閑數據盤快位圖找到空閑物理盤塊並分配給新建文件,並修改物理盤快位圖和文件FCB


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM