# 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