重拾操做系統

主要是把在重讀 《現代操做系統》 中以爲有價值的東西,以 Tips 的形式記錄下來。不可能面面俱到,可是若是有必定的基礎應該是會回想起不少知識的。具體解釋將會以連接形式補充。GitHub
相關閱讀 : 重拾數據結構node

1. 進程與線程

1.1 進程

主要是爲了支持僞併發能力git

  • 運行態 : 實際佔用 CPU 資源
  • 就緒態 : 可運行,可是因爲沒有時間片而被暫停等待 CPU 從新調度
  • 阻塞態 : 外部某種事件致使(資源不足)不可運行

CPU 利用率 = 1 - p ^ n

p : IO 等待時間比

n : 進程數github

1.2 線程

每個進程有一個地址空間和一個控制線程,主要是使某個進程內的任務之間不被相互阻塞,實現一種進程內並行操做的假象。建立銷燬更加輕量級。算法

共享一組資源,協同完成任務。每一個線程有本身的堆棧區(由於要區分同一進程內的線程,CPU 調度要進行狀態的保存)編程

線程模型
用戶空間中實現線程
內核中實現線程
混合實現

1.3 進程間通訊(IPC)

1.競爭條件

兩個或者多個進程讀寫某些共享數據緩存

2.臨界區

將共享內存的訪問代碼稱爲臨界區,確保在每一個時刻兩個進程不可能同時處於臨界區中,這樣能夠避免競爭條件。核心思想爲互斥。安全

併發程序準確高效要知足一下四個條件數據結構

  • 任何兩個進程不能同時處於其臨界區
  • 不該對 CPU 的速度和數量作任何假設
  • 臨界區外運行的程序不得阻塞其餘進程
  • 不得使進程無限期等待進入臨界區
3.忙等待的互斥

互斥實現方案併發

屏蔽中斷

每一個進程進入臨界區後當即屏蔽全部中斷,這樣 CPU 沒法進行進程切換,就要離開臨界區是打開中斷。函數

鎖變量

設置一個共享鎖變量,初始值爲 0。當一個進程想要進入臨界區,必須檢測鎖的值是否爲 0,是則置 1 進入臨界區。不是則等待其餘進程退出臨界區時釋放鎖直到本身能獲取到鎖開始進入臨界區。

鎖變量仍是會產生競爭條件

嚴格輪換法

一直循環等待直到出現容許該進程進入臨界區的條件纔開始運行,十分消耗 CPU 資源。

避免了競爭條件,可是臨界區外運行的程序會發生阻塞

用於忙等待的鎖稱爲自旋鎖。

A:
while (TRUE) {
    while (turn != 0);
    critical_region();
    turn = 1;
    noncritical_region();
}

B:
while (TRUE) {
    while (turn != 1); 
    critical_region();
    turn = 0;
    noncritical_region();
}複製代碼
Peterson 解法

一種互斥算法

#define FALSE 0
#define TRUE 1
#define N 2

int turn;
int interested[N];

void enter_region(int process) {
    int other;
    other = 1 - process;
    interested[process] = TRUE;
    turn = process;
    // 若是有另外一個程序進入臨界區的話則一直空循環
    while (turn == process && interested[other] == TRUE);
}

void leave_region(int process) {
    interested[process] = FALSE;
}複製代碼
4.睡眠與喚醒

前面的弊端是忙等待會消耗 CPU 資源。若是在等待進入臨界區時能夠掛起,等到某個信號到達再喚醒就能夠避免這種狀況了。

生產者-消費者問題

利用資源緩衝區實現進程間的協調

#define N 100 
int count = 0;

void producer(void) {
    int item;
    while (TURE) {
        item = produce_item();
        if (count == N) {
            sleep();
        }
        insert_item(item);
        count = count + 1;
        if (count == 1) {
            wakeup(consumer);
        }
    }
}

void consumer(void) {
    int item;
    while (TURE) {
        if (count == 0) {
            sleep();
        }
        item = remove_item();
        count = count - 1;
        if (count == N - 1) {
            wakeup(producer);
        }
        consume_item(item);
    }
}複製代碼
5.信號量

引入一個信號量來累計喚醒次數,能夠爲 0 或正數

使用 down 和 up 操做代替 sleep 和 wakeup

