---
# System prepended metadata

title: 作業系統
tags: [大學筆記系列]

---

# Overview
* 作業系統屬於「軟體」，目的是使機器更容易地執行程式
    * 讓程式們公平的使用資源
    * 能與其他設備互動
    * 提供api讓使用者能與系統資源互動
* Running a program = 執行一系列的指令

作業系統 = Processor + Memory + I/O interface

* Mechanism: 定義某個功能的概念
* Policy: 實作某個功能的演算法

# Process
作業系統具有以下api以控制Process
* Create
* Destroy
* Wait
* 其他控制(ex: 暫停、回復)
* 取得狀態

## Process 狀態
* New: 將相關資料從disk載入至memory
* Ready: 已準備好隨時被CPU執行
* Running: 正在被CPU執行
* Waiting(Blocked): 等待某個事件發生(ex: 等待輸入)
* terminated: 執行結束
![image.png](https://hackmd.io/_uploads/BkltWNZeXT.png)


### 創建 Process
一個Process在memory中所佔用空間的分配:
text: 決定要Fetch那些資料
data: 放全域變數
stack: 放置區域變數、函式回傳的address等
heap: 放動態配置的記憶體

## Process Control Block(PCB)
是一種作業系統用來追蹤Process的資料結構，其中儲存了以下資訊
* Register context: Process 被暫停後，register要暫時挪給其他Process用，故需要這個額外的空間將register的值暫時記住
* State: Process當前的狀態
* Program counter

## Dual-mode operation
Dual-mode指的是 kernal mode跟user mode，為了確保OS能掌握控制權，user Process 的某些操作(ex: 配置記憶體) 需要透過OS Process執行；換句話說，user Process的某些操作必須要間接透過System call，請OS Process 來執行

## Interrupt
當發生特定事件時，硬體或軟體可以通知CPU，使CPU暫時放下手邊執行的工作來處理這個Interrupt，並重新安排任務執行順序

Interrupt可以被硬體或軟體所觸發:
* hardware: I/O設備可以發送一個signal來實現Interrupt
* software: 通常是system call

### 流程
![](https://hackmd.io/_uploads/S1BRVzHGT.png =80%x)

## CPU 判斷 I/O 完成的邏輯
1. Polling: CPU定期檢查I/O是否結束了沒
2. **Interrupt**: 當I/O結束時，I/O device(硬體) 會送一個signal通知CPU，接著OS會進入kernal mode，來決定接著要讓CPU執行哪個Process(I/O結束的那個或是正在執行的那個)

補充: 由軟體產生的Interrupt通常稱為trap或是System call

## OS 重新取得CPU控制權的方法
1. 等待System call
2. Timer interrupt: 定時觸發一個interrupt

### Context Switch
指的是作業系統在切換不同Process時，保存和恢復每個Process的狀態信息的過程，操作本身並不會對Process運算有任何進展

由dispacther執行

## Process scheduling
假設一個CPU要同時有多個Process要處理，為了最大化使用效率，在分配資源的時候，需要盡量滿足以下原則:
1. 希望盡量讓CPU無時無刻在做事
2. 希望每個Process 都能公平地被執行到

實作上是透過操作一系列的queue，分別用來放置狀態為New、Ready、Wait的process來實現的

(補充)
放置狀態為**New**之process的queue又被稱作**Job queue**
放置狀態為**Ready**之process的queue又被稱作**Ready queue**
放置狀態為**Wait**之process的queue又被稱作**Device queue**
除此之外，也可以根據用途來進一步的命名queue，例如I/O waiting queue

### scheduler 的種類
scheduler的目的是改變Process的狀態，可以分為
* Long-term scheduler (job scheduler)
    * **選擇**要將那些Process 從Job queue 轉移至 ready queue 中(從 New 狀態變為 Ready 狀態)
    * 不太常使用，甚至有些作業系統沒有
    * 控制了multi-prigramming 的degree

* Short-term scheduler (CPU scheduler)
    * **選擇**要將那些將Process 從ready queue 拿出來跑(從 Ready 狀態變為 run 狀態)

(補充)
根據process的性質，可以分成兩類:
* I/O bound process: 充滿I/O工作的process
* CPU bound process: 充滿計算的process

適當的混用這兩類process能提升CPU的使用率

(補充)
CPU–I/O Burst Cycle: 一個Process在執行的時候，通常處於"**交由CPU執行中**"跟"**等待I/O**"兩種狀態，burst time指的是等待時間

CPU burst time: Process交由CPU執行的等待時間


### Dispatcher
scheduler 選擇要轉移狀態的Process後，便會交由Dispatcher來**完成轉移的動作**

Dispatcher 的工作具體有:
* switching context: 保存當前Process並切換至新的Process
* switching to user mode: 控制執行user Progrem的權限以保護系統
* Jumping to the proper location in the user program to restart that program: 重啟先前暫停的Process時，能順利跳至上次暫停的位置繼續執行

重要概念:
Dispatch latency: Dispatcher暫停一個Process到執行另一個Process之間所需的時間，可以視作切換Process所需的時間成本

### 理想狀況下的scheduling Algorithm
定義一系列參數作為衡量標準
* **Turnaround time**
Turnaround time = process完成的時間 - process arrive的時間 

* **Waiting time** (指的是Process在ready queue中的等待時間)
Waiting time = process完成的時間 - process arrive的時間 - process需要執行的時間(brust time)

* **response time**
response time = process第一次跑的時間 - process arrive的時間

1. First In, First Out
    * 邏輯: 先arrive的Process先執行，直到執行完畢後再換下一個執行
    * 缺點: 若不小心執行到CPU burst time過長的Process，會導致其他Process等待過久 (**Convoy effect**)
    * 範例: 
    ![](https://hackmd.io/_uploads/SyF6DuEJ6.png)

    Average Turnaround time: (100+110+120)/3
    
2. Shortest Job First(SJF)
    * 邏輯: 同樣是先arrive的Process先執行，直到執行完畢後再換下一個執行；若有多個Process同時arrive，則挑選CPU burst time短的Process先執行
    * 缺點: 若先到的Process的CPU burst time過長，會導致其他Process等待過久 (**Convoy effect**)
    * 範例:
    ![](https://hackmd.io/_uploads/Bk13PdVkp.png)
    Average Turnaround time: (10+20+120)/3
3. Preemptive SJF
    * 邏輯: 無論何時都選擇**當前剩餘CPU burst time最短**的Process執行；Preemptive的意思是Process執行到一半允許被打斷
    * 可以被證明這種scheduling的方法可以最佳化Turnaround time跟Waiting time(前提是已知每個Process的CPU burst time)
    * 缺點: 現實中不能精準得知一個process要執行多久
    * 範例:
    ![](https://hackmd.io/_uploads/HyTDOuE1p.png)
    Average Turnaround time: (120+10+20)/3
    
4. Approximate SJF:
    * 邏輯: 跟類似，不過CPU burst time用猜的，使用exponential average來預測下一次的CPU brust time
        ![](https://hackmd.io/_uploads/rkn96A8Jp.png =40%x)
        * tn: 第n個Process 實際的CPU brust time
        * τn+1: 下一次CPU brust time的預測值
        * τn+1: 上一次CPU brust time的預測值
        * α: 常數，通常為1/2
    * 範例:
    ![](https://hackmd.io/_uploads/ryFBZJvJp.png =80%x)


5. Round Robin (又稱為 RR 或 scheduling quantum )
    * 邏輯: 每隔一定時間(Time slice)就從ready queue中公平的挑出一個Process執行，讓每個Process都能平均的被執行到，以降低response time
    * 缺點: context switch成本高
    * 範例:
    ![](https://hackmd.io/_uploads/r1f1byP16.png)
    Average Turnaround time: (13+14+15)/3
    Average Response time: (1+1+1)/3
    
### Priority scheduling
現實狀況中，可能某些Process急著被執行，故為每個Process都設置一個Priority number，權限高的有被執行的優先權

* Starvation問題: 權限低的Process永遠不會被執行
* Aging解決方法: 每隔一段時間，便提升還未被執行的Process 2的權限

### Multilevel Queue Scheduling
現實狀況中，不同ready 狀態的Process可能有不同的工作性質，故將ready queue細分成不同的queue，並套用不同的scheduling algorithm，同樣為每個queue加上一個不同的權限

* Starvation問題: 權限低的queue永遠不會被執行
* Time Slice解決方法: 為每個queue都分配一定的時間可以使用CPU，例如權限高的queue執行5秒後換權限低的queue執行1秒

### Multi-Level Feedback Queue(MLFQ)
這個概念建立在作業系統中有數個priority不同的ready queue，是關於應該將Process分配至哪個ready queue中執行的演算法

該演算法執行結果類似SJF

假設A、B為兩個Process，Priority(A)指的是該Process處在的ready queue權限
規則:
1. 若Priority(A) > Priority(B)，則執行A
2. 若Priority(A) = Priority(B)，則A、B以Round Robin的方式執行
3. 當Process第一次轉成ready 狀態，會無條件的先進到權限最高的ready queue
4. 若一個Process 累計用光了該queue的Time Slice，則將該Process轉移至低一階的queue中 (避免gaming問題: 惡意使用99% 的Time Slice後，進入假I/O一下，再繼續占用CPU)
6. 每隔一段時間，便將所有的Process全部提升至權限最高的queue中 (避免Starvation問題、讓型態改變的Process有機會翻身)

ex:
![](https://hackmd.io/_uploads/SJmy4nhkp.png =50%x)
* 第一個Process進入系統，分配到Q2
* 第一個Process用光了Q2 的Time Slice，掉到Q1
* 第一個Process用光了Q1 的Time Slice，掉到Q0
* 第二個Process進入系統，分配到Q2；因為Q2權限大於Q0，故先執行第二個Process
* 第二個Process用光了Q2 的Time Slice，掉到Q1；因為Q1權限大於Q0，故還是先執行第二個Process
* 第二個Process執行完畢，換第一個Process執行
* 第一個Process執行完畢

ex:
![](https://hackmd.io/_uploads/rJlj4nnka.png =50%x)
* 第一個Process進入系統，分配到Q2
* 第一個Process用光了Q2 的Time Slice，掉到Q1
* 第一個Process用光了Q1 的Time Slice，掉到Q0
* 第二個Process進入系統，分配到Q2；因為Q2權限大於Q0，故先執行第二個Process
* 第二個Process放棄CPU轉而等待I/O，換第一個Process執行
* 第二個Process I/O結束，因為第二個Process權限較高，故切換到第二個Process執行
* 如此反覆直到兩個Process執行結束

### Fair-share scheduling
* 目的是"真正的平等"，而非齊頭式平等
* 真的更重要的Process，便獲得更多的CPU time

1. Lottery scheduling
    * 邏輯:
        * 根據Process的重要度，分配不同數量的ticket給該Process；每次都抽出一張ticket，並執行對應的Process
        * 至於要多少的ticket，則由user決定，再交由系統轉成global value(適當的比例)
        * Process也可以根據需求暫時提升ticket數量
    * 範例:
        ![](https://hackmd.io/_uploads/SJU0YCaJT.png)

2. Stride scheduling
    * 邏輯:
        * 根據Process的重要度，分配不同數量的ticket給該Process；拿一個大數除以某個Process 的ticket，可以得到該Process的Stride(步數)
        * 每次都從ready queue中挑出一個距離起點最近的Process執行，並將其與起點之距加上該Process的Stride
    * 範例:
        ![](https://hackmd.io/_uploads/BkYB2Aa16.png)
        * 大數=10000, 各Process的ticket數量如圖所示
        * 一開始，所有Process距離起點的距離皆相同，任意挑選A執行
        * A距離起點的距離加100，接著挑B
        * B距離起點的距離加200，接著挑C
        * C距離起點的距離加40，還是距離起點最近，繼續挑C
        * 以此類推，觀察可發現ticket越多的Process執行的越頻繁

3. Completely Fair Scheduling (CFS)
    * 特性:
        * time slice不固定，待處理的Process越多，time slice便會越短 (利用sched latency來控制，time slice=sched latency / Process 數量)
        * 使用"nice value" 來控管Process的Priority，為每個Process都分配一個-20~19的數
        * 使用Virtual runtime的概念來使權限高的Process能優先被執行，權限較高則Virtual runtime較少 (類似Stride scheduling的效果)
        * 使用紅黑樹來實作ready queue，以提升搜尋、插入、刪除的效率
    * 優點: 是Linux 預設使用的排程系統

# memory
為了因應Multiprogramming，不同Process可能需要互相切換，故引入了 memory virtualization的概念

* **memory virtualization**: 給每個Process一個假象，讓Process以為自己擁有獨立的記憶體空間；但實際上各個Process式共用同一個實體的記憶體空間
    1* 優點: 很好實做

* free list: 是一個linked list，用於紀錄空的記憶體空間位址開頭
![](https://hackmd.io/_uploads/Bk64CtPlp.png)



##  Address Translation (單純使用base and bound)
* 分配給Process一段連續的物理記憶體，用來存放Stack、code、heap
* 使用virtual memory的概念來使實做更容易(virtual memory從0開始)
* 藉由base and bound機制實現virtual memory跟physical memory之間的轉換

![](https://hackmd.io/_uploads/rJHHCzlxp.png)
* 左圖是virtual memory
* 右圖是physical memory
* 使用register存放一組base/bound
* virtual memory+base=physical memory
* bound 用來存放該Process的極限virtual memory大小

問題: 會有一堆空間沒辦法被指用
解決: 使用Segmentation

### Segmentation
是一個改善base and bound缺點的演算法

* 概念: 原本在base and bound中，分配給Process的物理記憶體是一段連續的空間；Segmentation允許存放Stack、code、heap的物理記憶體是非連續的 (virtual memory仍然要求是連續的)


ex:
![](https://hackmd.io/_uploads/ryuIo4Bza.png =50%x)

Q: virtual address為100的一條指令(屬於Code區域)，其physical memory為?

![](https://hackmd.io/_uploads/r1S63EBfT.png)
* 因為Code區域的virtual memory是從0開始計算的，故直接使用base轉換即可
* physical memory = virtual memory + base
* 所求 = 100 + 32K

Q: virtual address為4200的動態配置變數(屬於heap區域)，其physical memory為?

![](https://hackmd.io/_uploads/Sy3ea4SM6.png)
* 因為Code區域的virtual memory不是從0開始計算的，故virtual memory要先扣掉heap區域的起點virtual memory位置，才能加上base以得出答案
* physical memory = (virtual memory - heap區域的起點virtual memory位置) + base
* 所求 = (4200-4096) + 34K

補充: 若嘗試存取超過heap virtual memory範圍的空間，則會出現Segmentation fault的錯誤

補充:
* Coarse-Grained: 指的是segment數量較少的切法(ex: 切成code, geap, stack)
* Fine-Grained: 指的是segment數量較多的切法(ex: 各個function切開)
補充:
* External fragmentation(外部碎片化): 指的是物理記憶體的總空間是夠的，但找不到大的連續空間以配置
* Internal fragmentation(內部碎片化): 指的是已被配置的記憶體中，有部分實際上並未被使用

## Free space management
能夠處理「不同記憶體大小的請求」
* 在分配空間時，除了分配使用者要求的空間以外，還需要額外的空間儲存size，紀錄本次共分配了多少記憶體(因為free function並不需要size參數)

![](https://hackmd.io/_uploads/HJ0ZfqPep.png =50%x)
* hptr由ptr - "header大小" 計算得出


### spliting概念
當一段可用的連續空間大於需要的空間時，將空間切成兩塊，一塊拿來使用，一塊繼續留在Free list中
![](https://hackmd.io/_uploads/H17ik5wxa.png =70%x)

### Coalescing概念
若有空間被使用完畢並返還後，需合併這些空間
![](https://hackmd.io/_uploads/H1OMeqPgT.png =70%x)

### malloc 與free實作
有了上述觀念後，便可以實作出作業系統的malloc()跟free()函式 (不可以呼叫自己)

1. 初始化
![](https://hackmd.io/_uploads/BJ3sbTvxp.png =50%x)
    1. 呼叫更底層的mmap函式，配置一塊固定為4096 bytes (1 chunk)的空間
    2. 耗費8個byte存這塊空間header的相關資訊

2. 配置空間
    ![](https://hackmd.io/_uploads/r15Qz6DlT.png =50%x)
    1. 填上新空間所需的相關資訊(header的大小相同，不過未配置空間跟以配置空間的header定義不一致)
    2. header移動到free space的最前端 (1,2步實作spliting)
    3. 回傳ptr作為malloc的結果

3. 釋放空間
    ![](https://hackmd.io/_uploads/ryZhGpDgT.png =50%x)
    1. 用一個暫存的變數紀錄head指標原本指向的位置(16708)
    2. head移動到 ptr - 8 的位置 (8代表header大小)
    3. 使head->next指向剛剛紀錄的位置
    4. 透過演算法掃過free list，Coalescing破碎的已釋放空間

### 分配空間策略
假設free list中有不只一塊free space可以拿來分配，根據選擇思路，可以分成以下四種方法 (假設需要配置大小為M的空間):
1. bast fit: 選大於等於M的空間中最小的那塊
    * 缺點: spliting後剩的空間太小沒人要
2. worst space: 選所有空間中最大的那塊
3. first fit: 選第一個符合條件的空間
4. buddy system
    * 將大空間分割成兩個小空間，兩個小空間互為Buddy
    * 分配空間時，以上述規則不斷分割過大的空間，直到切出來的空間大小適合；最小空間圍32 bytes
    * 維護Free list header，指向對應大小的剩餘空間
    * 當釋放空間時，若該空間其Buddy也是空的，則合併之
    * ex:
        ![](https://hackmd.io/_uploads/H1VTWACGp.png)


buddy system:
優點: Coalescing實作容易、能幫助allocater跟paging system
缺點: 檢查步驟導致performace稍低、即使空間連在一起，但不一定可以合併

## paging (single level)
External fragmentation問題:
若要求Process的物理address space須連續，可能會導致硬碟還有空間，但連續的空間不足，而導致無法回應請求
![](https://hackmd.io/_uploads/B1oFZIFep.png =80%x)



為解決上述問題，定義Process的物理address space可以是非連續的，並引入paging 機制管理free space

![](https://hackmd.io/_uploads/H1HQIAAzT.png)
* paging 機制會將physical memory 切成多個大小固定的Page (本例的Page size為16 bytes)
* 可以將virtual address分成VPN 跟offset兩部分；offset的大小跟Page size有關(本例的Page size=2^4，故offset=4)
* 可以將physical address分成PFN 跟offset兩部分
* virtual address 會透過Page table的幫助轉成physical address；Page table就像一個hash table一樣 ( Page_table[VPN]=PFN )

![](https://hackmd.io/_uploads/Bkq_0CCMp.png)
* 這張圖說明了virtual address是如何透過Page table轉成physical address的

優點: 管理簡單、有彈性
缺點: paging table成本高(4MB)、需讀取很多次導致效率變差
![](https://hackmd.io/_uploads/B1wOTvCW6.png)


paging table中一個entry除了存轉換所需資訊外，實務上還存了一些bit，稱為Common Flags of Page Table Entry (PTE)
* Vaild bit: 用於驗證特定的translation 是否有效
* Protection bit: 指出該Page table entry 是否可被讀取、寫入或執行
* Present bit: 指出該Page table entry 是否被swapping出去
* Dirty bit: 指出該Page table entry是否被修改過
* Reference(Accessed) bit: 指出該Page table entry是否被訪問過

### Translation Lookaside Buffer (TLB)
TLB 是一塊cathe，目的是改善paging 的速度

透過硬體的幫助，加入一塊名為translation lookaside buffers(TLBs)的特殊的快速查找快取來實現

透過spatial locality 來提升性能

![](https://hackmd.io/_uploads/B1-468Fg6.png =70%x)
* 先去TLB找看看答案(TLB存的是最近被使用的entry)，查找的過程是同時進行的
* 若找不到，則去page table中查entry

補充:
Temporal Locality: 最近使用的資料可能會在不久後再次使用

Spatial Locality: 在"最近使用的資料"旁邊的資料可能會在不久後再次使用

#### 處理TLB miss
* 在CISC 架構中，交由硬體直接處理TLB miss
* 在RISC 架構中，交由軟體處理TLB miss
    * TLB miss ->  raises exception -> 系統執行Trap handler code

#### TLB when context switch
* 每個Process 都有自己的Page table，但整個系統中的TLB只有一個；為了分辨出TLB中的某筆資料是屬於哪個Process的，新增一個欄位(ASID) 來記住Process ID
![](https://hackmd.io/_uploads/HJlV1VH-T.png =80%x)

![](https://hackmd.io/_uploads/r1E3QkyXT.png =80%x)
* 特殊情況: 兩個Process可能會共用同一個Page(即共享某塊physical memory)，以節省空間
* 以這個例子來說，因為PFN一樣，故

#### TLB replacement 邏輯 (重要)
* 使用LRU(Least recently used)邏輯，每次都剔除一個最久沒被用過的人
![](https://hackmd.io/_uploads/By21W4Hbp.png)

## Paging 優化: 使用Segments
進一步優化Paging 的方法，混和Segments跟Paging兩種機制

![](https://hackmd.io/_uploads/r1G3EVrZa.png =70%x)
* 使用Seg 區域來標記
* 讓每個Seg 都有自己的page table

缺點: 只存Base and bound的方式，仍然可能會導致空間浪費(頭尾被使用中，但中間已被釋放)

## Paging 優化: Multi-level Page Tables
### 雙層 Page Tables
* 將Page table切成數個Page，並使用Page directory來做為搜尋的索引
* 使用Page directory來做為搜尋的索引
* 核心概念是"將page table 用page來管理"

![](https://hackmd.io/_uploads/B1OsP4Sb6.png)
* 左邊是舊版的Page table，右邊是新版
* 可發現右邊所需的記憶體空間較小
* 缺點: 要讀兩次memory(一次讀Page Directory，一次讀想要的Page內容)

![](https://hackmd.io/_uploads/rkUR6l1X6.png)
* 把VPN分割成兩個區塊，分別作為Page Directory Index以及Page Table Index

### 三層 Page Tables
假設原Page table大小是2^21，想要切成一個node 大小2^7
![](https://hackmd.io/_uploads/SyyM34r-T.png =20%x)

2^21 / 2^7 =2^14，故Page directory所需大小為2^14，還可以再切
![](https://hackmd.io/_uploads/ByQuhNrba.png =40%x)

2^14 / 2^7 =2^7，故Page directory所需大小為2^14，總層數為三層
![](https://hackmd.io/_uploads/S1OTh4rb6.png)

駐: 第一層是root還是leaf? root

## 其他 Page table
* Inverted Page Tables
    * 所有Process共用一個Page table
    ![](https://hackmd.io/_uploads/rJ1Wx-JQa.png)
* Hashed Page Table
    * 透過hashing function的不斷hashing完成PFN的查找
    ![](https://hackmd.io/_uploads/SkablWy7p.png)


## Swapping
Swapping 機制在memory 的空間不夠時，可以暫時把一個memory 中的一個Page放到disk中 (踢的是Page table所指向的記憶體空間)
* 簡單來說就是把disk當作是memory的延伸
![](https://hackmd.io/_uploads/Sy59I6sW6.png =50%x)


在一個page table entry中使用額外的一個bit紀路該page是否在disk中
* **Page fault**: 當實體memory中找不到想要的資料時，需先將資料從disk中load回實體memory中
* Page replacement: 將某些page暫時轉移至disk中，以釋放空間給其他人
    * Lazy approach: memory全滿了之後再執行Page replacement

![](https://hackmd.io/_uploads/HkVpK6i-6.png)
1. 發現想要存取的Page不在實體memory中
2. 觸發trap，交由OS執行接下來的動作
3. OS去disk中找到要移到memory中的Page
4. OS將要移到memory中的Page載入至memory中
5. 重設Page table entry中的Present bit
6. 繼續執行指令


### 決定哪個Page要被暫時轉移到disk中
1. optimize
    每次都移除一個最久才會被access 的page
    * 要能預知未來會使用的Page順序才能使用該演算法
2. FIFO
    * 先進的先被移除
    ![](https://hackmd.io/_uploads/BJ_f9rIz6.png)
3. Random
    * 隨機挑一個移除
5. LRU
    * 邏輯: Resulting Cache State 是一個queue，每次將使用過的page丟到queue的最右邊，並踢掉最左邊的page；
    ![](https://hackmd.io/_uploads/SJqP-0ib6.png =50%x)
        * 缺點: 時間成本高，每次操作O(n)
5. Clock algorithm
    * 邏輯:
        * 在有限的memory空間中，clock ptr輪流指向每個空間
        * 每個空間中都會有一個Reference bit，初始化為0
        * 當發生Page fault時，clock ptr會按順序跑過memory。若遇到Reference bit=0的空間，則將該空間作為victim取代之；若遇到Reference bit=1的空間，則將該空間的Reference bit變成0，接著繼續往下走
        * 當hit時，clock ptr不動 (檢查hit是一瞬間發生的事，不須透過clock ptr)，hit位置的Reference bit變成1
    ![](https://hackmd.io/_uploads/By-QYZymp.png)

    * 優點: 犧牲一點效率的情況下，相較於LRU，空間成本大幅降低

6. 其他參考目標:
    1. Considering Dirty Pages: 只讀沒有寫的那些page不需要重寫回disk，故選擇又舊又沒改過的較佳(設定一modified  bit來實做)
    2. prefetching: 系統預測將要被使用的PAGE，提前放進Page table 中
    3. Clustering, Grouping: 收集數個要寫入disk中的資料後，再一次寫入

## Thrashing問題
Thrashing: 因為一次處理的Process太多，導致memory競爭激烈，效率反而變低
![](https://hackmd.io/_uploads/HklP5w0ZT.png =50%x)

### Working-Set Model: 判斷Thrashing
利用page reference table (呼叫disk的歷史紀錄)來衡量

![](https://hackmd.io/_uploads/BJnW2P0W6.png)
* WS(t): 在某時間點下的活躍Page的個數
* dleta: 常數，代表這個演算法要參考多少過去的紀錄

定義D= WS(t)中集合的個數

若D>M 便會發生Thrashing，此時會暫停處理某些Process
若D<M 便會增加同時處理的Process 數量以提升效率

## 其他技術
![](https://hackmd.io/_uploads/HJDFWO0Wa.png =60%x)
* Address Binding 的時機可以在編譯時、載入時、執行時

### Dynamic Loading

### Dynamic Linking and Shared Libraries

### Demand paging
當Process要跑第一條指令的時候，在將包含第一條指令的Page載入

### Copy-on-Write
Parent Process跟Child Process在未寫入的情況下共用同一塊記憶體空間，當某一方寫入時，在進行分支的動作

### Memory-Mapped File
直接將檔案映射至記憶體中，以節省I/O 速度
缺點: 占用記憶體資源

---期中考線---

# Lock
* 之所以需要lock，是因為在multithread的情況下，可能會出現Uncontrolled scheduling
* 例如兩個thread同時執行以下指令時，可能會因為交錯執行而導致錯誤
    ```
    counter = counter + 1
    ```
    (某個thread先讀，但在寫入前又被另一個thread讀到)
* 這類型的指令所在區域又被稱為**Critical Section**
    解決方案: 讓指令的執行具有atomicity的特性 -> 由Lock達成 
* lock()提供的功能有
    * 讓這個thread嘗試取得某個 critical Section 的lock
    * 讓這個thread進入critical section (在 critical Section 的變數便受到 lock 保護)

## Spin Lock
### Test-And-Set (Atomic Exchange)
* 缺點: 不公平、單個CPU時表現極差

![image.png](https://hackmd.io/_uploads/HJlp3HKwmT.png =80%x)
* 邏輯: lock->flag負責紀錄某個變數是否被鎖住
	* 當 lock->flag 為 0 時，代表沒有人擁有某個變數的 lock，此時便會離開迴圈，並同時將lock->flag設成 1
	* 當 lock->flag 為 1 時，代表有人擁有某個變數的 lock，此時便會繼續留在迴圈中等待
* TestAndSet是硬體指令(可以視為atomic的)，邏輯為**回傳傳入的 ptr 的值，並將ptr設成新的值**
    ![image.png](https://hackmd.io/_uploads/ry6UgtP7T.png =80%x)

### Compare-And-Swap (SPARC)
* 將TestAndSet 替換成 CompareAndSwap

![image.png](https://hackmd.io/_uploads/BJSWxtPX6.png)
* 邏輯與 Test-And-Set 等價

![image](https://hackmd.io/_uploads/SyV1EJQ_T.png)
* Compare-And-Swap 是硬體指令(可以視為atomic的)，邏輯為
	* 檢查當前ptr的值
		* 若與 expect 相同，則將ptr設成新的值
		* 若與 expect 不同，則不做事
	* 回傳傳入的 ptr 的值

### Ticket Lock
* 本質上也是Spin Lock，但有些不同
* 具有公平性

![image.png](https://hackmd.io/_uploads/HkL_bKv7T.png)
* ticket 代表本次會抽出的號碼牌
* turn 代表當前輪到的號碼
* 邏輯: 
	* 每個Process都會先抽號碼牌 (ticket)，接著陷入迴圈循環等待，直到lock->turn = myturn (turn 輪到自己)時，便會離開迴圈進入 critical section
	* 而當某個thread unlock變數時，會將 turn+1，代表換下一個人
* FetchAndAdd()是硬體指令(可以視為atomic的)，邏輯為**回傳當前ptr的值，並將ptr中的值加1**；Ticket Lock 透過這個函式實現 "抽號碼牌" 的 atomic 步驟
    ![image.png](https://hackmd.io/_uploads/BkmfGtP7T.png =60%x)

## Non spin lock
* 優點: 當thread數量跟
* 缺點: 不公平、單個CPU時表現極差
* m->guard: 負責保證操作queue時不會遇到race condition
* park(): 使當前的 thread 去睡覺 (睡覺不消耗系統資源)
* unpark(thread ID): 使指定的 thread 醒來

![image](https://hackmd.io/_uploads/rkd4akXup.png)
![image](https://hackmd.io/_uploads/HJTS6km_a.png)
* 邏輯: 
	* lock(): 當某個變數沒人擁有時，直接使當前 thread 擁有這個變數；否則把自己加進 queue 中，接著睡覺等待
	* unlock(): 當沒人在睡覺等待時，便不須做任何事；否則叫醒 queue 中的一個 thread
* 問題: 若 thread B 告訴別人自己要去睡覺了 (queue_add()) 但還沒真的睡 (呼叫 park())，此時 thread A 呼叫 unlock() 並把 thread B 叫醒，接著 thread B 才真的去睡覺 (呼叫 park())，此時便會導致 thread B 永遠不會被叫醒 (因為 thread B 睡著了但不在 queue 中)
	* 解決方案: 新增 system call setpark()
		![image](https://hackmd.io/_uploads/rkXMxlmOp.png =40%x)
		* 邏輯: setpark() 代表這個 thread 進入"即將睡覺的狀態"，若此時有其他 thread 呼叫 unlock() 叫醒這個 thread 的話，使這個 thread 的 park() 直接回傳，不具有使當前 thread 睡覺的作用

### Peterson’s Solution
* 只靠純軟體便可以解決，能夠保證兩個Process間不會有race condition
* 前提是load 跟 store 指令是atomic的

![image.png](https://hackmd.io/_uploads/SybjzQ5Qa.png)
* flag[i]=true 表示 Process i已經準備好(想要)進critical section
* turn: 現在誰有權限進入critical section
* turn = j: 禮讓給對方先做 (為了避免某方無止盡等待的情況)
* 邏輯: 當對方不想進入 critical section，或是對方禮讓我方時，我方便可以進入 critical section
### Bakery algorithm
* 此演算法適用於n process

![image.png](https://hackmd.io/_uploads/SystmXqm6.png)
* num[i] = max(num[0 to n-1])+1: process i 抽取號碼牌 (此步驟並不是 atomic 的，故仍然可能會有兩個 process 同時抽到同個號碼)
* while(choosing[j]): 不斷等待，直到確認想要比較的那個人已抽完號碼牌便離開迴圈
* while((num[j]!=0) && (num[j],j)<(num[i],i)): 不斷等待，直到想要比較的那個人(j)不想進入critical section(num[j]==0)，或是想要比較的那個人比自己晚生成(透過號碼牌來判斷)時，便離開迴圈

# condition
希望一個thread能等待某個條件的發生，並在條件滿足時被通知繼續執行

api實做:
```cpp
int done = 0; // 用來確認是否操作是否做完的flag
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; // 初始化一個互斥鎖，用於保護共享資源的存取
pthread_cond_t c = PTHREAD_COND_INITIALIZER; // 初始化一個條件變數

void thr_exit() {
    Pthread_mutex_lock(&m); // 將操作用lock保護起來，以確定其中的操作是atomic的
    done = 1;
    Pthread_cond_signal(&c); // 發送條件變數的信號，通知等待的執行緒
    Pthread_mutex_unlock(&m); //解鎖
}

void thr_join() {
    Pthread_mutex_lock(&m); // 鎖定互斥鎖，開始進行臨界區域的操作
    while (done == 0) // 檢查標誌，如果條件未滿足
        Pthread_cond_wait(&c, &m); // 釋放鎖，進入等待狀態，等待信號的到來
    Pthread_mutex_unlock(&m); // 解鎖互斥鎖，結束臨界區域的操作
}
```

使用方式 (在這個例子中，parent thread 會等待 child thread 結束，而 child thread 結束時通知 parent thread)
```cpp
void *child(void *arg) {
    printf("child\n"); // 在子執行緒中顯示消息
    thr_exit(); // 呼叫退出函數，設置標誌，發送信號
    return NULL; // 子執行緒結束
}

int main(int argc, char *argv[]) {
    printf("parent: begin\n"); // 顯示父執行緒開始的消息
    pthread_t p; // 宣告一個執行緒識別符號 p
    Pthread_create(&p, NULL, child, NULL); // 創建一個新的執行緒 p，執行 child 函數
    thr_join(); // 等待子執行緒的結束
    printf("parent: end\n"); // 顯示父執行緒結束的消息
    return 0; // 主程式結束
}
```

## 應用場景: producer-Consumer model
* producer
	* 負責將request放入queue中 (put)
	* 一次可能會收到很多的request (loop)，但一次只會放一個，放到滿(=1) 為止
	* **希望在被叫醒時request前queue已經為空**
* Consumer
	* 負責回應queue中的request (get)
	* 一次可能會被要求處理很多的request (loop)，但一次只會處理一個，拿到空為止
	* **希望在被叫醒時request前queue不為空**
	* 可能有許多producer跟Consumer

錯誤版:
![image](https://hackmd.io/_uploads/BkqquG7_6.png)
* 問題: 在一個 producer、兩個 consumer 的情況下，producer 放入一個 request 後去睡覺，並叫醒 consumer 1； comsumer 1 順利消耗一個 request 後去睡覺，但**consumer 2 卻沒有 request 可以消耗** (假設 consumer 2 一直醒著，只是沒機會被執行到)
	* 解決方法: 將 producer 的第 8 行跟 consumer 的第 20 行改成 while 迴圈，如此一來被叫醒後便會再次確認當前情況
	* 在上面的狀況下，輪到 consumer 2 時會再次確認是否有 request 可以被消耗，接著發現沒有，便會繼續回去睡覺
* 問題: 在一個 producer、兩個 consumer 的情況下，consumer 1 在處理完 request 後，去睡覺並叫別人起來，但叫的是 consumer 2 而非 producer，接著consumer 2 醒來後發現沒有 request 可以消耗，又回去睡覺；至此，所有人都在睡覺，發生非預期情況
	* 解決方法: 上述問題發生的主要原因是因為 consumer 叫醒的是 另一個 consumer，只要讓producer 跟 consumer 使用不同的 condition variable，能保證不回叫醒與自己同類型的 process，問題得以解決

最終正確版:
![image](https://hackmd.io/_uploads/r1i8eQQ_a.png)
![image](https://hackmd.io/_uploads/HJXwlQQ_a.png)



註: 某個 process 去睡覺，其實就是進入 ready queue 中，而 Pthread_cond_signal() 的功能便是叫醒一個在 ready queue 中的人

補充:
**Mesa semantics**: 某個thread被叫起來後，不保證會馬上執行 (大部分的系統架構)
**Hoare semantics**: 某個thread被叫起來後，會馬上執行

# Semaphore
* 是一個更高階的工具，可以被視為一個用來處理同步問題的物件
* 可以拿來取代 lock 及 condition variable
* 具有以下幾個 api 可以使用
	* sem_init(&s, 0, 1)
		* 第二個參數設成 0，代表這個物件只能在同一個 process 的不同 thread 間共用
		* 第三個參數決定了這個物件的初始值
			* 設成 1 的時候可以實作 lock
			* 設成 0 的時候可以實作 condition variable
	* sem_wait(&s)
		* 進入 critical section 前呼叫
		* 無論如何，先將這個物件的值減 1
		* 接著檢查該物件的值，若 <0 則會卡住睡覺，否則離開
	* sem_post(&s)
		* 離開 critical section 前呼叫
		* 無論如何，先將這個物件的值加 1
		* 若有其他 thread 正在睡覺，則叫醒其中一個 thread

## 實作 lock
![image](https://hackmd.io/_uploads/r196dmQO6.png =70%x)

## 實作 condition variable
![image](https://hackmd.io/_uploads/H1f3YmmOa.png =70%x)

## 實作 Reader-Writer Locks
* 若一個讀取資料的 process 拿到 Reader-Writer Locks，其他讀取資料的 process 仍可以通行無阻的讀資料，但想要寫資料的 process 會被擋下
* 缺點: 公平性問題

![image](https://hackmd.io/_uploads/HJr3RQ7OT.png)
* sem_eait(&rw->lock): 確保 rw->readers++ 的動作是 atomic 的
* 只有第一個想要讀取資料的 process 會取得禁止別人寫入的鎖 (第 5 行)
* 其他想要讀取資料的 process 不會進到第四行的 if，故可以直接進入 critical section

![image](https://hackmd.io/_uploads/HkQFk4QOT.png)
* sem_eait(&rw->lock): 確保 rw->readers-- 的動作是 atomic 的
* 最後想要讀取資料的 process 結束讀取資料之後，才會釋放禁止別人寫入的鎖 (第 5 行)

# Monitor
* Monitor可以視作一個物件，可以解決特定情況下的 deadlock 問題
	* 被 Monitor 保護的變數及函式可以被許多 thread 使用，但 Monitor 保證同一時間只有一個 thread 能使用
	* 內含 condition variable 以便在各個 thread 之間溝通
	* 同一時間只會有一個 thread 使用 Monitor 中的 function

# 常見concurrency 問題總結
## non-deadlock bug
### Atomic violation
需要 Atomic 的程式被中斷，導致非預期的情況發生

ex: 兩個thread 都對某個變數做修改，導致被overwritten

* 解法: 使用 lock 保護變數，確保中間不會被插手

### Order violation
程式執行的順序非預期

ex: Consumer 在 Producer 執行前便先執行了
ex: 存取變數的 thread 比 建立變數的 thread 早執行

* 解法: 使用 cndition variable
	* Producer產生出東西後，才通知 Consumer
	* Consumer被通知後，才會進行下一步動作

## deadlock bug
![image](https://hackmd.io/_uploads/B1MJ57mra.png =40%x)
* 兩個 thread 同時需要兩個資源，但各自都只擁有一個不放棄，導致卡死

* deadlock 發生四大要件
	* Mutual exclusion: 資源同時只能被一個 thread 使用
		* Prevention 解法: 把 lock 拿掉，使用硬體指令維護 atomic
	* Hold-and-wait: thread 會在拿到部分資源的同時，等待其他資源釋放
		* Prevention 解法: 新增條件: 當 thread 可以全拿到所需 instance 時，才選擇拿
	* No preemption: thread 拿到某個資源後，別個 thread 便不能強佔
		* Prevention 解法: 讓資源允許被強佔
	* Cicular wait: 等待的 flow 形成 cycle
		* Prevention 解法: 嚴謹的定義 thread 拿鎖的順序

補充: Livelock: 兩個人不斷互相禮讓，導致系統有在運作，但進度沒有前進
	解法: 

### Resource-Allocation Graph
用於協助可視化資源的使用情況，並系統係的定義 dead lock 的發生情況

![image](https://hackmd.io/_uploads/BJtanXXBp.png =50%x)
* 圖形意義解釋
	* 圓形代表 process
	* 長方形代表 Resourse
	* 長方形中的點代表 Resourse 的 instance；擁有一個 resourse 中的任意 instance 便可以使用該 resourse
* 結論
	* 若圖中沒有cycle，則不可能有 dead lock
	* 若圖中有cycle，且參與cycle的所有 resourse 都只包含一個 instanse，則必會發生 dead lock
	* 若圖中有cycle，且參與cycle的所有 resourse 可能包含不只一個 instance，則可能發生 dead lock

## 解決 deadlock bug
### Prevention algorithm
直接去除 deadlock 發生的四要素之一

### Aviodance algorithm
確保下一步不會發生 dead lock

#### single instance (每個 resourse 都只有一個 instance)
* 基於上面提到的 Resource-Allocation Graph
* 演算法:
	* 定義 chaim edge 代表 "可能發生的情況"，以虛線表示
	* 
#### multiple instance --- Banker’s Algorithm
* 概念是判斷是否能找到一個順序，使程式能安全執行
* 演算法:
	* 在分配資源之前，先使用 safety algorithm 確認 "在分配資源後，系統是否是安全的"
	* 若確認是安全的，才會執行分配資源的動作 

ex:
給定 Allocation / Max Required
|     | A   | B   | C   | A   | B   | C   |
| --- | --- | --- | --- | --- | --- | --- |
| P1  | 0   | 1   | 0   | 7   | 5   | 3   |
| P2  | 2   | 0   | 0   | 3   | 1   | 2   |
| P3  | 3   | 0   | 2   | 9   | 0   | 2   |
| P4  | 2   | 1   | 1   | 2   | 2   | 1   |
| P5  | 0   | 0   | 2   | 5   | 3   | 3   |

給定 Available
| A | B | C |
| -------- | -------- | -------- |
| 3     | 3     | 2     |

求是否可以分配給 P1 (0, 2, 0)

解法:
1. 先分配資源下去

	Allocation / Max Required (new)
	|     | A   | B   | C   | A   | B   | C   |
	| --- | --- | --- | --- | --- | --- | --- |
	| P1  | 0   | **3**   | 0   | 7   | 5   | 3   |
	| P2  | 2   | 0   | 0   | 3   | 1   | 2   |
	| P3  | 3   | 0   | 2   | 9   | 0   | 2   |
	| P4  | 2   | 1   | 1   | 2   | 2   | 1   |
	| P5  | 0   | 0   | 2   | 5   | 3   | 3   |

	Available (new)
	| A | B | C |
	| -------- | -------- | -------- |
	| 3     | **1**     | 2     |
	
2. 計算出 Need matrix (Max Required - Allocation)

	|     | A   | B   | C   |
	| --- | --- | --- | --- |
	| P1  | 7   | 2   | 3   |
	| P2  | 1   | 1   | 2   |
	| P3  | 6   | 0   | 0   |
	| P4  | 0   | 1   | 0   |
	| P5  | 5   | 3   | 1   |

3. 若某個 Process 所需資源 (Need) 都小於 Available，則
	* 先做該 Process
	* 釋放該 process 的資源至 Allocation 中

	Available (new)
	| A   | B   | C   |      |
	| --- | --- | --- | ---- |
	| 3   | 1   | 2   |  初始情況    |
	| 5   | 1   | 2   | 選P2 |
	| 7   | 2   | 3   | 選P4 |
	| 7   | 5   | 3   | 選P1 |
	| 10  | 5   | 5   | 選P3 |
	| 10  | 5   | 7   | 選P5 |
	
	safety path: P2 -> P4 -> P1 -> P3 -> P5
4. 若能找到 safety path，代表可以分配給 P1 (0, 2, 0)

### 讓dead lock 發生，並即時檢測
主要著重在檢測 dead lock 上

#### single instance
![image](https://hackmd.io/_uploads/By3TRpSrp.png)
* 若存在環，則會發生 deadlock

#### multiple instance
![image](https://hackmd.io/_uploads/B1KDk0BS6.png =70%x)
* 若能找到一組Safe sequence，則當前沒有 dead lock

#### 解決當前的 deadlock
探討當檢測到 dead lock時，如何解決

1. 強制終止所有 Process
2. 不斷一個個拿掉 Process，直到 dead lock 問題解決

# I/O device
--- 斷片 ---

## DMA (Direct Memory Access)

## IDE Device Driver (設備驅動程式)
* 讓電腦的作業系統與硬體設備進行溝通
* 提供 api 給上層應用程式
* 負責控制IDE 硬碟中的各種 register

# Hard disk drive
## 硬碟基本結構
![image](https://hackmd.io/_uploads/SydzmgrUa.png)
* Spindle: 讓 platter 旋轉的軸心
* Disk arm: 讀寫臂
* arm assembly: 讀寫臂集合，同一時間讀寫臂的位置是同步的
* read write head: 讀寫頭
* Platter: 磁盤，存放資料的地方
	* Platter上有一圈圈的 track
	* 每個 track 可以再細分成 許多 sector
	* 數個 sector 可以組成 cluster
	* 不同盤中的相同位置稱為 Cylinder

## I/O time, I/O rate
* Seek time: 移動讀寫頭，使其找到正確 track 的時間 (讀寫頭在 track 間移動的時間)
* Rotation time: 旋轉 platter 使其轉到正確位置所需的時間
* Transfer time: 實際改變 platter 磁場狀態所需時間

I/O time = Seek time + Rotation time + Transfer time
I/O rate(每秒讀寫的資料量) = $\frac{Transfer 的資料大小}{I/O time}$

計算範例 (4KB Random write，假設一個 section 大小為 4KB):
![image](https://hackmd.io/_uploads/r1rdjyS8p.png =50%x)
**Seek time**: 4ms
**Rotation time**: 15000 RPM(rotation per minute) = 250 RPS(rotation per second) = 轉一圈需 4ms
-> 平均而言，找到資料需要轉半圈，故 Rotation time 為 2ms
**Transfer time**: 4KB / 125MB =30µs

故 I/O time = 4ms + 2ms + 30µs = 約為6ms
I/O rate = 4KB / 6ms = 0.66MB/s

計算範例 (100MB sequential write，假設一個 section 大小為 4KB):
![image](https://hackmd.io/_uploads/r1rdjyS8p.png =50%x)
**Seek time**: 4ms (只需要找一次)
**Rotation time**: 15000 RPM(rotation per minute) = 250 RPS(rotation per second) = 轉一圈需 4ms
-> 平均而言，找到資料需要轉半圈，故 Rotation time 為 2ms (只需要找一次)
**Transfer time**: 100MB / 125MB = 800ms

故 I/O time = 4ms + 2ms + 800ms = 806ms
I/O rate = 100MB / 806ms = 125MB/s


## 優化技術
1. Track skew: 當資料需存放在不同的 track 上時，考慮到 Seek time，調整其資料的擺放位置，以避免 platter 需要多轉一圈才能讀到資料
	![image](https://hackmd.io/_uploads/ByINXkSUp.png =40%x)
	* 觀察 22~25
2. Multi-zoned (又稱為 Zoned bit recording, ZBR): 使外圈的 track 能存放的 sector 數更多
	* 將硬碟分成許多 zone， 每個 zone 中的 track 其 sector 數量一樣，但外圈的 zone 其 sector 數量比內圈的 zone 多
	![image](https://hackmd.io/_uploads/ry2YE1HIa.png =40%x)
3. 硬碟的Cache (Track byffer)
	* 暫存硬碟讀到的資料，等待 OS 讀取
	* 將常讀的資料放置於此，以增進效率
	* 暫存要寫的資料，並在真正寫入之前便先回覆給 OS 已寫入 (write-Back)，若確認寫入後才回傳，則稱為 write-through
	* 暫存 request queue並重新排序優先度，等有時間再慢慢消化

## Disk scheduling
這些 scheduling algorithm 可以在 OS 或 Disk 中做

## FCFS (First Come Fist Serve)
概念: 先到的 request 先服務
![image](https://hackmd.io/_uploads/Hklf9MBL6.png =60%x)

## SSTF (Shortest Seek Time First)
概念: 每次都找 queue 中距離當前讀寫頭最近的 request 服務，已減少 seek time
![image](https://hackmd.io/_uploads/SJXAYzr8T.png =60%x)

缺點: 會造成 starvation

## SCAN (elevator algorithm)
概念: 選定起始位置及初始方向，走到底後再往反方向走，不斷往返並一路服務路上的 request
![image](https://hackmd.io/_uploads/SyIyhGHLT.png =60%x)

適用於硬碟 loading 很重的時候

## C-SCAN
提供了一個比 SCAN 演算法更公平的方法
概念: 選定起始位置，往同一個方向走並一路服務路上的 request，接著**直接跳回頭**後繼續往同一個方向走
![image](https://hackmd.io/_uploads/SkF4ZvX_6.png =60%x)

適用於硬碟 loading 很重的時候

## C-LOOK
概念: 選定起始位置，往同一個方向走到"**當前 queue 中最後一個 request 的位置**"，並一路服務路上的 request，接著直接跳回"**當前 queue 中第一個 request 的位置**"後繼續往同一個方向走
![image](https://hackmd.io/_uploads/SkBK0Gr8T.png =60%x)

* 備註: SCAN algorithm 也有 LOOK 版本，跟 C_LOOK 差在反方向走回去的時候也會服務 request
* 結論: SCAN 有許多變形，透過不同個關鍵字來區別
	* C: 直接跳回頭
	* LOOK: 不要走到底

## 加速技巧
I/O merging: OS 將連續的 request merge 起來，變成新的 sequential request，再將這個 request 送給硬碟處理
![image](https://hackmd.io/_uploads/ryQdb7H86.png =50%x)

## RAID
Redundant Arrays of Independent Disks
將多個獨立硬碟組合成一個邏輯單元

重點: Level 0,1,4,5

### RAID Level 0
* 多設置幾顆硬碟以增加可使用總容量
* 將連續的資料分割成不同區塊，並平均分配到不同硬碟中，如此一來便能同時讀取不同硬碟中的資料以加速
* 分割的區塊大小越大，平行度可能越差，但可以減少硬碟的 position time(磁頭移動到資料所在處所需的時間)

![image](https://hackmd.io/_uploads/r1oknqSUT.png =30%x)


### RAID Level 1
* 讓多顆硬碟存兩兩一組放完全一樣的資料，以提升讀取速度及可靠性

![image](https://hackmd.io/_uploads/rJLGyiSUT.png =30%x)

### RAID Level 2

### RAID Level 3
多設置一顆硬碟，以 bit 為單位儲存硬碟資料的 parity

### RAID Level 4
* 多設置一顆硬碟，以 block 為單位儲存硬碟資料的 parity
* 能容忍任何一顆硬碟壞掉
* Full-stripe write: 若要改到同一排的所有 bit，便不需要依序把舊資料讀出來 XOR，直接計算出新的 parity 寫入即可

![image](https://hackmd.io/_uploads/rJGYGiSU6.png =40%x)

計算 parity 的方法範例
![image](https://hackmd.io/_uploads/BJG2MsBLa.png =60%x)

更新 parity 方法:
![image](https://hackmd.io/_uploads/Bygc7srLp.png =60%x)
* 拿舊的資料跟新的資料 XOR，再跟舊的 parity XOR
* Random write 的性能瓶頸被 parity disk 所限制

### RAID Level 5
* 與 RAID Level 4 一樣以 block 為單位儲存硬碟資料的 parity，但 parity 相關資料分散儲存於各硬碟中，不額外使用其他硬碟
* 如此一來便能分散 parity disk 的性能瓶頸

### 結論
![image](https://hackmd.io/_uploads/Sy1YHoHLp.png)
* RAID Level 4 Random write 速度受 parity disk 所限制

# File system 使用
* 檔案所附帶的屬性
	* Name
	* identifier
	* type
	* location
	* size
	* protection

* 能對檔案進行的操作
	* create
	* write
	* read
	* seek
	* delete
	* open
	* close
## File
### opening files
* Open-file table: 管理已開啟的檔案
	* 同時也會管理該檔案的 file pointer	
* File-open count: 用來記錄某檔案同時被多少人開啟
	* 目的: 當最後一個人關掉某檔案時，能將該檔案從 Open-file table 中移除
### Access Methods
* Sequential access: 只能依序存取
	* read next: 
	* write next
	* reset: 跳回檔案開頭
* Direct access: 可以隨意跳至想要讀寫的位置
### Removing Files
* 以 unix 為例，rm 指令會呼叫 unlink()
* unlink()
	* 確認該檔案的 inode number
	* 移除 link
	* inode number--，若減至0，則真正釋放檔案占用的空間

## Directory
* 硬碟可以被分成需多區(partitions)，根據用途又可以分成
	* raw partition: 跟檔案系統無關的空間，例如儲存 database
	* Formatted partition(volume): 格式化的區域(包含檔案系統)，file 便儲存至這些區域中
* Directories 便是用來管那些格式化的區域

### Single-Level Directory
所有檔案都在同一層
![image](https://hackmd.io/_uploads/S1k8xSdL6.png =80%x)


* 檔案不可以有重複的名字
* 無法分群

### two-Level Directory
多了一層使用者的 level

![image](https://hackmd.io/_uploads/SJ-ugrOLa.png)

* 對於每個使用者來說仍然是 Single-Level

### Tree-Structured Directories
使用樹狀的格式儲存

![image](https://hackmd.io/_uploads/HJnWGru8T.png =70%x)

#### Making Directories
使用 mkdir()

#### Reading Directories
使用 readdir()

#### Deleting Directories
使用 rmdir()

注意: 在移除 Directory 之前，須確保該 Directory 為空

### Hard link
> ln file file2
* 使file2 跟 file 共用同一個 inode
* inode number 存的便是有幾個 hard link 指向該檔案

### Symbolic Links (soft link)
> ln –s file file2
* file2 存的是 file 的檔案路徑
* 概念像是桌面中的捷徑

### File System Mounting
將某個檔案系統掛載到另一個檔案系統上，需指定欲掛載的位置(mounting point)
![image](https://hackmd.io/_uploads/rJlyDB_Ia.png =30%x)

# File system 實作
## Allocation Methods
將硬碟切成許多 blocks，接著進行編號

### Contiguous Allocation
概念:
* 找一段連續的 block 空間儲存檔案
* 如此一來便只需存**開頭的 block 編號**及**占用的 block 數**即可

缺點: 若要分配的空間過大，可能會發生 external fragmetation

![image](https://hackmd.io/_uploads/S1U0ZAA86.png =50%x)

### Linked
概念: 
* 每個檔案由 blocks 組成的link listed 組成
* 每個 link listed 的 node 可以是一或多個 block，靈活調整以適應系統需求

缺點: 
1. 若其中一個 node 受損，便無法找到後面的內容
2. 搜尋時間久

![image](https://hackmd.io/_uploads/SkKScSOI6.png =50%x)

### Indexed
概念:
* 為了解決 linked list 斷掉導致檔案丟失的問題
* 使用 index table 依序紀錄某檔案用到的 block 有哪些

![image](https://hackmd.io/_uploads/rJUVjS_8T.png =50%x)

## 簡單的檔案系統 (VSFS)
![image](https://hackmd.io/_uploads/BJUp4CRI6.png)
* 假設這個硬碟共有 64 個 block
* 其中 8 個用來管理檔案
	* l: 用來存放 inode (固定大小這顆硬碟能儲存的最多檔案數取決於 inode 的最大數量)
	* i: inode 的 bitmap，用來追蹤某個 inode 是否正在被使用中
	* d: data block 的 bitmap，用來追蹤某個 data block 是否正在被使用中
	* s: super block: 用來存放整個檔案系統的基本資料 (掛載檔案系統時會用到)
* 剩下的 56 個 block 用來存放資料

缺點: 沒有考慮到 locatity 的特性，同一份檔案可能會放置在硬碟中離很遠的位置，導致 performance 降低

### inode 內部結構 (也就是上面的 l)
![image](https://hackmd.io/_uploads/Bytk_00LT.png =80%x)
* direct blocks: 存放多個連結到真實資料所在處的指標
* single indirect: 為了能處理大檔案，故使用多層的形式紀錄
	* 下面的 indirect 區域同理，能處理更大的檔案

### 開檔與讀檔
![image](https://hackmd.io/_uploads/r13jxdXOp.png =80%x)
* 開檔的部分
	* 讀取根目錄的 inode
	* 透過根目錄的 inode 讀取根目錄的 data
	* 透過根目錄的 data 讀取 foo 的 inode
	* 透過 foo 的 inode 讀取 foo 的 data
	* 透過 foo 的 data 讀取 bar 的 inode
	* 透過 bar 的 inode 找到欲開啟的檔案
* 讀檔的部分
	* 讀取 bar 的 inode
	* 透過 bar 的 inode 讀取 bar 中的 data
	* 更新 bar 的 inode 的資訊 (access time)

### 創建檔案與寫檔
![image](https://hackmd.io/_uploads/rkZ0-OQup.png =80%x)
* 創建檔案的部分 (在 foo 目錄下創建一個名為 bar 的檔案)
	* 先開啟 foo 的 data
	* 讀取 inode bitmap，並分配檔案所需的空間
	* 在 foo 的 data 中寫入新建檔案的相關資訊
	* 對 bar 的 inode 進行讀寫 (寫入 create time 等資訊)

* 寫入檔案的部分
	* 從 data bitmap 中找到一段可用的空間，並配置出去
	* 寫入真正的資料到 bar 中
	* 更新 bar 的 inode 資訊
## FFS
考慮到硬碟架構，將相關的檔案放在一起

原則:
1. 將不同的 directory放在不同的 group 中 
2. 將相同目錄下的檔案放在同一個 group 中
	* 但對於大檔案，將其分割後，放置於不同 group 中 (因為大檔案可能會占用太多 group 中的空間，導致其他同目錄下的檔案放不進去)
		* 決定分割後一個區塊大小時，會希望 positioning time 跟 transfer time 消耗的時間相同
		* 所求為 average positioning time * Transfer rate

ex:
![image](https://hackmd.io/_uploads/BJ-Egk1va.png =50%x)

### 其他優化
Sub-block: 將 block 進一步細分，以解決 internal fragmentation
parameterization: block 排放的方式設定一點小 delay，以配合操作 FFS 的耗時

## Crash recovry
### FSCK (file system checker)
* 概念: 查看 file system 的狀態，嘗試找到不合理之處，並修復成合理的狀態

缺點: 仍無法完整復原寫入的資料

### Data Journaling (Write-Ahead Logging)
* 概念: 透過維護額外的一個空間(journal area)，來記錄寫入的資訊(含 data 、metadata)，以在系統發生非預期的情況時(ex: 斷電)，能夠追蹤發生了甚麼事

* ![image](https://hackmd.io/_uploads/H1ywlF-Pa.png =50%x)
	* TxB: 某筆 transaction 的開頭
	* 中間的白色區域: 某筆 transaction 的 data 、metadata
	* TxE: 某筆 transaction 的結尾，透過 commit 寫入

* 詳細步驟:
	![image](https://hackmd.io/_uploads/ryjrmtZPT.png)
	* 同時開始寫入資料到 TxB、Journal contents 中
	* 上面兩者全部完成後，進行 Journal commit (寫入 TxE)
	* 將 matadata、 data 寫入至 file system 中 (checkpoints)
	* 刪除 Journal 中的該筆 transaction


* 復原資料的方法: crash後，replay 仍留存在 Journal 的那些 transaction (若資料確定寫入硬碟的話，便不會存在於 Journal 中)

### Metadata Journaling
* Journaling 會將完整資料寫進 Journal 中，效率過低
* Metadata Journaling 只寫入關於檔案的 metadata，以加速寫入
* 詳細步驟:
	![image](https://hackmd.io/_uploads/ByRuCuWPa.png)
	* 同時開始寫入資料到 TxB、Journal contents、Data
	* 上面三者全部完成後，進行 Journal commit (寫入 TxE)
	* 將真正的 Metadata 寫進硬碟中 (checkpoints)
	* 刪除 Journal 中的該筆 transaction

Tricky case:
1. 某個 transaction 刪除某個 directory (視為 matadata)
2. 下一個 transaction 建立某個 directory並寫下一些檔案內容，使用了同一個空間
3. 這兩筆 transaction checkpoints 前，發生斷電
4. 斷電恢復後，replay 這兩筆 transaction
5. 第二筆 transaction 寫入的檔案內容被刪除!

解決方法:
1. 在確定 checkpoints 前，不使用該空間
2. 在刪除硬碟中的檔案時，順便 revoke(撤銷) journal 中對應的 transaction




















	


