#define N 100
typedef int semaphore
semaphore mutex = 1;  // 控制對臨界區的訪問
semaphore empty = N; // 計數緩衝區的空槽數目
semaphore full = 0; // 計數緩衝區的滿槽數目

void producer(void) {
    int item;
    while (TRUE) {
        utem = produce_item();
        down(&empty);
        down(&mutex);
        insert_item(item);
        up(&mutex);
        up(&full);
    }
}

void consumer(void) {
    int item;
    while (TRUE) {
        down(&full);
        down(&mutex);
        item = remove_item();
        up(&mutex);
        up(&empty);
        consume_item(item);
    }
}複製代碼
  • mutex : 用於互斥,保證任一時刻只有一個進程讀寫緩衝區
  • full && empty : 實現同步,保證某種時間的順序發生或者不發生
6.互斥量

僅僅適用於管理共享資源或一小段代碼

7.管程
8.消息傳遞
9.屏障
1.4 調度

當有多個進程處於就緒態時就面臨調度的選擇。

內核管理線程時調度能夠認爲是線程級別的。

進程行爲有 計算密集型I/O 密集型。而如今因爲 CPU 改進速度加快,進程行爲更傾向於後者,因此應該多運行該類進程保持 CPU 的利用率。

調度算法
  1. 批處理

    • 先來先服務
    • 最短做業優先
    • 最短剩餘時間優先
  2. 交互式

    • 輪轉調度(每一個進程一個時間片,用完就輪轉)
    • 優先級調度
    • 多級隊列
    • 最短進程優先 (aT0 + (1 - a)T1
    • 保證優先
    • 彩票調度
    • 公平分享調度
  3. 實時

線程調度

和系統支持的線程實現方式有關(理解 : 線程表存在哪的區別)

用戶級線程 : 內核並不知道,內核只是選中該進程,至於進程中跑哪一個線程由用戶態調度決定。

內核級線程 : 直接調度某個進程內的線程。

以上兩種方式在性能上有主要差異 : 前面說起 I/O 操做實際上是很耗時的,因此在進程間切換比在線程間切換更加耗時。由於線程輕量,而進程完成切換要完整的山下文切換,修改內存映像。並且同一進程內的線程 I/O 訪問 cache 的局部性更高,不一樣進程間切換的清理緩存,這也會消耗時間。

2. 存儲管理

主要思想就是內存抽象

2.1 空閒內存管理

使用位圖的存儲管理
使用鏈表的存儲管理

2.2 虛擬內存

程序產生的地址爲虛擬地址,在沒有虛擬內存的操做系統上,直接將地址輸送到內存總線上。而有虛擬內存的操做系統上,把虛擬地址輸送到 MMU(Memory Management Unit),由 MMU 將虛擬地址映射爲爲物理地址。

分頁

虛擬地址空間 : 頁面 物理內存地址 : 葉框 4k大小

虛擬地址 = 虛擬頁號(高位) + 偏移量(低位)

頁表 : 把虛擬地址的頁面映射爲頁框,每一個進程都有本身的頁表

加速分頁方法 : 轉換檢測緩衝區(TLB)主要是優先在 TLB 中查找頁面號。

大內存頁表

  1. 多級頁表
  2. 倒排頁表 : 每一個頁框一個表項 + TLB 快表

2.3 頁面置換算法

最優頁面置換算法不可實現,由於沒法肯定將來。

1.最近未使用頁面置換算法(NRU)

設置訪問(讀、寫)位 R,頁面修改位 M。

2.先進先出頁面置換算法(FIFO)
3.第二次機會頁面置換算法

設置一個檢測最老頁面位 R

4.時鐘頁面置換算法

鏈表實現頁面選擇

5.最近最少使用頁面置換算法(LRU)

利用矩陣模擬 : 增長自身權重減小其餘權重,行置 1,列置 0。

6.用軟件模擬 LRU

老化算法

7.工做集頁面置換算法
8.工做集時鐘頁面置換算法
算法 註釋
最優算法 不可實現,但可做爲基準
NRU(最近未使用)算法 LRU 的很粗糙近似
FIFO(先進先出)算法 可能拋棄重要頁面
第二次機會算法 比 FIFO 有大的改善
時鐘算法 現實的
LRU(最近最少使用)算法 很優秀,但很難實現
NFU(最不常用)算法 LRU 的相對粗略的近似
老化算法 很是近似 LRU 的有效算法
工做集算法 實現起來開銷很大
工做集時鐘算法 好的有效算法

2.4 內存映射文件

進程發起系統調用,把文件映射到其虛擬地址空間的一部分。通常實現是開始不加載,在程序訪問時在按頁加載。

// Linux 待填

2.5 實現

分頁工做
  • 進程建立時 : 操做系統要肯定程序和數據在初始時有多大,併爲它們建立一個頁表,操做系統還要在內存中爲頁表分配空間並對其進行初始化。
  • 進程運行時 : 頁表必須在內存中(反之不須要),而且在磁盤交換區中分配空間。
  • 調度一個進程執行時 : 爲新進程充值 MMU,刷新 TLB,更換頁表。
  • 缺頁中斷髮生時 : 操做系統必須經過讀硬件寄存器肯定是哪一個虛擬地址形成了缺頁中斷經過該信息計算須要哪一個頁面,定位磁盤位置並找到合適的頁框來存放新頁面,必要的話要置換老頁面,而後把所需頁面讀入頁框。最後,備份程序計數器,是程序計數器指向引發缺頁中斷的指令,並從新執行該指令。
  • 進程退出時 : 釋放頁表,頁面和頁面在硬盤上佔的空間。
缺頁中斷處理
  1. 硬件陷入內核,在堆棧中保存程序計數器。大多數機器將當前的指令的各類狀態信息保存在特殊的 CPU 寄存器中。
  2. 啓動一個彙編代碼例程保存通用寄存器和其餘易失信息,以避免被操做系統破壞。這個例程將操做系統作爲一個函數來調用。
  3. 當操做系統發現一個缺頁中斷時,嘗試發現須要哪一個虛擬頁面。一般一個硬件寄存器包含了這一信息,若是沒有的話,操做系統必須檢索程序計數器,取出這條指令,用軟件分析這條指令,看看他在缺頁中斷時正在作什麼。
  4. 一旦知道了發生缺頁中斷的虛擬地址,操做系統檢查這個地址是否有效,並檢查存取與保護是否一致,若是不一致,向進程發出一個信號或殺掉該進程。若是地址有效且沒有保護錯誤發生,系統則檢查是否有空閒頁框。若是沒有空閒頁框,執行頁面置換算法尋找一個頁面來淘汰。
  5. 若是選擇的頁框「髒」了,安排該頁面寫回磁盤,併發生一次上下文切換,掛起產生缺頁中斷的進程,讓其餘進程運行直至磁盤傳輸結束。不管如何,該頁框被標記爲忙,以避免由於其餘緣由而被其餘進程佔用。
  6. 一旦頁框「乾淨」後(不管是馬上仍是在寫回磁盤後),操做系統查找所需頁面在磁盤上的地址,經過磁盤操做將其裝入。該頁面被裝入後,產生缺頁中斷的進程仍然被掛起,而且若是有其餘可運行用戶進程,則選擇另外一個用戶進程運行。
  7. 當磁盤中斷髮生時,代表該頁已被裝入,頁表已經更新能夠反映他的位置,頁框也被標記爲正常狀態。
  8. 恢復發生缺頁中斷指令之前的狀態,程序計數器從新定向這條指令。
  9. 調度引起缺頁中斷的進程,操做系統返回調用他的彙編語言例程。
  10. 該例程恢復寄存器和其餘狀態信息,放回到用戶空間繼續執行,就好像缺頁中斷沒有發生過同樣。

2.6 分段

段是邏輯實體,大小不固定。

2.7 分段和分頁結合 : MULTICS

還有 Intel Pentuium 未介紹

34 位的 MULTICS 虛擬地址

段號 頁號 頁內偏移
18 6 10
  1. 根據段號找到段描述符
  2. 檢查該段的頁表是否存在內存中。若是在,則找到他;若是再也不,則產生一個段錯誤。若是訪問違反了段的保護要求就要求發出一個越界錯誤(陷阱)。
  3. 檢查所請求虛擬頁面的頁表項,若是該頁面再也不內存中則產生一個缺頁中斷,若是在內存就從頁表中取出這個頁面在內存中的起始地址。
  4. 把偏移量加到頁面的起始地址上,獲得要訪問的字在內存中的地址。
  5. 最後進行讀或寫操做。

3. 文件系統

文件系統存放在磁盤上。多數磁盤劃分爲一個或多個分區,每一個分區中有一個獨立的文件系統。磁盤的 0 號盤扇區稱爲主引導記錄(Master Boot Record, MBR),用來引導計算機。在 MBR 的結尾是分區表,該表給出了每一個分區的其實和結束地址。表中的一個分區被標記爲活動分區。在計算機被引導時,BIOS 讀入並執行 MBR。MBR 作的第一件事是肯定活動分區,讀入它的第一個塊,稱爲引導塊,並執行。

整個分區:

MBR 分區表 磁盤分區 磁盤分區 磁盤分區...

磁盤分區:

引導塊 超級塊 空閒空間管理 i 節點 根目錄 文件和目錄

3.1 文件實現

連續分配

把每一個文件做爲一連串連續數據塊存儲在磁盤上。

鏈表分配

一個文件由幾個磁盤塊組成。

在內存中採用表的鏈表分配

把每一個磁盤塊的指針字放在內存的一個表中

i 節點

每一個文件賦予一個稱爲 i 節點(index-node)的數據結構,列出文件屬性和文件快的磁盤地址。

4. 輸入/輸出

4.1 I/O 硬件原理
I/O 設備

塊設備 : 以塊爲單位傳輸,可尋址

字符設備 : 以字符爲單位收發字符流,不可尋址

設備控制器
內存映射 I/O
直接存儲器存取

DMA 工做原理:

  1. CPU 對 DMA 控制器進行編程
  2. DMA 請求磁盤傳送數據到內存
  3. 磁盤傳送數據到內存
  4. 磁盤給 DMA 控制器應答
  5. 完成中斷

5. 死鎖

5.1 資源

在進程對設備,文件等取得了排他性訪問權限的時候,有可能會出現死鎖。這類須要排他性使用的對象稱爲資源。

可搶佔資源

能夠從擁有它的進程中搶佔而不會產生任何反作用。(存儲器)

不可搶佔資源

指在不引發相關的計算失敗的狀況下,沒法把他從佔有它的進程處搶佔過來。( CD 刻錄)

資源使用步驟:

  1. 請求資源
  2. 使用資源
  3. 釋放資源

5.2 死鎖概述

若是一個進程集合中的每一個進程都在等待只能由該進程集合中的其餘進程才能引起的事件,那麼,該進程集合就是死鎖的。

資源死鎖條件

發生資源死鎖的四個必要條件:

  1. 互斥條件 : 每一個資源要麼已經分配了一個進程,要麼就是可用的。
  2. 佔有和等待條件 : 已經獲得了某個資源的進程能夠再請求新的資源。
  3. 不可搶佔條件 : 已經分配給一個進程的資源不能強制性地被搶佔,它只能被佔有它的進程顯示地釋放。
  4. 環路等待條件 : 死鎖發生時,系統中必定有兩個或兩個以上的進程組成的一條環路,該環路中的每一個進程都在等待着下一個進程所佔有的資源。

5.3 死鎖檢測與死鎖恢復

死鎖檢測主要是判斷當前空閒資源在某種合理分配下是否能使全部進程都運行完而且最終資源都可以釋放。

恢復方法 :

  1. 利用搶佔式恢復
  2. 利用回滾恢復
  3. 利用殺死進程恢復

5.4 死鎖避免

資源軌跡圖
安全狀態和不安全狀態
單個資源的銀行家算法
多個資源的銀行家算法

5.5 死鎖預防

死鎖避免從本質上來講是不可能的,由於他要獲取將來的信息。

破壞互斥條件

若是資源不被一個進程獨佔死鎖不會發生。(假脫機打印機)

破壞佔有和等待條件

開始執行前請求全部資源就不會形成等待。另外一種是請求資源時先釋放本身所持有的資源,再嘗試一次請求資源。

破壞不可搶佔條件

針對某些資源進行虛擬化,實現可搶佔。

破壞環路等待條件

保證每一個進程在任什麼時候刻只能佔用一個資源若是要請求另一個資源它必須先釋放第一個資源。另外一種是將全部資源統一編號,進程能夠在任什麼時候刻提出資源請求,可是請求必須按照資源編號順序(升序)提出。

5.6 其餘問題

兩階段加鎖
通信死鎖
活鎖
飢餓
相關文章
相關標籤/搜索