# 作業系統 Operating System 筆記
## Ch1 Introduction
作業系統(Operating system, OS)是管理電腦硬體與軟體資源的電腦程式,同時也是電腦系統的核心與基石。OS 主要有以下兩個功能:
1. 資源分配者
2. 監控使用者程式的執行,以防止不正常的運作造成對系統的危害
一個標準PC的作業系統應該提供以下的功能:
- 行程管理(Processing management)
- 記憶體管理(Memory management)
- 檔案系統(File system)
- 網路通訊(Networking)
- 安全機制(Security)
- 使用者介面(User interface)
- 驅動程式(Device drivers)
### 作業系統類別
#### 1. Mainframe Systems
最早的計算機之一,更好的可靠性與安全性,應用在醫院銀行等,由Batch演進到Time-sharing。
- Batch
OS 只是將控制權從一個程式轉移到下一個程式,缺點是一次只有一個程式且無法與使用者交互,CPU使用率低。
- Multi-programming
系統中存在多組行程同時(concurrent)執行,透過Spooling(Simultaneous Peripheral Operation On-Line)的機制讓CPU跟I/O時間重疊,提升 CPU 利用度(注意,不是並行執行(parallel))。
Multi-programming OS任務有Memory management、CPU scheduling and I/O system。
- Time-sharing
CPU在執行的時候頻繁的在跟device做交換,OS在程式間快速切換,每個程式執行幾個ms,time slot就換下一個程式。
Time-sharing OS任務有Virtual memory、File system、disk management、Process synchronization and deadlock。
#### 2. Computer-system architecture
- Desktop Systems
Personal computers (PC) – 計算機系統專用於單個用戶有GUI與許多I/O devices方便操作,像是Windows、MacOS、Unix、Linux,但是缺乏對用戶的文件和作業系統保護。
- Parallel Systems
也稱為 multiprocessor or tightly coupled system,多個CPU/核心緊密通訊,通常通過共享內存進行通信,優點是Throughput、Economical、Reliability。通常又可分為SMP與AMP。
- Symmetric multiprocessor system (SMP)
- 每個處理器運行相同的作業系統
- 最流行的多處理器架構
- 需要廣泛的同步來保護數據的完整性
- Asymmetric multiprocessor system (AMP)
- 每個處理器被分配一個特定的任務
- 一個Master CPU & 多個Slave CPU
- 常見於超大型系統中
從Processor可分為以下兩種:
- Multi-Core Processor:在同一個die上具有多個core的CPU,在chip上通訊更快,一個多核比多個單核工皓更低。
- Many-Core Processor:一個divice上有幾百個計算核心,像是Nvidia GPU,Intel Xeon Phi,TILE64等。
從Memory可分為以下兩種:
- Uniform Memory Access (UMA):目前最常見的代表是對稱多處理器(SMP),相同的處理器,內存訪問次數相等,最常見的架構。
![](./img/Ch1.UMA.jpg)
- Non-Uniform Memory Access (NUMA):通過物理連接多個SMP來實現,一個SMP可以直接訪問另一個SMP的內存,跨鏈接的內存訪問速度較慢。
![](./img/Ch1.NUMA.jpg)
- Distributed Systems
也稱為loosely coupled system,每個處理器都有本地的內存,透過I/O bus或網路相互通信,容易擴展大量節點像是Internet。
- Client-Server Distributed System:容易管理與分配資源,但是server會成為瓶頸與單一故障點。
- Peer-to-Peer Distributed System:每台機器在分佈式系統中的角色都是相同的(去中心化),像是bitTorrent,Internet。
![](./img/Ch1.Client_Server_P2P.jpg)
- Clustered Systems:集群計算機共享存儲並通過局域網 (LAN) 或更快的互連(例如 InfiniBand(高達 300Gb/s))緊密相連。
#### 3. Special-purpose Systems
- Real-Time Systems
- "Real-time"並不意味著速度,而是要keeping deadlines,保證響應和反應時間,Real-time又可以分為Soft real-time與Hard real-time。
Soft real-time requirements:不期望錯過deadline,但錯過也不嚴重,一個關鍵的real-time task獲得優先於其他任務,並保持該***優先級***直到它完成,例如多媒體串流。
Hard real-time requirements:錯過deadline會導致失敗,例如:核電站控制器,Hard real-time通常沒有Secondary storage,資料都存在short term memory或read-only memory (ROM)。
- Multimedia Systems
廣泛的應用程序,包括音頻和視頻文件,例如online TV
- Embedded Systems
移動裝置,通常內存較小,電池容量有限,處理器速度慢,顯示器較小。
### Computer system
- 用戶(User):人、機器、其他computer。
- 應用程序(Application):定義系統資源用於解決計算問題的方式。
- 作業系統(Operating System):控制和協調硬件/資源的使用。
- 硬件(Hardware):提供基本的計算資源(CPU、內存、I/O設備)。
![](./img/Ch1.General_Purpose_Operating_Systems.jpg)
#### Operating System的定義
- 資源分配器:管理和分配資源以確保效率和公平。
- 控製程序:控制用戶程序的執行和I/O設備的操作,防止計算機出錯和不當使用。
- 內核Kernel:一直運行的一個程序(其他都是系統/應用程序)。
#### Operating System的目標:
- 方便(使computer system易於使用和計算,適用於小型PC)。
- 效率(有效率使用電腦硬體,適用於大型、共享、多用戶系統)。
#### Operating System的重要性:
- 系統 API 是用戶應用程序和硬件之間的唯一接口,API是為通用目的而設計。
- OS code不允許任何錯誤,任何中斷(例如無效訪問)都會導致重啟。
- OS technology的擁有者控制著軟硬件產業(Wintel)。
- Operating systems和computer architecture相互影響。
#### Modern Operating Systems
- x86 platform:Linux,Windows
- Smartphone Mobile OS:iOS,Android
- Embedded OS:Embedded Linux,RTOS,Raspberry Pi,Xbox
### Computer-System Organization(計算機組織)
一個或多個CPU、設備控制器通過公共總線連接,提供對共享內存的訪問,並發執行CPU和競爭內存週期的設備。
每個device controller都有一個local buffers,I/O是從device到device controller的local buffers,CPU將數據從內存移動到device controller的local buffers。
![](./img/Ch1.Computer_System_Operations.jpg)
#### I/O的操作有兩種
1. Busy/wait output
CPU不斷監控等待I/O,但是CPU在監控device時不能做其他工作,很難同時進行其他I/O。
2. Interrupt I/O
Interrupt允許device改變CPU的控制流,透過改變CPU的控制流在I/O與user process間切換。
![](./img/Ch1.Interrupt_Driven_IO.jpg)
### Interrupt
現代OS都是中斷驅動的,事件的發生由來自hardware或software的中斷發出信號。
- Hardware(signal):可以隨時通過向 CPU 發送信號***signal***來觸發中斷。
- Software(trap):可能會因錯誤(被零除或無效內存訪問)或用戶對作業系統服務的請求(系統調用)而觸發中斷,稱為**trap**。
![](./img/Ch1.Interrupt.jpg)
中斷的常用功能
- 中斷一般通過中斷向量將控制轉移到中斷服務例程,中斷向量包含所有服務(即中斷處理程序)例程的地址(函數指針)。
- 中斷架構必須保存被中斷指令的地址。
- 在處理另一個中斷時禁止傳入中斷以防止丟失中斷。
### Storage-Device Hierarchy
![](./img/Ch1.Storage_Device_Hierarchy.jpg)
存儲系統的結構(**速度**、成本、揮發性),其中又可大致分為
- Main memory:CPU可以直接訪問的大型存儲介質,RAM(Random Access Memory)。
- Secondary storage:主存儲器的擴展,提供非揮發性的大存儲容量,例如Hard disk,tapes。
RAM: Random-Access Memory
- DRAM (Dynamic RAM):主記憶體(Access Speed:>= 30ns)。
- SRAM (Static RAM):CPU cache(Access Speed:10ns~30ns)。
Disk Mechanism:旋轉磁盤,並透過磁頭去去讀取(寫入)磁盤資料
Caching:使用中的資料暫時從較慢的存儲,複製到較快的存儲
>Coherency and Consistency Issue:更改register中的副本,使其與其他副本不一致
>- Single task accessing:沒有問題,總是使用Highest level copy。
>- Multi-task accessing:需要獲取最近的值。
>- Distributed system:Difficult b.c. copies are on different computers。
### Hardware Protection
#### 1.Dual-Mode Operation
- 共享系統資源需要OS保證一個不正確的程序不會導致其他程序的錯誤執行,提供硬件支持以區分至少兩種操作模式
1. User mode:代表User執行。
2. Monitor mode:也叫kernel mode或system mode,代表作業系統執行。
- hardware中加入Mode bit以指示當前模式的:kernel(0) or user(1),當interrupt/trap或故障發生時,hardware切換到Monitor mode。
![](./img/Ch1.Dual_Mode_Operation.jpg)
>Privileged instructions
>- Executed only in monitor mode
>- Requested by users (system calls)
#### 2. I/O Protection
- 所有I/O instructions都是Privileged instructions,任何I/O設備在user之間共享,必須確保user program永遠無法在monitor mode下獲得對computer的控制權。
#### 3. Memory Protection
- Memory Protection,保護中斷向量和中斷服務程序,還有數據訪問與被其他程序over-write。
HW support:兩個合法可使用地址的寄存器,超出定義範圍的內存受到保護
1. Base register:保存最小的合法物理內存地址。
2. Limit register:包含範圍的大小。
![](./img/Ch1.Use_of_Base_and_Limit_Register.jpg)
#### 4. CPU Protection
-防止user program不返回CPU控制權,造成陷入死循環或無法調用系統服務。
HW support:定時器Timer,在指定時間後interrupts computer,Timer在每個clock中遞減,當Timer達到值0時,發生interrupts。
>Timer常用於實現time sharing。
>Load-timer is a privileged instruction。
## Ch2 OS Structure
### OS Services
OS服務可大致分為以下這幾種
- User interface(用戶界面)
- Program Execution(程式執行)
- I/O operations(輸入輸出操作)
- File-system manipulation(文件系統操作)
- Communication(通訊)
- Error detection(錯誤檢測)
- Resource allocation(資源分配)
- Accounting(計數)
- Protection and security(保護和安全)
#### User interface
- CLI (Command Line Interface)
從用戶獲取命令並執行它,Shell(命令行解釋器,如CSHELL、BASH)。
- GUI (Graphic User Interface)
通常是滑鼠、鍵盤和螢幕,圖標代表文件、程序、動作等,界面中對像上的各種鼠標按鈕會導致各種操作。
#### Communication
可以使用消息傳遞或共享內存進行通信。
![](./img/Ch2.Communication_Models.jpg)
### OS-Application Interface
#### System calls
運行程序的作業系統接口,請求作業系統服務,通過軟件中斷向內核發出的明確請求,通常是組合語言。
- 進程控制——中止、創建、終止進程分配/釋放內存
- 文件管理——創建、刪除、打開、關閉文件
- 設備管理——讀、寫、重定位設備
- 信息維護——獲取時間或日期
- 通訊—發送接收信息
#### API(Application Program Interface)
用戶主要針對API進行編程而不是system call,通常由language libraries實現,例如 C Library,API 調用可能涉及零個或多個系統調用。
![](./img/Ch2.System_Calls%26API.jpg)
>Both malloc() and free() use system call brk()
>Math API functions, such as abs(), don’t need to involve system call
三個最常見的API:
1. Win32 API for Windows
2. POSIX API for POSIX-based systems (including virtually all versions of UNIX, Linux, and Mac OS X)
3. Java API for the Java virtual machine (JVM)
#### Why use API?
- 簡單(API是為應用程序而設計的)。
- 可移植性(API是統一定義的接口)。
- 效率(並非所有功能都需要OS services或涉及kernel)。
#### System Calls傳遞參數
System Calls傳遞參數通常使用以下三種方法,在運行程式和作業系統之間傳遞參數。
1. 在寄存器中傳遞參數。
2. 將參數存儲在memory中的一個table中,table的address作為參數傳遞到一個寄存器中。
3. 程式將參數push(store)入stack ,作業系統將參數pop出stack。
### OS Structure
從用戶角度,作業系統應該易於使用和學習,以及可靠、安全和快速。
從系統角度,作業系統應該易於設計、實施和維護,並且可靠、無錯誤且高效。
#### 1. Simple OS Architecture
只有一層或兩層代碼,缺點:不安全,難以增強改善。
![](./img/Ch2.Simple_OS_Architecture.jpg)
#### 2. Layered OS Architecture
下層獨立於上層,第N層只能訪問第0~(N-1)層提供的服務。優點式更容易調試與維護,缺點是效率較低,難以定義層。
![](./img/Ch2.Layered_OS_Architecture.jpg)
#### 3. Microkernel OS
盡可能多地從kernel移動到userspace像是一個program,通過消息傳遞提供通信,容易擴展和移植。缺點是效能更差,因為所有的動作都要再透過kernel。
![](./img/Ch2.Microkernel_OS.jpg)
#### 4. Modular OS Structure
大多數現代作業系統都實現了kernel modules,與Microkernel不同之處在於Modular OS全部都在kernel space。
- 使用物件導向的方法(object-oriented approach)。
- 每個核心組件都是獨立的。
- 每個模組都通過已知接口與其他模組通訊。
- 每一個模組都可以根據需要在kernel中掛載。
>kernel module實作
>http://www.linuxchix.org/content/courses/kernel_hacking/lesson8
>http://en.wikibooks.org/wiki/The_Linux_Kernel/Modules
>https://www.thc.org/papers/LKM_HACKING.html
#### 5. Virtual Machine
虛擬機採用分層方法得出其邏輯結論,它將hardware和operating system kernel視為硬體。虛擬機提供與底層bare hardware相同的接口,為每個process提供底層計算機的(virtual) copy。
由於VM是執行在user space,當遇到無效的instruction會透過interrupt去call底層的kernel space執行,而目前有hardware support的硬體是透過加一個bit去判斷kernel mode,user mode,VM mode。
![](./img/Ch2.Virtual_Machine.jpg)
>虛擬機的困難點在於"critical instruction"難以處理。
VM的用處:
- 提供對系統資源的完整保護。
- 解決系統兼容性問題的手段。
- 作業系統研究和開發的完美工具。
- 一種提高雲計算資源利用率的方法。
- 資安作滲透測試。
Vmware (Full Virtualization)
以user mode運行,作為作業系統之上的應用程序,虛擬機認為它們是在bare hardware上運行,但實際上是在user-level應用程序中運行。
![](./img/Ch2.Vmware.jpg)
Para-virtualization: Xen
向guest提供與guest系統相似但不相同的系統(必須修改guest),只有一半被虛擬化。在container (zone)processes中認為它們是系統上唯一的進程,Hardware被虛擬化(只安裝一個kernel)。
![](./img/Ch2.Para-virtualization.jpg)
#### 6. Java Virtual Machine
已編譯的 Java 程式是由Java Virtual Machine(JVM)執行與平台無關的bytecodes,Just-In-Time(JIT)編譯器可提高性能。
![](./img/Ch2.Java_Virtual_Machine.jpg)
>JVM consists of
> - class loader
> - class verifier
> - runtime interpreter
## Ch3 Processes Concept
### Process Concept
Process與Program最主要的差異,在於是否處於執行狀態
- Program (Passive entity) 只是儲存於"disk"中等待被執行的程式碼
- Process (Active entity) 則是位於記憶體中的正在被執行的"Program"
一個"Process"由以下內容組成:
Code segment:也稱text section,將 program (已編譯為instruction) 讀到記憶體中,等待被 CPU fetch 並執行
Data section:global variables
Stack:temporary local variables and functions(方便使用後直接pop掉)
Heap:dynamic allocated variables or classes
Current Activity:管理 Process 的 Meta data,如Program counter, Register Contents
A set of associated resources:相關資源的 set,如 Open file handlers (檔案的開啟、或是跟 OS 要某些硬體的控制權)
![](./img/Ch3.Process_in_Memory.jpg)
#### 1. Threads
Threads 又稱為 Lightweight process,是使用 CPU 的最小單位,同 Process 的 Threads 有共享的記憶體空間。在 Parent Process 創造 Threads 時就會 allocate ,因此省去在空間管理及處理上的執行動作。
![](img/Ch3.Threads.jpg)
- 同Process中,Threads共用以下內容
- code section
- data section
- OS resources (e.g. open files and signals)
- 但以下部份各自獨立
- stack
- register set
- thread ID
- program counter
#### 2. Process State
Process 在其生命週期中,共有 5 個狀態
![](img/Ch3.Diagram_of_Process_State.jpg)
- New:當 process 被創建時,將 code 讀到記憶體中,並將前述記憶體狀態初始化
- Ready: Process 競爭的資源是 CPU 的核心,管理的方式為佇列 (Queue),在等待被執行的階段,會被放在佇列當中,此狀態稱為"Ready"
- Running:被 CPU 排程選到,執行 instructions。OS 為了確保主控權,一段時間就會將 CPU switch 給 scheduler 而"打斷(interrupt)"程序
- Waiting:有些指令不會直接使用 CPU (如 I/O),因為不需要 CPU ,又需等待某些指令完成,所以進入 Waiting 狀態,待完成後重新放回 Ready
- Terminated:Process 完成執行,釋放資源給 OS。
#### 3. Process Control Block (PCB)
User 創建 Process 後,OS 會自動建立 PCB 來做管理。
我們說將 Process 放到佇列當中,指得是將 Process 的 pointer 放到佇列中,而非記憶體空間,使用的資料結構為"Linked list"。PCB 包含以下內容:
- Process state
- Program counter
- CPU registers
此外還有:
- CPU scheduling information(e.g. priority)
- memory management information(e.g. base/limit register):只有 process 在執行時才會把這兩個值從 memory load 進 CPU 的 register。
- I/O status/information
- accounting information
![](img/Ch3.Process_Control_Block.jpg)
#### 4. Context Switch
CPU 一次只能執行一個 Process,要切換給另一個 Process 時,必須將舊 Process 的資訊 (e.g. PCB) 儲存起來,並載入新 Process 的資訊,這個動作稱為 「Context Switch」。
CPU 完整切換的流程如下:
1. 執行 P0
2. P0 被打斷讓出 CPU ,此時處於 idle 狀態
3. 進行 Context Switch
4. 將 P0 的狀態存於 PCB
5. 並讀取 P1 的 PCB。
6. 完成 Context Switch
7. 執行 P1
![](img/Ch3.context_switch.jpg)
Context switch 的過程中, CPU 沒有執行任何 instrunction,因此 Context Switch 其實是 overhead,浪費 CPU 的資源。但為了 CPU sharing 及 time sharing,Context switch 無法避免地經常發生,所以我們只能盡可能縮短時間:
- memory speed
- number of registers:越少則存取的次數降低,但現在的系統中為了更快速的計算,register 數量是變多的
- special instructions:合併 load & save PCB 的 instruction,讓 CPU 一次做完
- hardware support:multiple sets of registers,一次記住多個 Process 的狀態,可以快速的在 registers 間進行切換,無須透過 memory access
### Process Scheduling
- Multiprogramming:多工處理, CPU 可以執行多個 Process,以最大化利用 CPU
- Time sharing:分時系統,為一種資源的共享方式,系統可以頻繁地進行 Context Switch,讓多個 User 可以同時使用 Program
由於 Process 共享 CPU,使用的先後順序就是由 scheduler 來決定。
#### 1. Process Scheduling Queues
- Job queue (New State): 系統中所有的 Process
- Ready queue (Ready State): 在主記憶體中正在及等待執行的 Process
- Device queue (Wait State): 等待 I/O 處理的 Process
![](img/Ch3.Process_Scheduling_Queues.jpg)
>- 會有一個 ready queue for CPU,很多個 I/O 的 waiting queue,看是哪個 device 或 disk,會有自己獨立的 queue,利用 PCB 中的 pointer 串起來
>- queue 之間的 tail 和 head 都有串起來,比較容易做 reordering,不同的 Scheduling Algorithm,可能會 dynamically 決定 ordering (run time做調整),需要其他 pointer 串起來,變成雙向或把前、後記起來
>- ready queue 可能會有 level 1、level 2、level 3,取決於 Scheduling Algorithm 自身管理方式
![](img/Ch3.Process_Scheduling_Diagram.jpg)
- Ready queue 中的 process 會等待被 load 進 CPU
- Waiting queue 發生的原因有很多種,OS 會有專門的 queue 去處理這些 event 的發生:
- I/O request:若自己發出 I/O request,這時候會被放到 I/O queue,等到做完 I/O,會 throw 一個 interrupt 回來,OS就會把它放回 ready queue,e.g. call printf(),會被放到 monitor queue
- time slice expired:timer的 alarm 被 fire,會從 CPU(running state) 直接回到 ready queue,這是為了確保主控權可以回到OS的手上
- fork a child:process 可以自己 create process,在Linux中,create process的動作叫做"fork"有兩種實做方式
1. 可能自己繼續執行讓children等Parent執行完在執行
2. 先讓children先執行,原來的Parent process會被放在waiting queue,等到Children執行完,才可以再回到ready queue執行
- wait for an interrupt:e.g. call sleep() 的 system call,call sleep() 的 process 會被放在 waiting queue 裡,等著 interrupt 的發生,才可以回到 ready queue 裡面
#### 2. Schedulers
Scheduling 分為 CPU 及 Job Scheduling
- CPU Scheduling : 因為 time sharing 的原因,發生頻率非常高,又稱為 Short-term scheduler。CPU Scheduler 會選擇該被執行的 Process 並 Load 到 CPU 當中,從"Ready State" 轉換成 "Run State"
- Job Scheduling : 可能幾秒或幾分鐘才需要scheduling,又稱為 Long-term scheduler,通常是使用者人為觸發,新的程式被執行,產生程序時才需要被排程,從 "New State" 轉換成 "Ready State"
- Medium-term : 跟虛擬記憶體結合產生的一種 scheduler,因為 memory 空間是有限的,有時會將 disk 的空間拿來使用,當 memory 不夠時,就會將部份 Process 從 memory 踢回 disk,這個動作即由 Medium-term scheduler 來處理,由 "Ready state" 轉換為 "Wait state"
![](img/Ch3.Schedulers.jpg)
#### 3. Long-Term Scheduler
- 控制在記憶體中 Process 的數量 (degree of multi-programming)
- 當 degree 太低時, CPU 有很多的時間在 idle
- 當 degree 太高時,會發生 Thrashing,有太多的 Process 在爭搶有限的 memory,導致 Process 一直在 memory 跟 disk 之間 swap,不斷的做 I/O
- 因我們希望 CPU 跟 I/O 執行的時間差不多,負載盡可能平衡而最大化系統效能,所以 Process 要能夠在 CPU-Bound & I/O-Bound 良好的混合
- 在 UNIX/NT 系統中,並沒有 Long-Term Scheduler
直接將 Process 放到 short-term scheduler 中 (現今電腦的記憶體空間通常是足夠的)
Multipropramming degree 受硬體限制 (e.g. # of terminals) 或著由使用者自己調整
#### 4. Short-Term Scheduler
- 執行頻率很高,大約 100ms 執行一次
- 由演算法來縮短每個 Process 的等待時間
- 因為效率要高,演算法也不能太過複雜,避免 CPU overhead 過高
overhead = (time of pick) / (time of execution + time of pick)
![](img/Ch3.Short-Term_Scheduler.jpg)
#### 5. Medium-Term Scheduler
因現代 memory 空間的增長以及虛擬記憶體的觀念引入,過往由 Long-Term Schduler 處理的動作多改由 Medium-Term Schduler 執行
- swap out : 將 Process 由 memory 搬到 disk 中
- swap in : 將 Process 由 disk 搬到 memory 中
- propose : 改善 Process mix / 減少記憶體中的 Process 數量,降低 degree釋放記憶體空間
![](img/Ch3.Medium-Term_Schduler.jpg)
### Operations on Processes
回到 programmer 及 user 角度,我們到底如何跟 Process 溝通
#### 1. Tree of Processes
- 無論什麼作業系統, Processes 一定可以畫成如下的樹狀結構
- 每一個 Process 由 unique processor identifier (pid) 識別
![](img/Ch3.Tree_of_Processes.jpg)
#### 2. Process Creation
- 資源之間的關聯性可能如下
- Process 的 Parent 及 Child 共享資源
- Child Process 共享部份 Parent 的資源
- Parent 及 Child 完全不分享資訊
- 兩種可能的執行方式
- Parent 及 Children 同時執行
- Parent 等到 Children 結束後才執行
- 兩種可能的記憶體空間
- Child 為 Parent 的複製 (執行碼、counter 等與 Parent 完全一樣),透過 sharing variable 溝通 (address 不同)
- 將特定的 program 丟進 Child 的 code section,重設 counter,讓 Child 變成完全不同狀態的 Process,透過 message passing 溝通
#### 3. UNIX/Linux Process Creation
[Reference](https://hackmd.io/@Chang-Chia-Chi/MIT6S081_XV6_and_Unix#find-moderate)
- fork system call (Create Process)
- 創建新的 (child) process
- 該 process 複製其 parent 的記憶體空間
- Child 與 Parent 在 fork 後同時執行 (execution concurrently)
- Child 在 fork 的回傳值為 0
- Parent 在 fork 的回傳值為 Child process 的 PID
- execlp system call (Reset Process)
- 先創建 Process 及 OS 要記憶體空間,並註冊新的 PID
- 將新的 binary file 讀入記憶體,並將內容如變數 (heap、stack)、counter 等全部清空 (reset) ,等於讓新的 Child Process 執行不同的程式
- wait system call
- 因 Child & Parent 在 Unix 中是 concurrent 執行,若要控制執行的順序,必須用 wait system call,強制 parent 直到其中一個 Child 的 process 完成後才執行
- Memory space of fork()
- 舊的實作 Child 為 Parent 的完整複製
- 新的實作使用 copy-on-wirte 技術,在 Run time 過程中,儲存 Child 的與 Parent 的不同處 (A’s Child 在執行過程中會慢慢增長)
- 若 call execlp,則會產生完全獨立的 Child Process
![](img/Ch3.Child_Process.jpg)
以下為 UNIX/Linux Process Creation 的範例程式碼
![](img/Ch3.Linux_Example.jpg)
以下範例簡單示意 fork 的特性, Child Process 會保存 fork() 呼叫當下 Parent Process 的狀態。注意如果過程中呼叫 execlp ,則該分支會斷掉,樹不會繼續往下生長
![](img/Ch3.Example_Quiz.jpg)
#### 4. Process Termination
當 Process 執行到最後一個指令或是呼叫 exit(),都會中止程序
- 該 Process 所有相關資源,如記憶體空間 (physical, virtual)、I/O buffer、open files 等都會清空並將資源還給 OS
- Parent 可利用 PID(abort) 來中止特定 Child Process
- 當 Child 配置太多記憶體
- Child Process 執行的任務不再需要
- Cascading termination:因為是樹狀結構,所以當 Parent 被中止,則 Child Process 也會被中止,這也是為什麼 "ctrl+C" 可以強制停止 Process,因為其他程序都是由 console 程序創建,更進階的是呼叫 "kill",但需要管理者權限
### Interprocess Communication
- 定義:在多個 Process (或 Threads) 間溝通的機制
- 目的:
- 資訊的共享
- 加快計算速度 (not always true)
- 便利性 (performs several tasks at one time)
- modularity (e.g. [micro-kernel](https://en.wikipedia.org/wiki/Microkernel))
Communication Methods
要做到 IPC 用 memory 分別有幾種方式
- Shared memory:共用記憶體空間
- 需要小心處理 synchronization
- 利用 memory address 實作,好處就是快
- 透過 memory 的 address 存取資料
- Message passing:透過 memory 的 copy,網路或跨電腦的程式通常都是以這種作法溝通
- 沒有 conflict 的問題,但若資料小,用這個方法的話可以省去 share memory 的 lock 之類的額外資源消耗,反而會略快(且不用在意 sync 的問題)
- 使用 send/recv message
- 以 system call 實作,通常較慢
![](img/Ch3.Msg_Passing&Shared_Memory.jpg)
- Sockets
- 透過 IP & Port 來 identify 使用者,port number 指得就是 Process
- 藉由未結構化的位元資料來溝通 (unstructed stream of bytes)
- 會有 client & server
- server 要先打開 client 才能連
- client 進行 connect 之後,就可以盡情 read / write
- 補充:server 在 accept 之後,會動態開 thread,如此才能一次處理多個 request
![](img/Ch3.Sockets.jpg)
- [Remote Procedure Calls (RPC)](https://en.wikipedia.org/wiki/Remote_procedure_call)
- 可以呼叫其他的 Process 甚至其他電腦的 Process
- 參數以及回傳值以 message 傳遞
- 與 socket 不同,傳輸的資料是有 data type 的
- Library 通常會有 stubs,在 client 端和 server 端會各有一隻小程式,(server 端的叫 skeleton)會負責 implement 細節
- 其實通常下面也可能用 socket,只是幫 programmer 包起來而已
- [parameter mashaling](https://en.wikipedia.org/wiki/Marshalling_(computer_science)): 把 params 包進 message 且嚴謹的管理。要把兩台電腦串在一起其實有許多細節要處理,RPC 就要負責幫使用者把這些繁瑣的事情解決掉
![](img/Ch3.Remote_Procedure_Calls.jpg)
> In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared network), which is coded as if it were a normal (local) procedure call, without the programmer explicitly coding the details for the remote interaction.
#### 1. Shared Memory
Process 要能夠 …
- 創建 shared memory 的區塊
- 共享的記憶體區塊通常會在創建 shared memory 的 Process 的附近
- 透過這個 shared memory 溝通的 Processes,要告知 OS 說我要共用這塊記憶體位置 (attach)
- 資料格式為 bytes,由 process 自行定義, OS 不處理
- Processes 不能同時對同一塊記憶體做讀寫的動作
Consumer & Producer Problem
- 兩個 Process 共用一塊 Buffer 記憶體
- Buffer 是大小為 B 的 circular array
- 因為 Buffer 有空間限制,必須有以下資訊來控制
- next free: in
- first available: out
- empty: in = out (out 拿光)
- full: (in + 1) % B = out
- 這邊為了 sync,避免無法判斷 in = out 時是 empty 還是 full,必須使用 in+1 留一個空格。之後會提到以 locking 的方式使用所有的 Buffer 空間
![](img/Ch3.Buffer.jpg)
#### 2. Message Passing
- 讓 Process 之間可以溝通且同步
- IPC 一般來說 OS 會提供兩種操作:Send/Receive 的方式達到同步化,並在背後做各種 memory copy 的處理
- Send(message)
- Receive(message)
- Message system : Process 不使用 shared variable 達到溝通的目的
- 為了 Process 間的溝通
- 必須建立 communication link
- 藉由 Send/Receive 交換資訊
- 實作 communication link 的方式
- physical (e.g. shared memory, HW bus or network)
- logical (e.g. logical properties, programmer 在意的事情)
- communication 是否有方向性
- 角色是否相對
- Blocking / non-blocking:傳輸行為執行完成才會回傳,或不管對方是否有收到立刻回傳
- send by copy or by reference
- 固定大小 / 可變大小 的 message
Direct communication (如打電話)
- Processes 必須要有對方的名稱
- Send(P, message) : 傳輸 message 給 Process P
- Receive(Q, message) : 從 Process Q 接收 message
- communication link 的特性
- Links 通常為自動建立
- links & processes 為一對一的關係
- link 可以是單向,但通常是雙向
- Direct communication 最大的缺點是關係唯一對一,且連線的對象無法變更,除非重新建立,所以比較難以模組化 (modularity)
Indirect communication (如寄電子郵件)
- Messsages 由 mailboxes(或稱為 port) 導向及接受
- 每一個 mailbox 有自己的 ID
- Processes 若共享 mailbox 則可以互相溝通
- Send(A, message):沒有指定接收者,單純將 message 放到 mailbox A
- Receive(A, message):沒有指定傳送者,單純將 message 由 mailbox A 接收
- communication link 的特性
- Link 必須由 programmer 建立,透過 Process 間共享 mailbox 來溝通
- 因為沒有指定接收者/傳送者,所以 Link 與 Process 間為 Many-to-Many
- Link 可以為單向或雙向
- Mailbox 可以由 Process 或 OS 擁有,通常處理 message 的是另一隻程式
- Mailbox Sharing
![](img/Ch3.Mailbox_sharing.jpg)
- 因為可能同時有很多人去讀取 message,但理論上一個 message 應該只能有一個人拿到,有以下幾種解決方法:
- Link 只允許兩個 Process (一個 send 及一個 receive,等同 direct,最糟的解法)
- 利用 lock 一次只允許一個 Process 拿取 message
- 讓 mailbox 自行決定誰來接收。receiver 會先提出要求, mailbox 決定接收者後, sender 會被告知 receiver 是誰,再告知 receiver 來取得 message
Synchronization
- Message passing 可以為 blocking (synchronous)或是 non-blocking (asynchronous)
- Blocking send: sender 被 blocked 直到 message 被 receiver 或 mailbox 接收才 return
- Nonblocking send: sender 送出 message 之後繼續執行
- Blocking receive: receiver 被 blocked 直到皆收到 message 才 return
- Nonblocking receive:若 sender 先 call,則 receiver 正常接收 message ; 若 sender 前 call,則 receiver 只會收到之前的值。所以 function call 通常還會 return 一個 token ,來確定是否真的收到 message
- Buffer implementation : non-blocking 通常透過 buffer 實作,只要 buffer 空間沒有滿,就可以一直 send ; 對 receiver 來說就是一直接收,不管 buffer 有沒有東西
- Zero capacity : 通常是 blocking send/receive
- Bounded capacity : 若 buffer 滿了, sender 會被 blocked,或是 return 一個 error
- Unbounded capacity : 一直送直到系統滿了為止
#### 3. Socket
socket只負責建立channel,passing由programmer負責。
- server端先開一個socket,
- bind到一個port,OS才知道哪個人要用這個port做溝通
- server要先listen,等人進來
- client 開socket,然後connect過去server
- server等到有人就accept,建立兩邊通訊
- 建立起來就可以read,write
- 結束後兩端可以選擇close,server close後就沒人可以連進去
![](img/Ch3.Sockets.jpg)
>大部分web server或是socket programming,都是設計可以讓許多人連線,如果server是single thread,就只能handle一個user,所以實際上accept後,server side會去creat一個thread(動態建立)。
#### 4. Remote Procedure Calls
socket只能傳bytes,RDC能夠傳struct,類似透過網路remote的functon call。
RDC有stubs在client與server運行,幫忙處理parmeter,把東西package跟unpackage,底層再透過socket傳輸。
![](img/Ch3.Stubs.jpg)
RDC可以跨電腦(eg.big/small endian的問題,32跟64位元int定義長度不同),marshaling需要處理不同平台的問題;如果要pass pointer大部分的RDC是不支援的,因為pointer指向可能會用到的memory都要copy。
## Ch8 Memory Management
### Background
- 主記憶體和暫存器為唯二 CPU 可以直接存取資料的地方
- Processes 在硬碟中等待被讀入記憶體中及被執行
- 多個 program 被讀入記憶體中可以改進資源使用率以及反應時間
- Process 在執行期間有可能在硬碟及記憶體之間移動
![](img/Ch8.Multistep_Processing_of_a_User_Program.jpg)
#### How to refer memory in a program? – Address Binding
- Compile Time
程式中的資料、參數等必須要有對應的記憶體位置,早期的作法是在編譯期間,就決定要放在哪裡,送進 CPU 時 CPU 就知道該去哪裡存取資料
- Compiler 會將組合語言轉換成實際的記憶體位置(absolute code)
- 若起始地址變更(可能該地址被其他 process 占住),則需要重新編譯 (recompile),等於要關閉並重新執行
- ex: MS-DOS .COM format
![](img/Ch8.Compile_Time.jpg)
- Load Time
- Compiler 不會決定實際的記憶體地址,而是會留一個變數(Base Register),給出相對位置。程式讀取後(loader,loader 一般假設程式是重新執行,因此會執行非常多動作,如 process 的 control block、記憶體初始化、data structure 的創建等,也就是 program 變成 process 的過程),才透過該變數決定真正的地址(relocatable code)
- 若 run time 起始地址變更,則需要重新讀取 (reload)
![](img/Ch8.Load_Time.jpg)
- Execution Time
- Compiler 將組合語言轉換成 logical-address(i.e. virtual-address)
- 雖然看似 compile time 決定實際地址,但這其實是個虛擬的地址。
- CPU 以為記憶體地址就是 compiler 轉換的位置,但送要求去記憶體存取資料時,中間還有一個硬體單元 MMU(i.e. Memory-Management-Unit, MMU),會做記憶體地址的轉換,導向正確的實際地址
- MMU 將虛擬地址 map 為實際地址
- 由 logical-address 加上 relocation register 產生實際地址
![](img/Ch8.MMU.jpg)
- MMU 可以看成 OS 的一部分,但因為使用頻率非常高,所以一般用硬體來實作
- 多數 general-purpose 的作業系統使用這個方法
![](img/Ch8.Execution_Time.jpg)
- Logical vs. Physical Address
- Logical address 為 CPU 送出的地址,也就是 virtual address
- Physical address 為記憶體看到的真實地址
- Compile time & Load time address binding 的方式,logical address = physical address
- Execution-time address binding 的方式,logical address != physical
- User program(Programming, Compiling…etc) 只管 "logical" address,永遠不會看到 "physical" address
### How to load a program into memory? – static/dynamic loading and linking
#### Dynamica Loading
- 不會將整個程式 load 到記憶體,子程式在 function call 的時候才 load
- 這樣做有更好的記憶體空間使用
- 未用到 routine 不會被載入記憶體
- 在大量程式碼使用率較低時(如 error handling code)特別有用
- OS 提供功能,但不會決定 routine 是 dynamic 或 static loading,而是由使用者決定(library, API)
- 在 C 語言中使用 Dynamic Loading 的範例,當呼叫 ``` printf("%f\n", (*cosine)(2.0)) ```時,``` cosine ```才會讀到記憶體中。現在 programmer 使用 Dynamic Loading 常常是希望在 run time 決定該使用哪個函式。
![](img/Ch8.Dynamic_Loading.jpg)
![](img/Ch8.Dynamic_Loading_Example.jpg)
#### Static Linking
- 函式庫透過 loader 被結合進 program 的 image 當中,compile 的時候,將函式庫加入程式碼
- 每一個用到函式庫的 program 都有一份,浪費記憶體資源,但執行比較快
- 即便改用 Static Linking 加上 Dynamic Loading,因為函式庫仍然需要,因此無法解決程式碼重複複製的問題
![](img/Ch8.Static_Linking.jpg)
#### Dynamic Linking
- Dynamic Linking 在 run time 才去找連結
- 因此只需要一份 library 在記憶體當中
- OS 在 program 中加入 stub,call stub 的時候會向 OS 確認 library 是否在記憶體中
- stub call --> 找 referred lib 是否在 memory 中,沒有的話則 Load 進來 --> 將其位址替換掉,之後就不用經過 stub(relocation)
- 因為在 run time 時期才找函式庫,執行速度較慢
在 windows 中為了系統效能,是編譯成 DLL(Dynamic link library)
![](img/Ch8.Dynamic_Linking.jpg)
### How to move a program between mem. & disk? – Swap
- Process 可以從記憶體中 swap 到 backing store,稍後再 swap 回記憶體中執行
- virtual memory 及 mid-term scheduler 使用
- backing store 是與檔案系統(file system)分離的一塊硬碟空間,透過 OS 的 MMU 直接管理
- 為何需要 Swap
- 釋放 memory space
- 讓重要性較低的 process roll out,roll in 重要性較高的 process
- swap back memory location
- 若 binding address 是在編譯或 load 時期決定,則 swap 回去的記憶體位置要相同
- 若 binding address 是在 run time 動態決定,則 swap 回去的記憶體位置可以不同
- 程序被 swap 前, OS 會確認是否是閒置的(idle),除 CPU 執行外,也不能執行 I/O
- 若想 swap 在 I/O pending 的 process,有以下解法:
- 乾脆不要 swap 的 I/O pending 的 process
- 做完的 I/O 先copy 資料到 OS buffers(memory space 不屬於任何 user process),就能把 process swap 出去,swap 回來時,只要 OS buffer 中的資料複製到 process 的 buffer 即可
- swap 主要時間花在資料的傳輸,transfer time 與資料傳輸的大小成正比,該如何決定被 swap 的 process 非常重要,詳見 CH9 - virtual memory
### Contiguous Memory Allocation
相關資料 [CS:APP Malloc Lab 解題筆記](https://hackmd.io/@Chang-Chia-Chi/r13xrF0Vt)
#### Memory Allocation
- Fixed-partition allocation:
- 每個 process 讀進一個 fixed-size 的 partition
- Multi-programming 的程度由 partition 的數量決定
- Variable-size partition (Multiple Partition)
- Hole 定義為一塊連續的記憶體位置
- 不同大小的 Holes 散布在記憶體當中
- 當有 process 抵達,會被 allocate 在一個夠大的 hole 裡頭
- OS 會理 in-use 及 free 的 hole 的資訊
- 一個被釋放的 hole 能夠跟其他 hole 合併成一個更大的 hole
![](img/Ch8.Multiple_Partition.jpg)
#### Dynamic Storage Allocation Problem
- 問題:如何從一串 free holes 中找出符合大小 n 要求的 hole
- 解法:
- First fit - 1st 符合的 hole 就直接配置
- Best fit - 配置大小最剛好的 hole
- 可能要搜尋整個 hole list
- Worst fit - 配置最大的 hole
- 可能要搜尋整個 hole list
- First fit and Best fit 無論在速度或空間使用上都比 Worst fit 好
#### Fragmentation
- External fragmentation (frag space 在 process 外)
- 全部的 free memory space 是足夠滿足需求的,但卻不是連續的
- 發生在 variable-size allocation
- 解法: Compaction
- 將記憶體空間做 shuffle,在 execution time 讓 free memory 結合成一整塊
- 只有 binding address 在 run time 決定才能使用
![](img/Ch8.Fragmentation.jpg)
### Non-Contiguous Memory Allocation — Paging
相關資料 [MIT6.s081 Lab: page tables](https://hackmd.io/@Chang-Chia-Chi/rkPuUJVaY)
#### Paging Concept
- Method:
- 將 physical memory 切成 fixed-sized blocks 稱為 frames
- 將 logical address space 切成一樣大小的 blocks 稱為 pages
- 為了要 run 一個有 n pages 的 program,需要找 n 個 free frames 並載入 program
- OS 會持續追蹤 free frames,以 page table 建立 logical to physical address 的轉換關係
- Benefit:
- 允許 process 的 physical-address space 可以是不連續的
- 避免 external fragmentation
- 盡可能降低 internal fragmentation
- 提供 shared memory/pages
#### Page Table
- 每一個 entry 對應到在 physical memory 中的 page 的 base address
- 每一個 process 有一個 page table,由 OS 直接進行管理
- Page table 只包含 process 擁有的 pages
- process 無法存取不是其 space 的記憶體地址
![](img/Ch8.Page_table.jpg)
#### Address Translation Scheme
- Logical address 分成兩部份
- Page number ( p )
- 為 page table 的 key 值,對應 value 為 page 在 physical memory 的 base address
- N bits 代表一個 process 最多可配置 2<sup>N</sup> 個 pages 的記憶體,限制住 programm 可 allocate 的大小 (例如 32-bits 的電腦,一個 program 最多 allocate 4GB 左右的空間)
- Page offset ( d )
- 與 base address 結合定義 physical memory address
- N bits 代表 page size 為 2<sup>N</sup>
- 由 page number 找出放在第幾個 page,再加上 page offset 得到實際的記憶體位置
- physical address = page base address + page offset
- 如果 page size 為 1kb(2^10) 且 page 2 map 到 frame 5
- 若 logical address 為 13 bits (p=2, d=20),則 physical address 為?
- 5 * 1kB + 20 = 1,010,000,000,000 + 0,000,010,100 = 1,010,000,010,100
![](img/Ch8.Address_Translation_Architecture.jpg)
- pages 的總數量不一定和 frames 的總數量相同
- pages 的總數量決定 process 的 logical memory size
- frames 的總數量則與 physical memory 大小有關
- 例如: 給定一個 32-bits 的 logical address,36 bits 的 physical address 以及 4KB 的 page size,這代表?
- page table size = 2^32 / 2^12 = 2^20 個 entries
- Max program memory: 2^32 = 4GB
- Total physical memory size = 2^36 = 64GB
- Number of bits for page number = 2^20 pages -> 20 bits
- Number of bits for frame number = 2^24 frames -> 24 bits
- Number of bits for page offset = 4KB page size = 2^12 bytes -> 12 bits
#### Free Frames
OS 用 free frame list 記下所有 free 的 frame
![](img/Ch8.Free_Frames.jpg)
#### Page/Frame Size
- page & frame size 由 hardware 定義
- 通常是 2 的冪次
- 通常從 512 bytes 到 16 MB/page
- 最常見的是 4KB/8KB(32bit -> 4KB;64bit -> 8KB)
- Internal fragmentation?
- Larger page size -> 更多空間的浪費
- 但因為以下原因, page sizes 有變大的趨勢
- 記憶體、process、data sets 變得更大, process 變大代表要 access 的 page 也越多,若 page size 太小一來找尋的次數增加,二來因為 page 太多很可能部份的 page 放在 disk 中,降低執行及讀取速度
- 更好的 I/O performance(during page fault)
- page table 可以比較小
#### Page Summary
- 使用者看到的是連續性的記憶體,但實際上是分散的
- OS 為每個 process 維持一個 page table
- OS 維持 frame table 管理 physical memory
- 每個 frame 有自己的 entry
- 表示 frame 是 free 還是 allocated
- entry 記 " process id " 和 " page number "
#### Implementation of Page Table
- Page table 保存在記憶體中
- 因為做 translate 的為 MMU,而 MMU 是硬體單元,所以 Page table 在記憶體中的位置會存在 Page-table base register (PTBR)
- 記憶 Page table 在記憶體的位置
- PTBR value 存在 Process Control Block 裡頭(PCB)
- 在 Context switch 時更新 PTBR value (除了 CPU 的 register,MMU 的 register 也要重新 load)
- 每一次讀取記憶體實際上動作有兩步
- 第一步先找到 Page Table,mapping 後才到真實的記憶體地址存取資料
- 兩步讀取的動作可以由 Translation Look-aside Buffers (TLB) 解決,簡單來說是一個 cache,若 cache hit,則可以縮短為 1 步,以 Asscociative Memory 實作
#### Asscociative Memory
- Associative Memory 為硬體,所有的 memory entires 可以同時被讀取 (查詢為 O(1))
- 每一個 entry 對應一個 associative register
- 但 entries 的數量是有限制的,一般來說是 64~1024 左右
![](img/Ch8.Associative_Memory.jpg)
#### Translation Look-aside Buffer (TLB)
- 由 Associative Memory 組成,是硬體單元
- page table 的快取被所有的 process 共享
- Context switch 後,因為 process 不同,page table 也不同,一般會 flush 整個 TLB; 另一個解法是在 TLB entry 增加 PID 的欄位,同時符合 page number 及 PID 才叫做 hit (address-space identifiers(ASIDs))
- 因為 context switch 後 TLB 被清空,等於剛開始記憶體地址都要兩步才能取得,是 context switch 拖慢效能的主要原因
![](img/Ch8.Translation_Look-aside_Buffer.jpg)
#### Effective Memory-Access Time
- 20ns for TLB search
- 100 ns for memory access
- Effective Memory-Access Time (EMAT)
- 假設 70% TLB hit-ratio: EMAT = 0.7x(20+100) + (1-0.7)x(20+100+100) = 150 ns
- 假設 98% TLB hit-ratio: EMAT = 0.98x120 + 0.02x220 = 122 ns
#### Memory Protection
- page table 中,每一個 page 都有 protection bit ,表示 “唯讀” 或 “可讀寫”
- valid-invalid bit
- Valid: page/frame 在 process 的 logical address space,process 可以讀取
- Invalid: page/frame 不在 process 的 logical address space ,禁止 process 存取
- 早期會先創建固定大小的 page table,因為 program 會長,有可能目前 page 的數量少於 page table 的大小,若此時有 pointer 不小心指到該處,就會報 segmentation fault
- 缺點是可能很多 entry 沒有用到 -> 使用 page table length register (PTLR),動態變更 page table 的大小。
- program 配置記憶體以 byte 為單位,而 valid-invalid bit 以 page 為單位,因此需要 memory limit register 紀錄 program 真正使用的記憶體空間,限制無效的存取
![](img/Ch8.Valid-Invalid_Bit_Example.jpg)
#### Shared Pages
- 允許 page 在不同 processes 間共享相同的 Reentrant code(read only)
- Reentrant code (Pure code)
- 在執行期間不會改變
- 如 text editors, compilers, web servers, etc
- 在 physical memory 中只需要保存一份 shared code
- Process 保有自己的 private data 以及 code
- 數個 virtual address map 到相同的 physical address
![](img/Ch8.Shared_Pages_by_Page_Table.jpg)
#### Page Table Memory Structure
- Page table 有可能很大而且難以存取
- 4GB (2^32) logical address space with 4KB (2^12) page
- 要有 1 million(2^20) page table entry
- 若一個 entry 4 bytes,則 page table 要 4MB,又因為 page table 是給 MMU 讀取的,MMU 沒有 address translation,所以這 4MB 的空間必須連續,但 physical address 通常很難找到 4MB 的連續空間
- 需要將 page table 切分成數個較小的 page tables,最好在一個 page size 大小(4KB) 的範圍內
- 或著將 page table 的總大小限縮
- 解法:
- Hierarchical Paging
- Hash Page Tables
- Inverted Page Table
#### Hierarchical Paging
將 page table 拆成數個較小的 table,將 logical address space 化成多層 page tables (n-level page table),但因為多層的結構,實際上總 entry 是增加的。ex: A[1000] -> A[10][100] -> A[10][10][10]
![](img/Ch8.Two-level_paging-1.jpg)
![](img/Ch8.Two-level_paging-2.jpg)
![](img/Ch8.Two-level_paging-3.jpg)
- 64-bit Address
- 如果切成 42(p1) + 10(p2) + 12(offset)
- outer table 需要 2^42 X 4B = 16TB 的連續記憶體!!
- 如果切成 12(p1) + 10(p2) + 10(p3) + 10(p4) + 10(p5) + 12(offset)
- outer table 需要 2^12 X 4B = 16KB 的連續記憶體
- 但記憶體存取次數變成 6 倍
#### Hashed Page Table
- 通常用在 address > 32 bits
- Virtual page number 會被 hash 進 hash table
- Hash table 的大小影響 bucket 的 chain 的長度
- 每一個 hash table 的 entry 包含以下資訊
- Virtual Page Number, Frame Number, Next Pointer
- 不過 pointer 的結構會浪費額外記憶體。另外,linked-list 搜尋時間為 O(N)
![](img/Ch8.Hashed_Page_Table.jpg)
- 一個改善的實作是將 linked-list 的 element 設為 array,降低 linked-list traverse 的次數
![](img/Ch8.Improved_Hashed_Page.jpg)
#### Inverted Page Table
- 不使用 page table,而是改用 frame table, entry 為 PID + Page Number
- 若使用 page table,當 process 數量越來越多, page table 的數量跟著增加
- frame table 只需要一個,可以事先配置,且使用率接近 100%,減少儲存 table 的記憶體需求
- 但存取時要搜尋整個 table,增加存取的時間
- 最麻煩的是難以實作 page 的 sharing,所以這種作法比較少見
![](img/Ch8.Inverted_Page_Table.jpg)
### Non-Contiguous Memory Allocation — Segmentation
#### Segmentation
- 以使用者的角度來決定如何切割記憶體
- 一個 program 可以看成 segments 的集合,segment 是一個 logic unit,可能包含:
- main program
- function, object
- local/global variables
- stack, symbol table
- arrays, etc…
![](img/Ch8.User_View_of_Memory.jpg)![](img/Ch8.Logical_View_of_Segmentation.jpg)
#### Segmentation Table
- Logical address:(seg#, offset)
- Offset 可以描述的長度不受 program 長度限制,只受限於系統定義 program 最多可配置多少記憶體有關
- Segmentation table - map 二維的 physical address,每一個 entry 有
- Base(4 bytes): physical address 的起始位置
- Limit(4 bytes): segment 的長度
- Segment-table base register (STBR):儲存 table 的 physical addr.
- Segment-table length register (STLR): 儲存 segment 的數量
#### Segmentation Hardware
- limit register 檢查 offset 有沒有超過範圍
- MMU 指派一個合適的 base address 給 segment 以配置 memory
- segment 之間的 physical address 不能重疊
- s 為 table 的 index, d 是 offset,若 offset 超過 limit,就發生 segmentation fault; 若符合條件,與 page table 不同, physical address 為 base + offset 直接相加沒有單位(page) 的概念
- 每一個 segment 的 control 是獨立的,如 heap, stack 允許的空間可能不同
![](img/Ch8.Segmentation_Hardware.jpg)
#### Address Translation Comparison
- Segment
- Table entry: segment base address, limit
- Segment base address 可以為任意值
- Offset 的最大值等同於 physical memory size
- Page
- Table entry: frame base address
- Frame base address = frame number * page size
- Offset 的最大值等同於 page size
#### Example of Segmentation
![](img/Ch8.Example_of_Segmentation.jpg)
#### Sharing of Segments
![](img/Ch8.Sharing_of_Segments.jpg)
#### Protection & Sharing
- Segments 的 protection bits
- Read only segment (code)
- Read-write segments (data, heap, stack)
- Code sharing 發生在 segment level
- Shared memory communication
- Shared library
- 不同的 segment tables 有相同的 base address 就共享 segment
### Segmentation with Paging
#### 基本概念
- 在 logical address space 使用 segmentation,映射到 pages 時再切,compiler 不用管 physical address 怎麼對應
- 在 physical address space 使用 paging,藉由 MMU 做地址的映射,提高記憶體使用的效率
![](img/Ch8.Segmentation_with_Paging.jpg)
#### Address Translation
- CPU 產生 logical address
- 交給 segmentation unit 後產生 linear address
- Linear address 交給 page unit
- 產生 physical memory address
- Segmentation & pageing units 都由 MMU 處理
![](img/Ch8.Address_Translation.jpg)
#### Example: Intel Pentium
- Logical-address space 分成兩部份
- 1st: 8K(2^13) segments(private), local descriptor table
- 2st: 8K(2^13) segments (shared), global descriptor table
- private & share 會是兩個分開的 segment
- Logical address
- 一個 process 其 segments 的數量為 2^14 = 16K
- segment 的大小 <= 2^32 = 4GB
![](img/Ch8.Intel_Pentium_address_space.jpg)
#### Segmentation
- Segment descriptor
- Segment base address & length
- 存取權利及優先級
![](img/Ch8.Segment_Descriptor.jpg)
#### Paging (2 Level)
- Page size 可以是 4KB 或 4MB
- 將 32 bits 切成 page directory(outer page table), page tables(inner page table) & offset
- 每一個 page directory entry 有 indication 用的 flag,代表使用 4KB 或是 4MB 的 page,系統會動態決定,若是 4MB 則 32 bits 只切兩份,後面的 bits 都給 offset
![](img/Ch8.Page_Directory.jpg)
#### 範例題目
- physical mem size = 512B 代表 seg offset 為 9 bits
- page size = 32 B 代表 page offest 為 5 bits
- 8 個 segments linear address 前 3 bits 是 segment table 的 index 現有 12 bits 的 logical address = 448 -> 010|001001000
1. segment index = 010 = 2
2. 對應到 001110110
3. 加上 segment offset 001110110 + 001001000 = 0101|11110
4. 因為 page offset 為 5 bits,所以 page table index = 0101 = 5
5. 對應到 physical frame number = 2
6. 所以最終結果為 0010|11110
![](img/Ch8.Example_Question.jpg)
## Ch9 Virtual Memory Management
### Background
- 為何我們不希望執行 program 時,將所有內容放進 memory 中
- 有許多程式碼負責不常使用的 erros 或 conditions
- 部份 program routine 不常使用
- 很多 program 使用相同的 libary
- 已經配置但沒有被使用的 Arrays, lists & tables
- 我們希望更好的 memory 使用率,真的需要使用的才放入 memory 中
- Virtual memory: 將 user logical memory 從 physical memory 分離
- 因為 logical -> physical 只是 mapping 的問題,所以 logical memory 可以比 physical memory 來的大,可以執行非常大的 process
- 提高 CPU/resources 的使用率,越多 process 在 virtual memory 中,degree 越高,就越容易找到需要某個資源的 process 去使用
- 簡化 compiling 的工作,不需要管 memory 的限制
- 提昇 programs 的執行速度,因為不用將整個 program 讀進 memory,有較低的 I/O load & swap 的負載
- Virtual memory 可以透過以下兩種方式實作
- Demand paging,因為是 fix-size,對硬體使用來說資源分配比較容易管理
- Demand segmentation -> 因 segmentation 大小不固定,要找到空間的複雜度較高,效率較低,但對 user 來說比較容易理解及使用 (heap, stack…etc)
- Virtual Memory vs. Physical Memory
![](img/Ch9.Virtual_Memory_vs._Physical_Memory.jpg)
### Demand Paging
#### Demand Paging
- 當 Page 被需要時才讀進 memory 中,好處是
- 較少的 I/O 需求 --> 更快的響應
- 較少的 memory 需求 --> 可以有更多 programs 執行
- 當 Page 有 reference 指向它時,代表 Page 被需要
- 若 reference invalid --> abort (沒有 allocate)
- Not-in-memory --> 利用 paging 讀入記憶體 (page fault)
- pure demand paging
- process 啟動時完全沒有 page
- 只有當 page 需要時才讀進 memory
- swapper (midterm scheduler) 管理整個 process ,而 pager 則是管理 process 的一個 page 為單位
- 硬體支援
- Page Table: valid-invalid bit 來表示 page 是否在記憶體中
- 1 -> page in memory
- 0 -> page not in memory
- 一開始該 bits 都設成 0
- Secondary memory (swap space, backing store): page 從 memory 被 swap 出去的儲存空間,通常是 high-speed disk
#### Page Fault
相關資料:[MIT6.s081 Lab: xv6 lazy page allocation](https://hackmd.io/@Chang-Chia-Chi/SyVVrPBRt)
- 第一次的 reference 會引發 trap to OS
- 稱為 page fault trap
1. OS 會查看在 PCB 裡頭的 internal table ,確認是 Invalid 或只是 not in memory,決定後續動作
2. 找到一個 empty frame (可以置放 page 的空間)
3. 將 page 從 disk (swap space) 移到 frame
4. 設定 Page-table,將 valid-invalid bit 設為 1
5. 重啟 instruction
![](img/Ch9.Page_Fault_Handling_Steps.jpg)
#### Page Replacement
- 若 page fault 時沒有 free frame 的話
- Swap 一個 frame 回 backing store
- Swap 目標 page 進 frame
#### Demand Paging Performance
- Effective Access Time (EAT): (1 – p) * ma + p * pft
- p 為 page fault rate, ma 為 mem. access time, pft 為 page fault time
- mem. access time 包含找到位置及將資料從 memory chip 搬到 CPU register 的時間
- page fault time 則多了資料在記憶體 - disk 之間 swap 的時間
- pft 遠大於 ma,EAT 基本上由 pft 及 page fault rate 決定
- 假設 ma = 200 ns, pft = 8 ms,若 1000 次產生 1 次 page fault,則速度就會慢上 40 倍 ; 換個角度如果我們希望 page fault 對讀取時間的影響小於 10%,則 page fault rate 必須小於 0.0000025
- Programs 的 refernce 通常是 locality 的,代表 program 通常會存取在鄰近位置的 memory address
- 一個 page fault 可以讀取 4KB 的資料進來
- 大幅降低 page fault 發生的機率
- Page fault time 的時間組成
1. page-fault 中斷
2. 將 page 從 disk 中讀進 memory (最花時間)
3. 重啟 process
### Process Creation
#### Process & Virtual Memory
- Demand Paging: 當有需要時才 swap page 進 memory
- Copy-on-Write: parent 跟 child processes 一開始共享 frames,當有內容發生不同時,才會 copy frame
- Memory-Mapped File: 將 file 的 disk content 當作 memory content,用 virtual address space 做 mapping,避開 file system,增加 I/O 效能。通常適用於大檔案,因為 paging 需要 4KB 的空間,小檔案使用會有記憶體空間碎片化的問題
#### Copy-on-Write
相關資料:[MIT6.s081 Lab: Copy-on-Write Fork for xv6](https://hackmd.io/@Chang-Chia-Chi/BkMAP0Hk5)
- 允許 parent & child 共享 frames
- 若其中一者變更 frame,則僅對該 frame 進行 copy 的動作
- COW 使得 process creation 有效率(e.g. fork())
- 通常 free frame 時會完全清空,避免前一個 process 使用的一些 content 被其他 process 看見,造成潛在的安全性問題
- When a child process is forked
![](img/Ch9.When_a_child_process_is_forked.jpg)
- After a page is modified
![](img/Ch9.After_a_page_is_modified.jpg)
#### Memory-Mapped Files
- 方法:
- MMF 透過 disk block to memory frame 的 mapping,讓 I/O 像一般的記憶體存取,略過 file system calls (eg. read(), write())
- file 一開始使用 demand paging 讀取,之後的讀寫動作視作一般的 memory access
- 好處:
- 略過 file system calls (如 read(), write())
- 允許多個 processes 共享 file 的內容
- 壞處:
- 安全性、資料遺失、更多的 programming efforts
![](img/Ch9.Memory-mapped_files.jpg)
- 範例:
![](img/Ch9.Memory-Mapped_File_Example.jpg)
### Page Replacement
Page Replacement 的演算法會嚴重影響系統的速度,假設演算法經常將需要用到的 pages swap 去 disk,則 pages 在 memory 跟 disk 一直來回的時間會大幅拖慢系統的效能
#### Page Replacement Concept
- 當沒有 free frame 時發生 page fault
- 將整個 process swap 掉,釋放所有 process 佔有的 free frames,但這樣 process 就完全不能使用,並不是一個好方法
- 採用 page replacement ,找到一個目前沒有被使用的 frame 將其 free 掉
- 使用 dirty bit 表示 page 的內容是否被修改過,若 page 沒有被修改過,就直接 free 掉 frame 的內容; 有修改過才將資料寫回 disk ,來減少 page transfers 的 overhead,
- replacement 必須要處理兩個問題
- free-allocation algorithm: 演算法必須決定多少 frames 應該 allocate 給 process
- page-replacement algorithm: 選擇哪些 frames 被取代
#### Page Replacement Step
1. 在 disk 中找到需要的 page
2. 若有 free frame,則直接使用; 若沒有 free frame 則執行 page replacement 演算法來選擇被取代的 frame
3. 將需求的 page 讀進 free frame 中,並更新 page & frame tables
4. 重啟 process 的指令
#### Page Replacement Algorithms
- 目標:最低的 page-fault rate
- 評估:一連串的 page id (reference string, memory refernces) 為輸入,計算 page fault 發生的次數
#### FIFO algorithm
- 在佇列中最舊的 page 會被取代
- 範例:
- 假設 available memory frames = 3
- 產生 9 次 page fault
![](img/Ch9.FIFO_algorithm.jpg)
- Belady’s Anomaly: 即便增加 frames 的數量,page fault 的數量不一定會減少,還有可能會增加
- 假設 available memory frames = 4
- 產生 10 次 page fault
![](img/Ch9.FIFO_Illustrating_Belady%E2%80%99s_Anomaly.jpg)
- 因為 Belady’s Anomaly,使得我們很難對系統進行調整,例如插越多條記憶體,反而執行速度有機會變慢
#### Optimal (Belady) Algorithm
- 取代未來最長時間不會被使用的 page
- 但實際上我們不會有未來的資訊
- 因此這個演算法比較類似提供我們一個基準
![](img/Ch9.Optimal_(Belady)_Algorithm.jpg)
#### LRU Algorithm (Least Recently Used)
- 一種 optimal algorithm 的近似
- 因為無法預測未來,所以我們往回看
- 取代最長時間沒有被使用的 page
- 因為 locality 及時間複雜度低(O(1))的緣故,LRU 很常被使用
- Counter implementation
- 紀錄每個 page 最近被使用的 time-stamp
- 取代 time-stamp 最舊的 page
- 時間複雜度 O(N)
- Stack implementation (實際使用)
- 當 page 被使用時,將 page 移到 double-linked list 最前方
- 可以用 hash map 紀錄 double-linked list 每個 element 的指標,達成 O(1) 的實作
- 取代 linked list 中位在 tail 的 page
![](img/Ch9.LRU_Algorithm_Implementations.jpg)
#### Stack Algorithm
Optimal & LRU 都是 stack algorithm,有以下這些特性
- n frames 的 pages 一定是 n+1 frames pages 的 subset,也就是說因為 stack 的特性,先被放入的元素一定會先被看到,若增加 frames,也只是在原 stack 後面增加一些 pages 而已
- Stack algorithm 不會有 Belady’s anomaly 問題
#### LRU 近似的演算法
- additional-reference-bits algorithm
- second-chance algorithm:2 個 LRU stack
- enhanced second-chance algorithm
#### Counting Algorithms
- LFU Algorithm (least frequently used)
- 每一個 page 有 counter
- 經常被使用的 page 應該要有大的 count value
- MFU Algorithm (most frequently used)
- 不過常用不代表正在用,有可能在某些時刻集中使用,將 count 拉高,但多數時間沒有使用
- 最小的 count 有可能是剛帶進來的,還沒被使用,所以應該留著
- 兩者都不常用
- 要一直執行加法成本很高
- 有 overflow 的問題
- 表現不一定較佳
### Allocation of frames
每個 process 最少都需要一定數量的 frames
#### Frame Allocation
- Fixed allocation: 根據 process 在一開始就決定要 allocate 多少 frame
- Equal allocation: 每個 process 配置到的 frames 數目相同
- Proportional allocation: 根據 process 的大小來配置
- Priority allocation
- 在 run time 的時候,根據 process 的優先度決定配置
- 如果 process 發生 page fault
- 選擇 process 本身的一個 frame 來替代
- 或著選擇低優先度的 process 的 frame 來替代
- Local allocation: 只能替換自己的 frames,通常跟 fixed allocation 綁在一起
- Global allocation: 從所有的 frames 中選擇一個來替代
- 可以拿其他 process 的 frame
- 例如讓高優先度的 process 搶低優先度的 process
- 有好的系統表現,所以較常使用
- 會有一個問題是如果一直搶低優先度 process 的 frame,有可能該 process 甚至無法維持最低的 frames 數量,所以要有機制來維持最低 frames,避免 process 被 thrashing
### Thrashing
如果一個 process 沒有足夠的 frames,代表 process 在記憶體中的 frames 數量不足以支撐 process 執行,導致 page fault 一直在發生 (且 page fault 是 instruction level,無法 overlap),等於 CPU 要一直等待 I/O,造成 CPU 使用率反而下降的情況
- Thrashing 的定義為: 當一個 process 花在 paging 的時間比執行時間更高時,稱為 thrashing
![](img/Ch9.Thrashing.jpg)
- Thrashing 造成的 performance 問題
- processes 因 page fault 在等待 I/O
- 因此 CPU 使用率下降
- OS 發現 CPU 使用率下降,增加 degree of multiprogramming
- 新產生的 processes 從舊的 processes 搶 frames,導致更多的 page fault
- 結果 CPU 的使用率繼續下降
- 為了避免 Thrashing,必須要控制 degree of multiprogramming
#### Working-Set Model
- 從 Locality 的角度思考,對一個 process 而言,我們可以計算在這段時間內使用多少 pages,這個數量就是要 assign 給 process 的 frames 數量
- 因為 process 的需求是動態變化的,所以 locality 會隨時間改變,OS 必須要持續追蹤 processes 的 locality
- 例如 program structure (如 subroutine, loop, stack)
- 或 data structure (array, table…etc)
- Working-set Model
- Working-set window: 觀察的一段時間
- Working set: 這一段時間內,被 process reference 到的 page 數
![](img/Ch9.Working-Set_Example.jpg)
- 每一個 process 的 working set size $WSS_i$
- frames 的總需求數 $D=∑WSS_i$
- 如果 D > m (available frames) ,則有 thrashing 發生
- OS 會監控每個 process 的 $WSS_i$ ,並配置足夠的 frames 給每個 process
- 如果 D << m,增加 degree of multiprogramming
- 如果 D > m,把整個 process suspend (mid-term scheduler) 掉,free 該 process 所有的 frames
- 優點
1. 在最大化 multiprogramming 的情況下,同時防止 trashing 的發生
2. 可以最大化 CPU 使用率
- 缺點
1. tracking 的成本很高
#### Page Fault Frequency Scheme
- 監控每個 process 發生 page fault 的機率,控制 page fault rate 在一定範圍內來防止 thrashing 的現象
- 依據 page fault rate 為每一個 process 建立 upper 跟 lower bounds
- 如果 page fault rate 超過 upper limit,則配置更多的 frames
- 如果 page fault rate 低於 lower limit,則移除部份的 frames
![](img/Ch9.Page_Fault_Frequency_Scheme.jpg)
#### Working Sets and Page Fault Rates
重點是 memory 的 locality 會變化,所以需要追蹤 process 的使用情況,動態地調整 processes 及 frames 的配置情況
![](img/Ch9.Working_Sets_and_Page_Fault_Rates.jpg)
## Ch4 Multithreaded Programming
### Threads
Threads 又稱為 Lightweight process,是使用 CPU 的最小單位,同 Process 的 Threads 有共享的記憶體空間。在 Parent Process 創造 Threads 時就會 allocate ,因此省去在空間管理及處理上的執行動作。
- 同 Process 中,Threads 共用以下內容
- code section
- data section (global variable, heap)
- OS resources (e.g. open files and signals)
- 但以下部份各自獨立
- stack
- register set -->可能 instruction 的執行位置不同,甚至執行不同的 function call
- thread ID
- program counter
![](img/Ch4.Single-threaded_and_multithreaded_processes.jpg)
### Motivation
- web browser
- 如一個 thread 負責顯示文字,其他 thread 接收資料等
- web server
- One request / process: poor performance
- One request / thread: 因為 code 及 data sharing,效率較高
- 會有一個 main thread 一直在等待,當接收到新 request 時,會產生一個 thread 專門處理,讓 main thread 可以繼續等待連接
- RPC server
- One RPC request / thread
![](img/Ch4.Multithreaded_server_architecture.jpg)
### Benefits of Multithreading
- Responsiveness : 即使 program 的一部分被 block 住,或是執行冗長的操作,仍可繼續運行,加速響應時間
- Resource sharing : 不同的 threads 可利用相同的記憶體位置來共享資源
- Utilization of MP arch : 因為現在都是多核心,因此 thread 可以在不同 processors 上平行執行
- Economy : thread 無須記憶體空間管理及處理,建立 Thread的 cost 比 process 來得小。而且因為 thread 共用 process 資源,thread 之間的 context switch 也比 process 來得快。
- Multithreaded programming 提供一種更有效率使用多核心的機制,改善共時性 (concurrency)
- 多核心系統使 system designer 以及 application programmer 面對更多挑戰
- 對 OS 設計者 : 如何設計 scheduling algorithms 讓多核心可以平行執行
![](img/Ch4.Parallel_execution_on_a_multicore_system.jpg)
### Challenges in Multicore Programming
- Dividing activities : 將 program 拆成多個可以同時處理的任務
- Data splitting : 除了計算任務要拆,資料也要能夠拆開給不同任務處理。有時候 data 很複雜,不一定是平分給所有任務處理
- Data dependency : shared data 的同步,是平行程式中最複雜且重要的部份
- Balance : 各個任務的執行時間要差不多,否則會受限於執行最久的任務
- Testing and debugging 非常困難
### User vs. Kernel Threads
- User threads : 由 user 透過 threads library 進行的 thread 控制。雖然由 user 創建,但實際 thread 要執行任務時, OS 會將該 thread binding 到某一個 kernel thread 上,每次可對應到不同的 kernel thread。
- POSIX Pthreads
- Win32 threads
- Java threads
- 所謂 user thread 就是 thread library,更重要的是 user level 的 thread,不由 kernel 直接控制,通常在創建及管理上會比較快
- 但如果 kernel thread 只有一個的話,即便有多個 user threads,因為有 binding 的關係,只要某個 user thread 執行時呼叫 I/O 或 sleep(),則其他 threads 就沒辦法再使用該 kernel thread
- Kernel threads : 由 kernel(OS) 直接控制
- Windows 2000(NT)
- Solaris
- Linux
- Tru64 UNIX
- 由 kernel 進行創建及管理,速度較慢
- 若某個 thread 被 block,仍可由 kernel 安排其他 threads 執行工作
### Multithreading Models
Kernel mapping threads 的方式通常有以下幾種:
1. Many-to-One
- 許多 user thread mapping 到一個 kernel thread
- 在不支援多 trheads 的 kernel 使用
- Thread 管理在 user space 完成,效率高
- 但整個 process 可能被 block
- 一次只有一個 thread 可以 access kernel,多執行緒在多處理器的環境下也無法平行化
![](img/Ch4.Many-to-one_model.jpg)
2. One-to-One
- Windows XP/NT/2000
- Linux
- Solaris 9 and later
- 每個 user thread mapping 到一個 kernel thread
- 因為 kernel thread 數量通常受到限制,所以 user thread
- More concurrency
- 因為創建一個 user thread 必須同時創建一個對應的 kernel thread,所以有較高的 overhead
![](img/Ch4.One-to-one_model.jpg)
3. Many-to-Many
- 多個 user threads 對應到相同或較少數量的 kernel threads,而且是在 runtime 期間做 mapping
- user threads 數量不受限制
- kernel threads 可以在多核心系統中平行執行
- 當某個 thread 被 block 時,可安排其他 kernel thread 來執行
![](img/Ch4.Many-to-many_model.jpg)
### Shared-Memory Programming
- 定義:Processes 透過 shared memory space 進行溝通及協作
- 比 messgae passing 更快也更有效率
![](img/Ch4.Shared-Memory.jpg)
- 有許多議題需要處理:
- Synchronization
- deadlock
- Cache coherence
- Programming 的技巧
- Parallelizing compiler
- Unix Processes
- Threads (Pthreads, Java)
#### What is Pthread
- POSIX (Portable Operating System Interface,只定義行為不定義實作,也就是 API 層面必須一模一樣) 標準針對 Unix 類的不同系統 (MacOs, Linux…等),程式碼只需要重新編譯而不用修改即可執行
- Message passing 的 library MPI 也有相同概念
- Pthread 即是定義在 POSIX 標準下的 thread 庫
Pthread Creation
- pthread_create(thread, attr, routine, arg)
- 有時我們會指定某個 thread 榜在某個指定的 core 上,因為不同 core 的 L1 cache 不共享,這個行為可以透過 attr 參數來控制
![](img/Ch4.pthread_create.jpg)
Pthread Joining & Detaching
- pthread_join(threadId, status)
- Block 直到特定 threadId 的 thread 結束
- 達成 threads 之間同步化的一種方法
- 範例:產生 pthread barrier
```c
for (int i = 0; i < n; i++) pthread_join(thread[i], NULL)
```
- 要注意的是,若今天 thread 執行完之後,我們不需要把資料讀回來,則不用 call pthread_join,但多數的 thread 庫會要求 programmer call pthread_detach,告訴 library thread 結束後就直接 free 掉。若不去 call detach, OS 無法知道 thread 是否之後會被 join,導致 thread 的回傳值一直在等待,程式無法結束
- pthread_detach(threadId)
- 一但 thread 被 detached,則永遠無法被 join
- Detach 一個 thread 可以 free 掉 thread 的資源
![](img/Ch4.pthread_detach.jpg)
#### Linux Threads
- Linux 在 kernel 不 support multithreading(因為 Linux 只有 process 或 task)
- User-level 可以使用 Pthreads 來實作多執行緒
- `fork` system call: 產生新的 process 並完全複製 parent 的 data 及程式執行狀態
- `clone` system call: 產生新的 process 並控制哪些 segment 要與 parent share 哪些不需要。因為 Linux 沒有 thread ,所以用 `clone` system call 來達到 thread 的概念
- 有一系列的 flag 來控制資料共享的程度,共享的資料以 pointer 的方式指向
![](img/Ch4.Linux_Threads.jpg)
- Semantics of fork() and exec()
- 若 process 有兩個 thread,其中一個 thread call `fork()` 可能有以下兩種情況
![](img/Ch4.Semantics_of_fork_and_exec.jpg)
- 部份 UNIX 系統支援兩種 `fork()` 的方式
- `execlp()` 會取代整個 Process,而不是單一 thread
- Thread Cancellation
- 一個 thread 若執行結束後, main thread 必須有一系列 cancel 的動作
- Asynchronous cancellation : main thread 會馬上中止 target thread
- Deferred cancellation (default option) : target thread 會預設一些中斷點,main thread 會週期性地確認是否執行到中斷點,等 target thread 執行到中斷點才中斷。
- Signal Handling
- Signals(synchronous or asynchronous) 在 UNIX 系統中用來告知 process 某個事件發生
- synchronous : 如不合法的記憶體存取
- asynchronous : control-C
- Signal handler 處理 signals 的順序
1. Signal 被特定事件產生
2. Signal 被送到 process
3. Signal 被處理
- Signal 處理的 Options
- 傳送 signal 給 deliver signal 的 thread
- 傳送 signal 給所有 thread
- 傳送 signal 給特定的 thread
- 直接由 main thread 來處理 signal,如 file handler 的操作
#### Thread Pools
- 很多應用是直接產生一定數量的 threads,避免動態 thread 的產生、刪除時造成的 overhead,程式執行比較有效率,如 web server
- 優點
- 直接接收 request 通常比創建一個新的 thread 再接收 request 來得快
- 可以控制 threads 數量,進而控制系統的資源用量
- threads pool size 的決定: 利用 # of CPUs, 預期的 # of request 以及記憶體大小來決定
## Ch6 CPU Scheduling
[相關資料](https://hackmd.io/@Chang-Chia-Chi/BkoQi26kq)
### Basic Concepts
- Multiprogramming
- keep 多個 process 在 memory 中
- 一次只有一個 process 在 CPU 執行,其他的 process 處於 waiting 狀態
- 當執行狀態的行程必須等待資源才能往下執行(I/O request),作業系統拿回CPU的控制權交給等待的行程
- Process Scheduling 的目的是讓 CPU 在每個時刻都有工作可以做,增加使用效率
- Process 基本上不是在執行 instruction,就是在執行 I/O,執行一連串的 instrunction 又稱為 burst(CPU burst & I/O burst)
- 一般來說,多數的 CPU burst 時間很短,少部份的 CPU burst 時間很長
- 一個 I/O Bound(I/O 密集) 的 Program 通常有很多短的 CPU burst (若沒有很長的 CPU burst,則通常是 I/O Bound)
- 一個 CPU Bound(計算密集) 的 Program 可能有一些很長的 CPU burst (如很長的 for loop)
![](img/Ch6.CPU_IO_Burst_Cycle.jpg)![](img/Ch6.Histogram_of_CPU-burst_durations.jpg)
#### CPU Scheduler
從 Ready Queue 選擇誰可以被執行 (也就是將 Process 讀到 CPU 當中)
![](img/Ch6.CPU_Scheduler.jpg)
#### Preemptive vs. Non-preemptive
- CPU scheduling 的決定時機主要有以下幾個
1. 從 running 換到 waiting state
2. 從 running 換到 ready state (CPU burst 被打斷,time sharing)
3. 從 waiting 到 ready state
4. 終止狀態
- Non-preemptive scheduling: 若一個 process 可以繼續執行(在 CPU burst),則不會被打斷
- 因此只會在上面 1. 跟 4. 的情況下作 re-scheduling
- 例如 Window 3.X
- Preemptive scheduling
- 在所有情況都有可能 re-sheduling
- 如 Windows 95 及之後的版本, Mac OS X
#### Preemptive Issues
- Preemptive 最大的缺點是 synchronization 的問題
- 需要同步化
- 導致存取 shared data 的問題與對應的 cost
- 對 OS kernel 設計的影響
- 在 kernel 中做 lock & unlock 的設計
- Unix 的解決方法是: 當在 run OS kernel 的 instruction 時,會把 timer disable,從 preemptive 變成 non-preemptive
#### Dispatcher
- 由 scheduler 決定誰被執行,dispatcher 執行換人的動作,其負責
- switching context
- 從 kernel mode 轉換到 user mode
- process 換了之後,page table 的 pointer、program counter(load 到新的 state)
- Dispatch latency: dispatcher 執行換人動作,停止一個 process,啟動另一個 process 所需的時間
- Scheduling time
- Interrupt re-enabling time
- Context switch time
### Scheduling Algorithms
#### Scheduling Criteria
- CPU utilization
- 理論上是 0% ~ 100%
- 實際上是 40% ~ 90%
- Throughput(從系統的角度)
- 單位時間完成的 processes 量
- Turnaround time(從 single job 的角度)
- submission ~ completion(從ready ~ terminate的時間)
- Waiting time
- Process 執行期間在 ready queue 中等待的時間
- 所以 I/O Burst 不算在 waiting time
- Response time
- submission ~ 第一個 response 產生(第一個 CPU burst 開始執行)
#### Algorithms
1. FCFS Scheduling
- 依 (Burst time) 抵達順序執行(先來的先執行)
- P1(24), P2(3), P3(3)
![](img/Ch6.FCFS1.jpg)![](img/Ch6.FCFS2.jpg)
- Waiting time: P1=0, P2=24, P3=27
- Average Waiting Time (AWT): (0 + 24 + 27)/3 = 17
- Non-preemptive
- Convoy effect: 很多 process 在等待一個很長 CPU Time 的 process,造成平均等待時間大幅增加的不良現象
2. Shortest-Job-First (SJF) Scheduling
- 時間最短的 process 先處理
- 運用行程下一個 CPU burst 的長度,而非 CPU burst total 長度
- SJF 的 average waiting time 一定是最短的
- Schemes
- Non-Preemptive SJF: 當一個行程拿到 CPU,不會被搶佔直到他完成
![](img/Ch6.Non-Preemptive_SJF_Example.jpg)
![](img/Ch6.Non-Preemptive_SJF.jpg)
- Preemptive SJF: 當有新的行程且他的 CPU burst 的長度比較小,搶佔發生
![](img/Ch6.Non-Preemptive_SJF_Example.jpg)
![](img/Ch6.Preemptive_SJF.jpg)
3. Approximate Shortest-Job-First (SJF)
- SJF 的難處是在執行下一個 CPU burst 之前,無法得知實際的執行長度
- Approximate SJF: 下一個 burst 可預測為過去 CPU burst 長度的 exponential average
![](img/Ch6.Approximate_Shortest-Job-First.jpg)
4. Priority Scheduling
- 每一個 process 有一個 priority number
- CPU 被分配給高優先度的行程
- Preemptive
- Non-preemptive
- SJF 可視為優先度的排程,其優先度是下一個預測的行程 CPU burst time
- 問題:Starvation – 優先度低的 process 可能不會被執行
- 解法:aging – 隨時間增加,若 process 還沒被執行到,則增加優先度
- ex: 每 15 分鐘增加優先度 1
5. Round-Robin(RR) Scheduling
- process 執行時,可在 CPU 執行的時間(time quantum)有限制,通常是 10~100 ms
- 達規定的執行時間後,程式被 preempted 並加到 ready queue 的尾端
- Performance
- TQ Large --> 類似 FIFO
- TQ small --> (context swtich) overhead 增加
![](img/Ch6.Round-Robin(RR)Scheduling.jpg)
6. Multilevel Queue Scheduling
- Ready queue 切成分開的 queue
- 同一個 queue 通常放類似功能的 process
- 每一個 queue 有自己的排程演算法
- 因為還是只有一個 queue 的程式可以執行,所以 queue 之間也有排程,常見的作法是用權重的方式隨機挑選一個出來
- Fixed priority scheduling:有 starvation 問題,若最上層的 queue 先做,下層的 process 可能一直做不到
- Time slice:每個 queue 分配一定的 CPU time
7. Multilevel Feedback queue Scheduling
- process 執行的狀況在 run time 才能得知,如過去 CPU burst 多少、呼叫了哪些 system call 等等,系統會在 run time 接收這些資訊並分類放到合適的 queue 中
- process 可以在不同的 queue 中移動,因為也是一種 priority queue,也會有 starvation 的問題,通常搭配 aging 實作
- 一定先執行上層的 queue
- 依照 process CPU burst 的特性分類
- I/O-bound 及 interactive 的 process 在較高層的 queue --> short CPU burst
- CPU-bound 的 process 在較低層的 queue --> long CPU burst
![](img/Ch6.Multilevel_Feedback_queue_Scheduling.jpg)
- 當新的 job 抵達 Q0,以 FCFS (First Come First Serve) 演算法排程,若沒辦法在 8ms 內完成,則將 job 移到 Q1
- 同樣地 Q1 也是 FCFS ,若 job 在 16ms 內還是沒執行完,則被 preempted 並丟到 Q2
- 只有當 Q0 ~ Qi-1 是空的時候, Qi 中的 job 才會被執行
- 下一次回來 ready queue 時,系統會依照 feedback 判斷放在哪個 queue 中 (feedback 資訊會放在 PCB 中)
- 通常 scheduling 演算法由以下參數決定
- queues 數量
- 每個 queue 的排程演算法
- 提昇/降級一個行程的方法
#### Evaluation Methods
- Deterministic modeling
- 以預定義的 workload 及演算法表現的好壞,缺點是難以通用化
- Queueing model
- 數學分析
- Simulation
- 以隨機數字產生器或 trace tapes 產生 workload
- Implementation
- 最準確的方式就是直接實作並觀察
### Multi-Processor Scheduling Multi-Core Processor Scheduling Real-Time Scheduling
#### Multi-Processor Scheduling
- Asymmetric multiprocessing
- 所有系統的執行會由一個 master processor 掌控
- 其他 processors 只執行 user code (由 master 配置)
- 遠比 SMP 簡單
- Symmetric multiprocessing (SMP)
- 每一個 processor 都是 self-scheduling
- 所有 processors 共用 ready queue,或是每一個 processor 有自己的 ready queue
- 需要同步機制
#### Processor affinity
- Processor affinity: process 與執行它的 processor 之間有 affinity 關係
- process 會將最近常使用的資料放在執行它的 processor 的快取中
- 快取的無效及資料重新放入是高成本的行為
- Solution
- soft affinity: 允許 process 在不同 processor 執行
- hard affinity: 只能在同一個 processor 執行
#### NUMA and CPU Scheduling
- NUMA (non-uniform memory access)
- 發生在結合 CPU 與 memory boards 的系統中
- CPU scheduler 及 memory-placement 會一起工作
- process 被配置在有 affinity 關係的 CPU 的 memory board
![](img/Ch6.NUMA_and_CPU_scheduling.jpg)
- Load-balancing
- 讓不同 processors 間的 workload 盡量平均
- 只在 processor 有 private queue 的系統下才需要
- 兩種策略
- Push migration: 將 processes 移動到閒置或 less-busy 的 processor 中
- Pull migration: 閒置的 processor 將等待中的 task 從其他忙碌的 processor 中拉過來
- 通常是平行實作
- Load balancing 經常抵銷 processor affinity 帶來的效益
#### Multi-core Processor Scheduling
- Multi-core Processor:
- 更快且更少的 power 消耗
- memory stall: 當存取記憶體時,花費許多時間在等待資料 available (e.g. cache miss)
- Multi-threaded multi-core systems
- 兩個(或更多)的硬體 thread 被 assign 給每一個 core (i.e. Intel Hyper-threading)
- 當一個 thread 在存取記憶體時,其他 thread 就可以執行 CPU 指令
![](img/Ch6.Memory_stall.jpg)
![](img/Ch6.Multithreaded_multicore_system.jpg)
- Two ways to multithread a processor
- coarse-grained: 當 memory stall 發生時切換到另一個 thread,因為 instruction pipeline 必須被 flush 所以成本很高
- fine-grained(interleaved): 把 pipeline 的狀態保留並切換到另一個 thread 執行,需要更多的 register 來保存資料
- Scheduling for Multi-threaded multi-core systems
- 1st level: 選擇某個 software thread 執行在每個 hardware thread([logical processor](https://daniel-jiang.medium.com/computer-science-101-in-depth-operating-systems-part-1-processes-and-scheduling-df9b9cd5b573))
![](img/Ch6.Logical_Processors.jpg)
- 2nd level: 每個 core 如何決定執行哪一個 hardware thread
#### Real-Time Scheduling
- Real-time 不代表越快越好,重點在 deadline 前要完成
- Soft real-time
- 雖然不希望超過 deadline,但並不會馬上出問題
- 例如影音串流
- Hard real-time
- 若超過 deadline 則導致 fundamental failure
- 例如核電廠的控制
- Hard real-time
#### Real-Time Scheduling Algorithms
- FCFS scheduling algorithm – Non-RTS
- T1 = (0, 4, 10) == (Ready, Execution, Period)
- T2 = (1, 2, 4)
- Rate-Monotonic (RM) algorithm
- 依據頻率的大小來做排程
- 更短的週期(週期是固定的數值,在 run time 期間不會變動) --> 更高的優先度
- 同一個 task 的所有 job 都有一樣的 priority
- task 的優先度是固定的
- Fixed-priority RTS scheduling algorithm
![](img/Ch6.Fixed-priority_RTS_scheduling_algorithm.jpg)
- Earliest-Deadline-First (EDF) algorithm
- 更早 deadline --> 更高的優先度
- 動態優先的演算法
![](img/Ch6.Earliest-Deadline-First_algorithm.jpg)
### Operating System Examples
#### Solaris Scheduler
- Priority-based multilevel feedback queue scheduling
- 有六類 scheduling,一個 process 只會屬於某一類:
1. real-time
2. system
3. time sharing
4. interactive
5. fair share
6. fixed priority
- 每一個類有自己的 priority & scheduling 演算法
- Scheduler 會將類的優先度轉換為 global 的優先度
![](img/Ch6.Solaris_scheduling.jpg)
- Solaris Example(time sharing, interactive)
- 優先度與 time slices 成反向關係,time slice 越小的優先度越高
- Time quantum expired: 當 thread 在沒有 blocking 的狀況下使用整個 TQ,則重新排優先度
- Return from sleep: 若 thread 從 sleeping(I/O wait)回來 ready queue 時,會重新排優先度
- 期望 CPU-bound 的 process 慢慢往下沉,I/O-bound 的 process 慢慢上浮
![](img/Ch6.Solaris_Example.jpg)
#### Windows XP Scheduler
- Multilevel feedback queue
- 優先度分為 0~31,由優先度最高的 queue 開始排程
- 優先度最高的 thread 永遠在執行
- 每一個 priority queue 使用 Round-robin
- 除了 Real-time 的 task 之外,優先度在 run time 時期動態變更
![](img/Ch6.Windows_XP_Scheduler.jpg)
#### Linux Scheduler
- Preemptive priority based scheduling
- 只有 user mode processes 可以被 preempted
- 兩個不一樣的 process priority ranges
- 值越低優先度越高
- TQ 較高者有更高的優先度
- Real-time tasks: (priority range 0~99)
- static priorities
- Other tasks: (priority range 100~14-)
- 依 task 執行狀況動態決定優先度
![](img/Ch6.Linux_Scheduler_Other_tasks.jpg)
- Scheduling algorithm
- 若一個 task 還有剩餘的 TQ,則 task 可以被執行,只要還沒耗盡 TQ,都會保留在 active array,希望每一個 task 都能用完它的 TQ
- 當 task 耗盡 TQ,則為 expired 且不應被執行,移到 expired array
- task expire 後,系統會決定新的優先度以及 TQ
![](img/Ch6.Linux_Scheduler_algorithm.jpg)
## Ch5 Process Synchronization
當共享的資料同時被不同 Process / threads 存取時,因為執行順序的不確定性,很容易發生 data inconsistency 的狀況,所以需要額外的機制來確保程式的正確性,也就是 Synchronization
以下為常見的 Consumer & Producer Problem
![](img/Ch5.Consumer&Producer_Problem.jpg)
除了 Buffer 之外, Counter 也是 share variable,要注意的是 `counter++` 及 `counter--` 在 instruction level 其實有三道指令,以 `counter++` 為例,要先將 counter 從記憶體中移到 register,加上 1 之後,再存回記憶體。由於 `context switch`, `Preemptive scheduling` 等因素, 這些指令可能不是一次執行完成,導致執行結果不符合預期
```nasm
move ax, counter
add ax, 1
move counter, ax
```
![](img/Ch5.Instruction_Interleaving.jpg)
### Race Condition
當多個 Processes 同時存取及操作 shared data 時,最終的值取決於最後一個完成執行的 Process,這個現象稱為 Race Condition
- 在 single-processor machine 中,我們可以 disable interrupt 或著使用 Non-preemptive scheduling 來達成同步,但在 User Program 中不可能使用,會影響整個系統的運作
- 通常將可能產生 Race Condition 的區域稱作 `Critical Section`
### Critical-Section Problem
- 目的:建立 Processes 之間合作的 protocal
- Problem description
- N 個 Processes 競爭使用 shared data
- 每一個 Process 存取 shared data 的 code segment 我們稱為 critical section
- 若我們確保當一個 Process 執行 critical section 時,其他 Processes 不得執行 critical section,稱之為 `mutually exclusive`,是比較暴力的解法
- 一般常見 critical section 的架構如下
![](img/Ch5.The_Critical-Section_Problem.jpg)
### Critical-Section Requirements
1. Mutual Exlusion:
- 當一個 Process 執行 critical section 時,其他 Processes 不得執行 critical section
2. Progress:
- 若沒有 Process 在執行 critical section,且有某些 Processes 希望進入它的 critical section,則 Process 不得在未定義的情況下被推遲,一定要進的去
3. Bounded Waiting:
- Process 等待進入 critical section 的期限必須受到限制,不可一直排隊等待
### Critical-Section Solution & Synchronization
#### Algorithm for Two Processes
- 我們先以兩個 Processes 的簡單例子做講解
- 只有兩個 Process P0 & P1
- 共享變數
- int turn
- turn = i --> Pi 可進入 critical section
![](img/Ch5.Algorithm_for_Two_Processes.jpg)
以上的程式碼其實並不完美,這個程式要能順利運作的前提是,兩個 Process 必須輪流執行,但 while loop,並不保證執行的順序,很有可能 Process 0 執行完一次,又想進入 critical section,但 Process 1 還沒執行到 turn = 0 這一行,P0 就會卡在 while loop ,不符合 progress
#### Peterson’s Solution for Two Processes
為了解決上述 Progress 的問題,我們引入另一個變數 `flag` ,用來表示 Process 是否想要進入 critical section 中,當 `flag[i] = True` 的時候,代表 Pi 已經準備好要進入 critical section,`turn = j` 代表把 key 交給別人。所以 while 條件式 `flag[j] && turn==j` 的意義為檢查其他 Process 是否可進入 critical section,確認沒有其他 Process 要進入 critical section 後,自己再進入 critical section。
要注意如果 `turn = j` 不是把 token 交給別人而是搶過來用,當某個 Process 先進入 critical section,輪到另一個 Process 時也可以 break while 迴圈,但無法進入 critical section 導致 deadlock
![](img/Ch5.Peterson’s_Solution_for_Two_Processes.jpg)
簡單的證明 Peterson’s Solution
- Mutual exclusion : turn 不可能同時為 0 或 1,因此不可能同時進入 critical section
- Progress :
- 若 P1 尚未準備好 --> flag[1] = False --> P0 可以進入
- 若兩者都準備好 --> flag[0] = flag[1] = True
- 若 turn = 0 則 P0 進入,否則 P1 進入
- 以上兩種狀況,Process 都可以順利進入 critical section
![](img/Ch5.Proof_of_Peterson’s_Solution.jpg)
- Bounded waiting :
- 若 P1 離開 critical section --> flag[1] = False --> P0 可以進入
- 若 P1 離開 critical section 後,繼續執行 while loop --> flag[1] = True --> turn = 0 (覆寫 P0 原先的 turn = 1) --> P0 可以進入
- 以上兩種狀況,P1 進入後接著進去的一定是 P0,符合 Bounded Waiting 的要求
![](img/Ch5.Proof_of_Peterson’s_Solution.jpg)
#### Producer/Consumer Problem
- 情況一:我們將所有程式碼都放入 critical section,如果 consumer 先 call,因為 buffer 是空的,所以 consumer 會卡在 while 迴圈 ; 這時再 call producer ,因為 consumer 已經在 critical section 裡頭, producer 無法進入導致 `deadlock`
![](img/Ch5.Producer_Consumer_Problem.jpg)
- 情況二:我們只將可能產生 race condition 的程式碼放進 critical section,雖然執行結果正確,但如果某一個 Process 進入 critical section 其他 Process 進不去,可能導致無謂的 context switch 降低效率
![](img/Ch5.Producer_Consumer_Problem2.jpg)
### Bakery Algorithm (n processes)
- 在進入 critical section 之前,每一個 Process 會抽號碼牌
- 號碼牌數字最小的先進入 critical section (注意可能有相同的號碼牌,如下方程式碼以 max 實作,而 `max` 指令其實有好幾行)
- 號碼牌數字的產生一定是 non-decreasing order; i.e. 1,2,3,3,4,5,5,5…
- 若兩個 Process Pi & Pj 有相同的號碼牌,則 PID 小的先進入
- `choosing[i]` 為是否正在抽號碼牌的 flag,在與其他 Process j 比較之前,我們必須等待 Process j 抽完號碼牌。考慮一個狀況, Process j 比 i 晚抽完號碼牌,但號碼與 i 一樣大,又 j 的 PID 比 i 小,所以 Process j 應該要先執行 ; 如果在 j 還沒抽號碼牌之前就進行比較,此時 num[j] = 0,程式會以為 j 不要執行,反而先執行 i,當 j 抽完號碼牌之後發現數字與 i 相同,因此也可以進入 critical section,但這時 i 已經在裡頭,導致 deadlock。
![](img/Ch5.Bakery_Algorithm.jpg)
### Pthread Lock/Mutex Routines
![](img/Ch5.Pthread_Lock_Mutex_Routines.jpg)
### Condition Variables (CV)
- Condition Variables 代表一個條件,符合該條件時可以觸發 thread 執行某些動作
- 三種 CV 的操作
- `wait()` : 直到其他 thread 呼叫 signal()/broadcast(),thread 處於等待狀態
- `signal()` : 喚醒一個等待中的 thread
- `broadcast()` : 喚醒所有等待中的 thread
- 在 Pthread 中,CV type 為 `pthread_cond_t`
- 使用 pthread_cond_init() 來初始化
- pthread_cond_wait (&theCV, &somelock)
- pthread_cond_signal (&theCV)
- pthread_cond_broadcast (&theCV)
![](img/Ch5.Using_Condition_Variable.jpg)
- 上圖執行順序如下:
1. action() 進入 CS,呼叫 `thread_cond_wait` 並釋放 lock
2. counter() 取得 lock 進入 CS
3. counter() 呼叫 `singal()` ,action() 被喚醒,注意這時候 action() 是沒有 lock 的
4. counter() 釋放 lock
5. action() 取得 lock 並執行函式
#### ThreadPool Implementation
一次創建一定數量的 threads,讓系統不用一直增刪 threads,並且讓系統可以有效控制 threads 的數量,避免過多的 context switch 反而降低效率,達到效能最佳化。
創建 threads 後,為了讓 threads 執行不同的 function,會建立一個 `threadpool_task_t` 的結構體,存放函式及對應參數的指標,因此 threads 在經過 while loop 被喚醒之後,執行目前看到 `threadpool_task_t` 的函式,等於處理了一個 request,處理完成後進入 wait 狀態,等待下次被喚醒。而 threads 要如何知道被喚醒,其中一個方式是透過 Condition Variable 判斷
以下程式碼為一個簡單的範例:
1. 創建 threads pool 以及 task 的 queue
2. thread 執行 while loop 處於 wait 狀態
3. 若 thread 被喚醒則離開 while loop,並執行 queue 中 pool->head 的函式
4. 執行後 pool->head 加 1,再判斷該值是否等於 `queue_size`,若相等表示已經執行到最後一個函式,將 `pool->head` 設為 0
5. 離開 critical section 執行任務
![](img/Ch5.ThreadPool_Implementation1.jpg)
![](img/Ch5.ThreadPool_Implementation2.jpg)
![](img/Ch5.ThreadPool_Implementation3.jpg)
### Hardware Support
Critical Section Problem 的發生是因為 shared variable 的修正可能被打斷,若某些指令我們可以一行完成,就可以避免 race condition 的問題。前面都是在說軟體上的支援,若硬體可以將指令變成 `atomic instructions` ,就沒有同步的問題
- atomic : 無法中斷執行的最小單元
- 例如 : `TestAndSet(var)`, `Swap(a,b)`
#### Atomic Test And Set()
注意以下程式碼不符合 Bounded waiting
![](img/Ch5.Atomic_TestAndSet.jpg)
#### Atomic Swap()
先 call Swap 的取得進入 critical section 的權力,執行完成後將 token 還給 lock。注意以下程式碼不符合 Bounded waiting
![](img/Ch5.Atomic_Swap.jpg)
### Semaphores
- `Samaphores` 是一個通用的同步處理工具,其紀錄某個資源有多少 units 可以去使用
- 若 # of record = 1 --> binary semaphore, 但其實用 mutex lock 就好
- 若 # of record > 1 --> counting semaphore
- 利用兩個 atomic operations `wait` & `signal` 來存取 (注意與 critical section 的 wait & signal 不同)
- 簡單的 Spinlock 以 semaphore 實作
![](img/Ch5.Semaphores.jpg)
- Semaphore 為 POSIX 標準庫之一,不屬於 Pthread,所以使用上不限於 thread ,也可以用於 Process
#### Non-busy waiting Implementation
Busy waiting (SpinLock) 因為 while loop,執行效率沒有被最佳化,所以相對 busy waiting 就有 non-busy waiting 的實作方式,但 system call 要考慮 context switch、re-scheduing 的影響,成本比 while 來得高。一般來說,等待時間很短的,我們會用 busy waiting,反之則使用 non-busy waiting
- Semaphore 為一個有 `queue` 的結構體,包含 Semaphore 的值以及有哪些 Processes 等待被執行
![](img/Ch5.struct_with_a_queue.jpg)
- wait() & signal()
- 使用 system calls: sleep() & wakeup()
- 一樣必須為 atomic 的操作
- 必須先進行 value--(++) 操作,避免進入 if 區域後被 sleep 無法執行
![](img/Ch5.wait_and_signal.jpg)
- 由於 `value--(++)` 還有 queue 的 insert, delete 等,必須確保 `wait()` & `signal()` 為 atomic 操作
- single-processor : 透過 disable interrupts
- multi-processor :
- HW Support (e.g. Test-And-Set, Swap)
- SW Support (Peterson’s solution, Bakery algorithm)
- Semaphore with Critical Section
- 將 `value--(++)` 以及 queue 的操作包進 critical section
- 因為 `sleep()` 及 `wakeup()` 為 system calls,無須擔心同步的問題, OS 會妥善處理,所以不用包進 critical section 中(也不該包進去)
![](img/Ch5.Semaphore_with_Critical_Section.jpg)
#### Cooperation Synchronization
- 有些情況不同 Process 間的執行順序很重要,考慮兩個 thread P1 & P2 分別執行 S1 & S2,S2 必須在 S1 完成後才可執行
- 實作方式 : 利用 shared variable `semaphore sync` ,以下程式碼確保 S1 一定先執行完成
![](img/Ch5.Cooperation_Synchronization.jpg)
- 更複雜的情況概念相同,由上述兩個 Processes 的例子做延伸
![](img/Ch5.Complicated_Example.jpg)
### Classical Synchronization Problems
常見的問題如下:
1. Bounded-Buffer (Producer-Consumer) Problem
2. Reader-Writers Problem
3. Dining-Philosopher Problem
#### Reader-Writers Problem
- 標準在處理 I/O 會遇到的問題
- A set of shared data objects
- A group of processes
- reader processes (read shared objects)
- writer processes (update shared objects)
- writer process 對 shared object 有獨佔權
- 不同的種類
- first RW problem : 只要確定 writer 有 access 的獨佔權即可,不關心 reader 要等多久,或要做什麼事情。reader 取得 token 時,可以傳給其他 reader,可能造成 writer 一直被其他 reader 插隊,永遠拿不到 lock
- second RW problem : 不只要確定 writer 的 access 權限,另外當 writer ready 且 object 被釋放時,writer 的優先性必須高於 reader ,不可有新的 reader 進來插隊
**First Reader-Writer Algorithm**
- writer 為 binary semophore
- Reader 共享一個 block
- `readcount` 計算有幾個人要讀
- 當 `readcount > 1` 時,代表已經有 reader 拿了 lock,所以這個 reader 也可以讀
- 當 `readcount = 1` 時,因為是第一個 reader,可能要等 writer 結束才可讀取
- 當 `readcount= 0` 岱表示最後一個讀的,讀完後要 signal writer
- `readercount` 為 shared variable,必須受到保護
- Writer可能會有 *starvation problem* ,因為只要有 reader 在讀,其他 reader 都可以進入 critical section,造成 writer 一直在等待狀態
```c
// mutual exclusion for write
semaphore wrt=1
// mutual exclusion for readcount
semaphore mutex=1
int readcount=0;
// writer
Writer(){
while(TRUE){
wait(wrt);
// Writer Code
signal(wrt);
}
}
// reader
Reader(){
while(TRUE){
wait(mutex);
readcount++;
if(readcount==1)
wait(wrt);
signal(mutex);
// Reader Code
wait(mutex);
readcount--;
if(readcount==0)
signal(wrt);
signal(mutex);
}
}
```
#### Dining-Philosopher Problem
- 5 個人坐在 5 張椅子上,且有 5 支筷子
- 每個人只會思考或是吃飯
- 思考時與剩下 4 個人沒有互動
- 每個人要用餐需要兩支筷子
- 一個人 1 次只能拿 1 支筷子
- 吃完飯後,將兩支筷子都放下
- 若每個人都拿其中一邊的筷子,就 deadlock 了
- starvation problem
![](img/Ch5.Dining_Philosophers.jpg)
### Monitor
雖然 semaphores 提供非常便利及有效的同步機制,但正確性主要是依賴 programmer
- 所有存取 shared data object 的 processes 都需確保 wait() & signal() 的執行順序及位置正確
- 但是人總有可能犯錯
- 可以將 Moniter 想成一個特殊 class (high level language construct),每一個 Moniter 都有可能發生 race condition 的 local variable (對應下圖的 shared data) 需要保護,而要操作這些 var 必須透過 Moniter 的 method。
- Monitor 與 OO 最重要的差異為,Monitor 一次只有一個 thread 可以執行其 method。(但可以很多個 threads 同時 call 這個 method,只是會處於 inactive 狀態)
- monitor 利用 queue 做排程,確保一次只有一個 process 是 active 的,保護 shared data
![](img/Ch5.Monitor.jpg)![](img/Ch5.Monitor_Schematic.jpg)
#### Monitor Condition Variables
- 為了允許 process 等待 monitor,需要宣告 condition variable,例如 `condition x, y`;
- condition variable 只能與 `wait()` 及 `signal()` 一起使用
- x.wait()
- 代表調用這個 operation 的 process 被閒置,直到其他 process 完成為止
- x.signal()
- 啟動一個等待中的 process,若沒有 process 處於等待狀態,則 `signal()` 操作沒有任何影響。與其相反,semophore 的 `signal()` 一定會改變 semaphore 的狀態 (`wait` 把 counter 減 1; `signal` 把 counter 加 1)
- 在 monitor 當中,condition variable 不是 counter 而是一個佇列,所以 x.wait() 類似把 process 放到 waiting queue (enqueue),而 signal 則是在 dequeue,所以如果 queue 是空的,signal 就沒有任何作用
- Monitor 可以有很多個 threads 存在,但一次只有一個 active
![](img/Ch5.Monitor_Conditions.jpg)
#### Dining Philosophers Example
**monitor type**
- 建立三個狀態 `thinking`, `hungry` & `eating`
- 建立 shared variable `self[5]`,存放每個人的狀態
- P.S. 可以用 shared variable 都還算好解決,難解的是無法得知他人狀態的問題
- 定義 4 個 method
```c
monitor dp {
enum {thinking, hungry, eating} state[5]; //current state
condition self[5]; //delay eating if can’t obtain chopsticks
void pickup(int i) // pickup chopsticks
void putdown(int i) // putdown chopsticks
void test(int i) // try to eat
void init() {
for (int i = 0; i < 5; i++)
state[i] = thinking;
}
}
```
- void pickup(int i)
- 將狀態設為 `hungry`,代表要吃東西了
- 測試拿筷子後是否可以吃
- 若狀態沒有改變,表示測試失敗,把自己放到 wating queue
```c
void pickup(int i) {
state[i] = hungry;
test(i); // try to eat
if (state[i] != eating)
// wait to eat
self[i].wait();
}
```
- voild putdown(int i)
- 吃完了將狀態改為 `thinking`
- 告知左右兩邊進行測試,看有沒有辦法吃
```c
void putdown(int i) {
state[i] = thinking;
// check if neighbors are waiting to eat
test((i+4) % 5);
test((i+1) % 5);
}
```
- void test(int i)
- 若左右兩邊的人都沒在吃,代表左右兩邊手上都沒筷子,還要確認自己是否想吃東西
- 都符合的話,將狀態改為 `eating` 並喚醒 thread/process
```c
// t## Ch7 Deaklock
### Deadlock Problem
- 一系列被 blocked 的 processes 持有部份資源且都在等待獲得其他 processes 擁有的資源
- Ex1: 2 processes and 2 tape drivers
- 每一個 process 擁有一個 tape drive
- 每一個 process 要求其他的 tape drive
- Ex2: 2 processes and semaphores A &
- P1 (holb B, wait A): wait(A), signal(B)
- P2 (holb A, wait B): wait(B), signal(A)
#### 必要條件
- Mutual exlusion
- 一次只能有一個 process 可以使用資源
- Hold & Wait (同時 Hold & Wait)
- 一個 process 持有某些資源且等待其他 processes 擁有的資源
- No preemption (有些情況很難保存 state 或保存的 cost 很高)
- 資源只能被持有的 process 自己放棄
- Circular wait
- 存在一系列等在中的 processes {P0, P1, …, Pn} 形成環狀等待
- P0 -> P1 -> P2 -> … -> Pn -> P0
- 當以上*四個條件*都發生時,才會有可能的 deadlock 的情況發生
#### System Model
- 資源類型 R1, R2,…,Rm
- 如 CPU, memory pages, I/O devices
- 每一個資源類型 Ri 有 Wi 個 instance
- 如一個電腦有兩個 CPUs
- 每一個 process 使用資源方式如下
- Request -> use -> release
#### Resource-Allocation Graph
- 3 個 processes, P1 ~ P3
- 4 個 resources, R1 ~ R4 (資源可能有多個 instances)
- R1 & R3 各有一個 instance
- R2 有兩個 instance
- R4 有三個 instance
- Request edges
- P1 -> R1: P1 要求 R1
- Assignment edges
- R2 -> P1: 一個 R2 的 instance 被 assign 給 P1
- 若圖上有 *circular* 存在,則*可能*有 deadlock 存在
- 左圖有 deadlock,右圖沒有 deadlock
![](img/Ch7.Deadlock.jpg)![](img/Ch7.Cycle_No_Deadlock.jpg)
#### Deadlock Detection
- 若圖沒有 cycle --> 沒有 deadlock
- 因為 Circular wait 不會形成
- 若圖有 cycle
- 若每一個資源類型都只有一個 instance --> deadlock
- 若每一個資源類型都有多個 instance --> 可能會 deadlock
#### Handling Deadlocks
- 方法1.,確保系統永遠不會進入 deadlock 的狀態
- deadlock prevention: 確保四個條件中至少一個條件沒有滿足
- deadlock avoidance: 分配資源前,Run-time 動態地檢查目前資源分配的狀況
- 方法2.,允許 deadlock,發生再想辦法修復
- deadlock detection
- deaklock recovery
- 方法3.,忽略問題且假設系統中永遠不會發生 deadlock
- 大多數作業系統使用此方法,包含 Unix 系統
### Deadlock Prevention
- Mutual exclusion (ME): 將不需要 ME 的資源取消 ME
- read-only 的資料不需要 mutual exclusion
- 但有些資源不能共享,如 critical section、printer 等
- Hold & Wait:
- 當一個 process 請求資源時,process 不能持有任何資源
- 必須讓所有的資源一次性地都有對應的 process 處理,才進行下一步動作
- 缺點是資源使用率非常低,可能很多 process 一直在 acquire 跟 release,沒辦法真正的執行
- No preemption:
- 當 process 在等待資源時,該 process 持有的所有資源必須 preempted 掉
- e.g. P1 請求目前被 P2 使用的 R1,而 P2 在等待 R2
- P2 紀錄目前執行的狀態後,R1 可以被 preempted 掉,並重新分配給 P1 使用
- 適合用在容易紀錄及恢復狀態的資源,如 CPU 的 register (context switch) 或 memory
- Circular Wait:
- 對所有資源類型加上 total ordering,讓資源的取得有方向性
- process 依遞增的順序來取得資源
- 若$R=R_0,R_1,...,R_N$為一系列資源
- 當 process 要求 $R_k$時,必須將所有$R_i,i>=k$ 釋放掉才可拿取資源
- 例如現在有三個資源 F(tape drive) = 1, F(disk drive) = 5, F(printer) = 12,process 在拿 printer 的資源時,一定要將 printer 釋放掉,才能拿 tape & disk drive
- 證明:要形成 Circular Wait,一定有以下情況
- $P_0(R_0)->R_1,P_1(R_1)->R_2,...,P_N(R_N)->R_0$
- 但這樣代表 $R_0$ 必須大於 $R_N$,產生矛盾
### Deadlock Avoidance
若無法做 Preventon,則必須在 run time 期間進行動態檢查
#### Avoidance Algorithms
- Single instance of a resource type
- resource-allocation graph (RAG) algorithm based on circle detection
- Multiple instances of a resource type
- banker’s algorithm based on safe sequence detection
#### Resource-Allocation Graph (RAG) Algorithm
- Request edge:
- $P_j->R_j$,$P_j$在等待$R_j$的資源
- Assignment edge:
- $R_j->P_j$,$R_j$被$P_j$所持有
- Claim edge:
- $P_j->R_j$,$P_j$在未來可能要求$R_j$
- 當 process 要求資源時,claim edge 轉換成 request edge
- 當資源被 process 釋放時,Assignment edge 轉換成 Claim edge
- Avoidance 基本上使用有向圖的方式建立 Claim edge,確認當任務 Assign 時是否有 Loop 產生,若產生迴圈則將拒絕或延遲該請求,如下圖 $P_1−>R_2$的請求在該狀況下不會被同意
![](img/Ch7.Resource-Allocation_Graph_Algorithm.jpg)
- Cycle detection 的演算法為 $O(n^2)$
#### Multiple instances
- Safe state: 若存在 sequence allocation 滿足所有 process 的請求,則系統處於 safe state
- 此 sequence of allocation 稱為 safe sequence
- safe state -> no deadlock
- unsafe state -> 可能有deadlock
- deadlock avoidance: 確保系統永遠不會進入 unsafe state
**Safe State with Safe Sequence**
- 有 12 個 tape drives
- t0 的狀態如下
![](img/Ch7.Safe_State_with_Safe_Sequence.jpg)
- $P_0$最多可能會一次要 10 個資源,才能執行下一個動作
- 目前已使用 9 個資源,剩下 3 個資源,因為$P_1$最多只需要 4 個,所以我們可以先把剩下的 3 個資源全部先給$P_1$,$P_1$執行完之後可用資源變成 5 個。這時候將 5 個資源全部給$P_0$,最後再給$P_2$就可以順利執行所有的 process,$<P_1,P_0,P_2>$稱為 safe sequence
**Un-Safe State w/o Safe Sequence**
- 仍然可能找不到 safe sequence,如下圖剩下 2 個資源,$P_1$執行後無論給$P_0$或$P_2$都不夠
![](img/Ch7.Un-Safe_State.jpg)
- 但仍未進入 deadlock 的狀態
- 這時候$P_0\&P_0$的請求就會被拒絕或延遲
**Banker’s algorithm**
- 適用於對每個資源是 multiple instances 的情況
- 使用通用的 *safety algorithm* 預先決定是否有 safe-sequence 存在
- 只在 safe-sequence 存在時才會去執行動作
- Algorithm:
1. 假設 processes 需要 *Maximum resources*
2. 找到一個 free resources 可以滿足的 process,將所有 free resources 給它
3. 將該 process 的所有資源釋放掉
4. 重複上述動作直到所有的 processes 都可滿足執行條件
- 範例:
- Total instances: A=10, B=5, C=7
- 初始 available 的 instances: A=3, B=3, C=2
- Safe sequence 為 P1 -> P3 -> P4 -> P2 -> P0
![](img/Ch7.Banker’s_Algorithm.jpg)
### Deadlock Detection
偵測目前系統狀態有沒有 deadlock
- Single instance
- 將 request/assignment edges 轉換為 wait-for graph
- 若 wait-for 存在 circle 則有 deadlock
![](img/Ch7.Resource_allocation_graph_Corresponding_wait-for_graph.jpg)
- Multiple instances: 不用管 Maximum Needs,只要確認 request 會不會造成 deadlock
- Total instances: A:7, B:2, C:6
- Available instances: A:0, B:0, C:0
- safe state -> <P0, P2, P3, P1, P4> -> no deadlock
- If P2 request = <0, 0, 1> -> no safe sequence -> deadlock
![](img/Ch7.Multiple-Instance_for_Each_Resource_Type.jpg)
### Deadlock Recovery
- Process termination
- kill 全部的 deadlock processes
- kill 一個或多個在 deadlock circle 的 process
- 要決定哪些 process 應該被砍掉,即便砍掉 priority 最低的 process 也不一定能解決 deadlock 的問題,這是一個需獨立出來討論的議題
- Resource preemption
- 選擇一個 victim 去 preempt
- 哪一個應該被 preempt ?
- rollback
- 部份或全部 rollback ?
- starvation
- 若一直 preempt 相同的 process,則該 process 可能要等待很久的時間才能被執行
## CH11 File System Interface
### File Concept
- File: 為 OS 所創造的邏輯儲存單元 (logical storage unit)
- 實體儲存單元 (physical storage unit) 則是 disk (sector, track)
- File 的屬性
- Identifier: non-human-readable name, Name, Type, Location, Size, Protection, Last-access time, Last update time
- File 的操作
- 創建、讀寫、repositioning、刪除、截斷 (truncate)、串接 (concatenate)
- Process 會儲存一個 open-file table
- Os 會儲存一個 system-wide table
#### Open-File Tables
- Open-file table 儲存 process 開啟的檔案資訊
- 紀錄所有被這個 process 開啟的檔案
- 每一個開啟檔案的指標
- 檔案的存取權以及 Accounting information (CPU數量,真實使用的時間, 時間限制, 帳戶數量, 行程數量)
- System-wide table 儲存 OS 系統所有開啟的檔案資訊
- open-file tables 所有檔案指向 system-wide table
- 還會儲存獨立於 process 的資訊,如 disk location, access dates 還有 file size
- Open count
- 紀錄各個檔案被指向的次數,代表目前有多少 user 在存取該檔案
- 若不同 process 存取同一個檔案,只會有一份檔案,確保資料的一致性
![](img/Ch11.Open-File_Tables.jpg)
#### Open-File Attributes
- Open-file attributes (metadata)
- File pointer (per-process)
- File open count (system table)
- Dist location (system table)
- Access rights (per-process)
- File types
- 副檔名,如 .exe, .com, .obj …etc
- 副檔名沒有實質意義,只是讓 OS 先嘗試用副檔名對應的應用程式開啟
### Access Method
- Sequential access: 檔案可以視為 Array of bytes,Sequential access 在讀檔時是連續讀取的,因此需要的資訊就是目前的位置以及要讀取多少長度
- Read/write next (block)
- Reset: 將 file pointer 重設到檔案的開頭
- Skip/rewind n record
- 因為硬碟磁寫頭的關係,連續的檔案讀寫效率較高
- 邏輯上的連續,檔案儲存的實體硬碟位置不一定要連續
![](img/Ch11.Sequential-access_file.jpg)
- Direct (relative) access: 直接告訴系統要讀取檔案什麼位置的資料
- 讀取 sequence 資料的任意元素
- File operations 需要 block number 作為參數
- 通常是為了 random access 的執行
- 檔案由多個*大小固定*的區塊所組成,可直接將檔案指標移動到某個特定的區塊,並一次讀取整個區塊
![](img/Ch11.Simulation_of_sequential_access_on_a_direct-access_file.jpg)
- Index Access Methods
- Index: 檔案各區塊的指標
- 為了找到檔案中的 record:
- 尋找 index file --> 找到指標
- 利用指標去直接存取 record
- 若檔案太大, index 有可能太大
![](img/Ch11.Example_of_index_and_relative_files.jpg)
- Physical 的硬體設備,執行 random access 類型的操作效率較低,如果要支援 random access,通常要有 memory cache 才會夠快
### Directory Structure
#### Partition, Volume & Directory
- A partition 硬碟分割 (formatted or raw)
- raw partition (無檔案系統): UNIX swap space, database
- Volume 意指在硬碟格式化有了檔案系統之後的磁碟區域
- 一個 partition 可以為硬碟的部份區域或是由多個硬碟組成 (如分散式系統)
- 有一些儲存設備無法被分割,如 floppy disk (ex: 3.5 磁碟片)
- Dirctories 為檔案系統在 partition 儲存檔案資訊的區塊
![](img/Ch11.A_typical_file-system_organization.jpg)
#### Directory vs. File
- Directory: 為一群包含所有檔案資訊的節點
- Directory structure 跟 files 都位於硬碟中
![](img/Ch11.Directory_structure.jpg)
- Directory 相關的操作
- 搜尋檔案、創建檔案、刪除檔案、列出目錄清單(對現代檔案系統常常是效能瓶頸處,因為可能資料很多、檔案的樹狀結構很深)、重新命名檔案、遍歷檔案樹狀結構
#### Single-Level Directory
- 所有檔案都在一個 Directory 底下
- 檔案名稱必須唯一
- 當檔案越來越多,找尋檔案的效率非常差 (一定要遍歷)
![](img/Ch11.Single-level_directory.jpg)
#### Two-Level Directory
- 對每一個使用者有不同的 directory
- 路徑 = user name + file name
- 但是對每一個 user 來說,搜尋的效率仍然很差
![](img/Ch11.Two-level_directory_structure.jpg)
#### Tree-Structured Directory
- 絕對路徑: 由根節點開始
- 相對路徑: 由 directory (子節點)開始
![](img/Ch11.Tree-structured_directory_structure.jpg)
#### Acylic-Graph Directory
- 使用 links 來共享檔案或 directories
- UNIX-like: symbolic link (ln -s/spell/count/dict/count)(<新連結位置> <原被連結位置>)
- 檔案可以有多個絕對路徑 (同個檔案給不同節點使用)
- 那何時檔案才真正被刪除?
- 刪除檔案但留下 link --> dangling pointer
- 真正保險的作法是刪除 link 但不刪除檔案,當檔案的 counter 歸零時才把檔案刪除
![](img/Ch11.Acyclic-graph_directory_structure.jpg)
#### Gerneral-Grpah Directory
![](img/Ch11.General_graph_directory.jpg)
- 與 Acylic-Graph 最大的差異是可能有迴圈
- counter 在這邊無法確保 dangling pointer
- 可能有 self-referencing file
- 所以問題在於要如何處理 dangling pointer
- 定期做 Garbage collection
- 首先遍歷整棵樹,標記樹可達到的檔案及 directories
- 將沒有被標記的檔案 free 掉
- 但是當檔案很多時效率很差,所以很多檔案系統不支援
- 另一種解法是在檔案被建立時確認是否會產生迴圈
![](img/Ch11.General-Graph_Directory.jpg)
### File-System Mounting & File Sharing
#### File Mounting
- 要使用檔案系統時,必須先掛載到系統中,系統才能存取
- Mounting point: 意指 File System 被掛載的根路徑
- Mounting time:
- boot time (如系統槽,作業系統也是存在檔案系統中)
- automatically at run-time,如 usb 自動偵測
- manually at run-time
![](img/Ch11.File_System_Mounting_Example.jpg)
#### File Sharing on Multiple Users
- 對每一個使用者會有 (userID, groupID)
- ID is associated with every ops/process/thread the user issues
- 一個 user 可以屬於多個 groupID
- 每一個 file 有三種屬性等級
- owner (擁有者權限)
- group (所屬 group 的權限)
- others (非擁有者也非 group 成員的權限)
- 擁有者 attributes 描述擁有者對檔案操作的權限
- group/others 也有對應的 attributes
- group/others 的權限由 owner 或 root 決定
![](img/Ch11.File_Sharing_on_Multiple_Users.jpg)
#### Access-Control List
- 為了控制不同層級使用者的檔案權限,對每個 user 建立 Access-control list
- 當使用者發出對檔案操作的請求時,透過 ACL 的資訊來確認是否合法
- 但當使用者數量很多時,透過 ACL 確認太耗費資源及時間
- 現行作法是將使用者分成 3 類 --> 用 3 個 bit (RWX) 描述不同層級 user 對檔案的執行權限
- owner (e.g. 7 = RWX = 111)
- group (e.g. 6 = RWX = 110)
- public (others) (e.g. 4 = RWX = 100)
- 如下圖第一行中的 -rw-rw-r–,分別代表 owner-group-others 的權限
![](img/Ch11.Access-Control_List.jpg)
#### File Protection
- 檔案的擁有者/創建者必須能控制
- 能執行哪些操作
- 誰能執行這些操作
- 由 ACL 定義
- 檔案應該要防止以下問題
- physical damage (reliability): i.e. RAID
- 不適當的存取: i.e. 密碼
- RAID:
- 以下出自 Wiki
> 容錯式磁碟陣列(RAID, Redundant Array of Independent Disks),舊稱容錯式廉價磁碟陣列(Redundant Array of Inexpensive Disks),簡稱磁碟陣列。利用虛擬化儲存技術把多個硬碟組合起來,成為一個或多個硬碟陣列組,目的為提升效能或資料冗餘,或是兩者同時提升。
> 在運作中,取決於 RAID 層級不同,資料會以多種模式分散於各個硬碟,RAID 層級的命名會以 RAID 開頭並帶數字,例如:RAID 0、RAID 1、RAID 5、RAID 6、RAID 7、RAID 01、RAID 10、RAID 50、RAID 60。每種等級都有其理論上的優缺點,不同的等級在兩個目標間取得平衡,分別是增加資料可靠性以及增加記憶體(群)讀寫效能。
- 再參考 [筆記](https://hackmd.io/@Pl-eQT9CQaS0jhExKqL8_w/BkhOSR4jW/https%3A%2F%2Fhackmd.io%2Fs%2FByk4uIjGG)
> RAID在實體上是多顆硬碟,在系統中被當作一顆硬碟使用,而在作業系統底下,也還是可以將它分割為單一或多個分割區。因此建立好的RAID,使用起來跟單一硬碟是完全相同的,只是依組成方式的不同。RAID可以提供更大的容量、更高的讀寫效能,或是額外的「安全性」。(這裡所說的「安全性」,是指硬碟損毀之後資料重建、回復的能力,與加密防駭等功能無關)
## Ch12 File System Implementation
### File-System Structure
#### File-System Structure
- 記憶體跟硬碟之間資料傳輸 I/O 的單位為 block
- 一個 block 可能有多個 sector
- 一個 sector 通常為 512 bytes
- 一個作業系統可以使用多個檔案系統
- 如 NTFS, FAT32
- 檔案系統設計的兩個主要的問題
- 與使用者程序的介面 (上層)
- 與硬碟的介面 (下層)
#### Layered File System
- File System 實作上通常由以下三個部份組成
- manages metadata: 找到 file 並提供 file handler
- logical <–> physical mapping: 將程序要讀取內容的位置轉換成 block 的位置
- read d1, c73…: 轉換後 physical 的 hard-drive 及 block 位置
![](img/Ch12.Layered_File_System.jpg)
- 對使用者而言,只有一個檔案系統的 Interface (FS manager) 存在,而 FS manager 會去呼叫檔案對應的檔案系統來執行
![](img/Ch12.Layered_File_System_2.jpg)
### File-System Implementation
#### On-Disk Structure(Structrue 要永久存在,所以一定要在 disk 上)
- Boot control block (per partition): 存放啟動作業系統相關資訊的 partition
- 通常為 partition 的第一個 block (若是空的代表沒有 OS)
- UFS (Unix File Sys.): boot block, NTFS: partition boot sector
- Partition control block (per partition): partition details
- details: blocks 的數量, block 的大小, free-block-list, free FB pointers 等
- UFS: superblock, NTFS: Master File Table
- File control block (per file): 跟檔案相關的 details
- details: 權限, 大小, data blocks 的位置 (physical 的位置)
- UFS: inode, NTFS: stored in MFT (relational database)
- Directory structure (per file system): organize files
![](img/Ch12.On-Disk_Structure.jpg)
#### In-Memory Structure
- in-memory partition table: 每個被掛載的 partition 的資訊
- in-memory directory structure: 最近存取的 directories 的資訊
- system-wide open-file table: 保存每一個被開啟檔案的 File Control Block
- per-process open-file table: 記錄僅跟 process 有關的資訊,比如讀檔位置、開檔權限等,另外會有 pointer (file handler/descriptor) 指到對應的 system-wide table entry
#### File-Open & File-Read
- 開檔時,會先去 directory structure 看檔案是否在記憶體中,若不存在則去硬碟把檔案的 directory structure 資訊讀進來,找到我們要的節點並指向 file control block,這樣就有了檔案的所有資訊
- 讀檔時,File handler 為 per-process open-file table 的某一個 entry,拿到 handler 確保檔案的確 open 之後,必須從 system-wide table 取得檔案的實際位置,才能從硬碟存取檔案內容
![](img/Ch12.File-Open&File-Read.jpg)
#### File Creation Procedure
1. 作業系統配置一個新的 FCB
2. 更新 directory structure
- OS 將對應的 directory structure 讀進記憶體中,檢查有無同名稱的檔案
- 將新的檔案名稱以及 FCB 更新進 directory structure
- (在檔案關閉後) OS 將 directory structure 寫回硬碟中
3. 檔案出現在 user 的 directory command
#### Virtual File System
- VFS 提供物件導向的方法實作 file system
- 對於不同檔案系統,VFS 使用同一個 system call interface
- VFS 依 partition info 呼叫適當的 file system routines
- 在 Linux VFS 中有 4 個主要的物件類型
- inode --> 單獨的檔案 (on disk)
- file object --> 開啟的檔案 (檔案開啟後的操作物件)
- superblock object --> 整個 file system (對應到 partition)
- dentry object --> 單獨的 directory entry
- VFS 定義一系列需要實作的操作
- int open(…) --> 開啟檔案
- ssize_t read() --> 讀取檔案
#### Directory Implementation
- Linear lists (Linked list)
- 檔案名稱的串列,且有相應的指標指向 data blocks
- 容易實作但 performance 差
- insertion, deletion, searching
- Hash table --> linear list w/ hash data structure
- O(1) 搜尋時間
- 每一個 hash key 對應到一個 linked list,避免 hash collision
- hash table 通常有固定數量的 entry 數量
![](img/Ch12.Directory_Implementation.jpg)
### Allocation Method
#### Contiguous allocation
- 檔案在硬碟中的 block 擺放位置要連續
- 不需要 traverse 整個硬碟,只要簡單的計算就知道檔案在哪一個 block --> *磁頭轉動的數量最小化*
- Directory entry 只要存 start 跟 length,非常簡單
- 無論是 sequential 或 random 的存取都可以有效率地實作
- 很容易有 External fragment
- 解法 --> compaction
- 檔案不能隨意地增長
- 解法 --> extend-based FS (跟 linked list 結合)
#### Extent-Based File System
- 許多新的 file system 使用 modified contiguous allocation scheme
- extent 是一個硬碟連續的 blocks
- 一個檔案可以有一個或多個 extents
- 一個 extent 若用完了,系統會給檔案另一個 extent,利用 linked list 串起來
- extents block 必須有一個 pointer 紀錄下一個 extent 的位置
- 缺點
- Random access 的成本增加 (例如檔案在第三個 extent,必須要讀前兩個 extent 才知道第三個 extent 的位置,增加硬碟讀取的次數)
- 仍然有 internal & external 的 fragment
![](img/Ch12.Extent-Based_File_System.jpg)
#### Linked Allocation
- 檔案由一個 linked list of blocks 所組成
- 每一個 block 有 pointer 指向下一個 block
- 因為需要空間放 pointer,所以 block 中實際存放的大小為 block size - pointer size
- Directory 只需要存起始跟結束的 block id
- 建立新檔案時並不需同時就宣告檔案大小
- 缺點
- 只適合 sequential-access
- random access 需要遍歷 linked list
- 因為 link pointer 存放在 block 中,每次存取 block 硬碟都要讀取一次
- 需要 pointer 的空間 (4/512 = 0.78%)
- 解法:unit = cluster of blocks (把很多 blocks 組成一個大的 blocks,減少遍歷的長度)
- 會有 internal fragment 的問題
- Reliability
- 只要遺失一個 link 等於整份檔案遺失
![](img/Ch12.Layered_File_System_2.jpg)
#### FAT (File Allocation Table) file system
- FAT32
- 在 MS/DOS & OS/2 中使用
- 將 links 存成一個 table (所有檔案的 links)
- 每一個 table entry 大小為 32 bits
- 放在 partition 的起始點
![](img/Ch12.FAT32.jpg)
- FAT (table) 通常會 cache 在記憶體中
- Random access 的效率改善
- 硬碟只要讀取 FAT 就可以找到檔案 block 的位置
#### Indexed Allocation Example
- Diretory 擁有 file index block 的 address
- 每一個 file 有自己的 index block
- index block 儲存檔案資料的 block id
- 將所有 blocks 的 pointer 集中存在一個 block 中
- 優點
- direct & random access 非常有效率
- 沒有 external fragmentation
- 很容易去建立檔案 (只要有 free list,就知道哪裡有空間放檔案)
- 缺點
- 需要額外放置 index 的 blocks,這些 blocks 只能放 index,空間被浪費掉
- 另一個問題是,當檔案太大,一個 index block 存不下時,那 index block 應該要多大?
- linked scheme
- multilevel index
- combined(inode in BSD UNIX)
![](img/Ch12.Indexed_Allocation_Example.jpg)
**Linked Indexed Scheme**
- 將 index blocks 用 linked list 串起來
- 適合 small to medium size 的檔案
![](img/Ch12.Linked_Indexed_Scheme.jpg)
**Multilevel Scheme (two-level)**
- 第一階的 index block 儲存第二階 index blocks 的指標
- 第二階 index blocks 的指標才指向 file blocks
- 適合 large size 的檔案
![](img/Ch12.Multilevel_Scheme.jpg)
**Combined Scheme: UNIX inode**
- File pointer: 4B (32 bits) --> 只能存放 4GB ($2^{32}$) 的檔案
- 可以讓不同大小的檔案,使用不同類型的 index scheme,提昇檔案讀取的效率
- 假設一個 block 的 entry 數量為 12,綠色因為每個 index blocks 還要存 2 個其他 index - blokcs 的 pointer,所以檔案大小乘上 $2^{10}$
![](img/Ch12.UNIX_inode.jpg)
### Free-Space Management
- Free-Space list: 紀錄所有硬碟中 free 的 blocks
- Scheme
- Linked list (same as linked allocation)
- Grouping (same as linked index allocation)
- Counting (same as contiguous allocation)
- 檔案系統通常用與管理檔案相同的方式管理 free space
#### Bit vector
- Bit Vector (bitmap): 一個 block 一個 bit 來表示,0 為 free; 1 為 occupied
- 優點
- 簡單且快速 (硬體支援 bit-manipulation instruction)
- 缺點
- 為了效能必須存在快取中,當檔案很大時 bitmap 會非常大,超過快取的空間
- 例如一個 1TB(4KB block) 的硬碟需要 32MB 的 bitmap
![](img/Ch12.Bit_vector.jpg)
#### Grouping & Counting
- Linked List
- 所有的 free blocks 利用 linked-list 串在一起
- 將第一個 free block pointer 存在 disk 和快取中的特別位置
- 遍歷 list 效率低
- 可參考 FAT 的作法所有的 link-pointers 存在一個 table 當中
- Grouping
- 與 linked-index allocation 一樣
- 將 n free blocks 的 address 放在 1st block
- 前(n-1) pointers 是 free blocks
- 最後一個 pointers 指向另一個 grouping block
- Counting
- 與 contiguous allocation 一樣
- 儲存第一個 free block 的指標和 contiguous free blocks 的數量
## Ch10 Mass Storage Structure
### Disk Structure
- 硬碟被 addressed 為一維的 logical blocks 陣列
- logical block 為最小的轉換單位,一個 block 有很多個 sector
- Logical block 以 sequential 的方式映射到硬碟當中
- Sector 0 為最外圈 1st track 的 1st sector
- 從最外圈到最內圈
![](img/Ch10.Disk_Mechanism.jpg)
#### Sectors per Track
- Constant linear velocity (CLV)
- 每一個 track bits 的密度相等
- 外圈有更多的 sectors
- 保持相同的 data rate
- 所以內圈的旋轉速度要加快
- 應用: CD-ROM & DVD-ROM
- Constant angular velocity (CAV)
- 保持相同的旋轉速度
- 內圈的 bit density 較高
- 保持相同的 data rate
- 應用:硬碟
#### Disk I/O
- 硬碟逆用 I/O bus 跟電腦連接
- EIDE, ATA, SATA (Serial ATA), USB, SCSI 等等
- I/O bus 利用 controller 來控制
- Host controller (computer end)
- Disk controller (disk drive 內建)
### Disk Scheduling
#### Introduction
- Disk-access 時間主要分成三個部份
- seek time: 將讀寫頭移到需求的環上 (因為移動磁頭是機械式的行為, seek time 是影響硬碟讀取時間最主要的成份,scheduling 要盡可能讓磁頭在讀取多個檔案時能沿著一個方向移動)
- rotational latency: 旋轉讀寫頭到需求的 sector 上
- read time: 資料轉移的時間
- Disk bandwidth
- bytes transferred/(完成最後一個請求的時間 - 開始讀取的時間)
#### Disk Scheduling
- 最小化 seek time
- 處理硬碟 I/O 請求的算法
- FCFS(first-come, first-served)
- SSTF(shortest-seek-time-first)
- 普遍使用,但不是最佳的算法,且可能有 starvation 的問題
- 適合 loading 輕的情況
- SCAN
- 在高負載的情況下表現佳
- 沒有 starvation 的問題
- C-SCAN(circular SCAN)
- SCAN 的延伸,讓等待時間更為平均
- LOOK and C-LOOK
#### FCFS (First-Come-First-Served)
- 對磁頭移動來說,只關心 track 的 ID
- 假設現在的 request queue 為 98, 183, 37, 122, 14, 124, 65, 67,Head pointer 為 53,總移動量為 640
![](img/Ch10.FCFS.jpg)
#### SSTF (Shortest-Seek-Time-First)
- SSTF 排程為 SJF 的一種,可能導致 starvation 的問題
- 總移動量為 236
![](img/Ch10.SSTF.jpg)
#### SCAN Scheduling
- 磁頭先往一邊走再往另一邊走
- A.K.A elevator algorithm
- 各個請求被讀取的時間容易不平均 (ex: 若磁頭往左邊移動但新請求剛好再最右側)
- 總移動量為 236
![](img/Ch10.SCAN.jpg)
#### C-SCAN Scheduling
- 磁頭只往一個方向移動
- 同方讀到底之後,會直接回到另一頭 (Round-Robin)
- 各個讀取時間比 SCAN 算法更為平均
- 總移動量 382
![](img/Ch10.C_SCAN.jpg)
#### C-Look
- 會看是否有新的請求與目前磁頭移動方向同向,有的話繼續延該方向移動
- 若同方向沒有新的請求,則磁頭不會回到最開頭,會從另一個方向最靠近目前位置的請求開始
- 若決定好移動位置,但此時有個介於之間的請求進來 (如 53 -> 65,決定了之後 64 才進來),必須要等到下一次才會被讀取
![](img/Ch10.C_LOOK.jpg)
### Disk Management
#### Disk Formatting
- Low-level formatting (or physical formatting):
- 硬碟出廠時,已完成格式化作業
- 將 disk (magnetic recording material)切分成數個 dist controller 可讀寫得 sectors
- 每個 sector = header + data area + trailer
- header & trailer 存放 sector 的數量及 ECC (error-correcting code)
- ECC 藉由所有在 data area 的 bytes 計算,用 trailer 的 ECC 檢查硬碟有無壞軌
- data area size 通常為 512B, 1KB or 4KB
- OS 接著做以下兩個步驟來使用硬碟
- 將硬碟 partition 成一個或多個 groups of cylnders
- logical formatting (建立 file system)
#### Boot Block
- Bootstrap program
- 初始化 CPU, registers, device, controllers, memory 並且啟動 OS
- 第一階段 bootstrap code 存放在主機板的 ROM (Read Only Memory)
- 完整的 bootstap 放在 boot block of boot disk (系統碟)
- Booting from a Disk in Windows 2000
1. 執行 ROM 中的 bootstrap code
2. 讀取 MBR (Master boot recode) 的 boot code
3. 利用 partition table 找到 boot 的 partition 位置
4. 讀取 boot sector/block 並繼續開機作業
![](img/Ch10.Windows_Booting.jpg)
- Bad Blocks
- Simple disks like IDE disks
- 人工執行格式化程式來標記 bad block 對應的 FAT entry
- 壞軌的區域不允許用來 allocation
- Sophisticated disks like SCSI disks
- disk controller 會維護一個 bad blocks list
- List 在硬碟的生命週期會持續更新
- Sector sparing (forwarding): 將壞軌區的資料 map 到備用的區域
- 可能會影響到 disk-scheduling 的表現 (Physical 位置不連續)
- 在硬碟 formatting 時,每個 cylinder 會留少數備用的 sectors
- Sector slipping: 若要避免 physical 位置不連續的問題,則必需將整個資料做搬移
- Swap-Space Management
- Swap-space: 虛擬記憶體使用硬碟空間 (swap-space) 作為主記憶體的延伸
- UNIX: 允許使用多個 swap spaces
- Location
- 一般檔案系統的一部分 (e.g. NT)
- 效率較差
- 獨立的 partition (raw partition)
- 效率較好
- 但 Size 固定
- 允許上面兩種方式 (e.g. Linux)
- Swap Space Allocation
- 1st version: process 創建時放在記憶體,並複製一份完整的內容到連續的硬碟空間。主要問題是所有內容都複製,造成空間浪費
- 2nd version: 只複製要被 swap 的 pages 到 swap space
- Solaris 1:
- text segments(程式碼) 可以從檔案系統讀取,所以當 swap out 的時候直接捨棄
- 只有 anonymous memory (stack, heap, etc) 這些動態配置的記憶體空間,在虛擬記憶體創建時,會存到 swap space
- Solaris 2:
- 更進一步只有當動態配置的記憶體空間需要被 swap out 時,才動態配置 swap-space,而非虛擬記憶體創建時就建立副本
![](img/Ch10.Linux_Swapping.jpg)
### RAID
- RAID (Redundant Arrays of Inexpensive Disks)
- 用多個便宜的硬碟組成
- 利用 redundancy 提昇可靠度
- 藉由 parallelism 提昇效能
- RAID 排成不同 levels
- Striping: 將檔案切割放到多個硬碟上
- Mirror (Replication): 備份
- Error-correcting code (ECC) & Parity bit: 產生 redundant 的 code 來做 Error detection 甚至檔案修補
#### RAID 0 & RAID 1
- RAID 0: non-redudant striping
- 透過平行讀取增加效率
- I/O 的 bandwidth 與 striping 的數量成正比
- 若有 N 顆硬碟,則讀寫的頻寬提昇 N 倍
- RAID 1: Mirrored disks
- 藉由 redundancy 提昇可靠度
- 讀取的頻寬與硬碟數量成正比
- 寫入的頻寬保持不變,甚至比單顆硬碟還差一些
![](img/Ch10.raid0.jpg)![](img/Ch10.raid1.jpg)
#### RAID 2: Hamming code
- 以 Hamming code 將資料編碼後分割成獨立的位元
- 在資料中加入錯誤修正碼
- e.g.: Hamming code(7, 4)
- 4 data bits (在 4 個 disks 上) + 3 paritu bits (在 3 個 disks 上)
- 每一個 parity bits 為 3 個 data bits 的線性編碼
- 當有一個 disk 失效時,可以復原資料
- 可以偵測到兩個 disks(bits) 的錯誤
- 但只能還原 1 bit 的錯誤
- 比 RAID 1 更好的空間使用率 (RAID 1 100% redundancy v.s. RAID 2 75 % redundancy)
![](img/Ch10.raid2.jpg)
#### RAID 3 & 4: Parity Bit
- 雖然 RAID 2 可以偵測硬碟錯誤,但 disk controller 就能夠偵測 sector 的讀取是否正確
- 因為 low level 的 controller 會提供哪個 sector 壞掉,所以 1 個 parity bit 就足夠修正 1 個 disk 的失效
- RAID 3: Bit-level striping; RAID 4: Block-level striping
- 優點是更好的空間使用率 (33% overhead)
- 缺點是計算與儲存 parity bit 的 cost
![](img/Ch10.raid3.jpg)
- RAID 4: 因為 controller 不需要從多顆硬碟重建 block,有更高的 throughput
- RAID3 一個 byte 會跨多顆硬碟,速度取決於最慢的那個 bit; 而 RAID 4 可以一次從單顆硬碟讀取多個 bits 的資料
![](img/Ch10.raid4.jpg)
#### RAID 5: Distributed Parity
- RAID 2~4 都把 parity 集中在單獨的一群硬碟中,對使用者來說這些硬碟等於不存在
- RAID 5 則將資料及 parity 放在一起
- 可以避免過度使用單一顆硬碟
- RAID 3 & 4 讀的時候可以平行處理,但寫的時候因為 parity bits 集中擺放,等於只有一顆硬碟的效率
![](img/Ch10.raid5.jpg)
- RAID 5 讀取的 BW 增進 N 倍
- RAID 5 寫入的 BW
- 法一:
1. 讀取所有未變動的資料 (N-2) bits,
2. 重新計算 parity bit,
3. 將變動及 parity bit 寫回硬碟
- BW = N / ((N-2) + 2) = 1 倍 --> 完全沒變
- 法二:
1. 只讀取變動及 parity 的 bit,
2. 利用更新前後的差異計算新的 partiy,
3. 將變動及 parity bit 寫回硬碟
- BW = N / (2+2) = N/4 倍 --> 有加速效果
- RAID 6: P+Q Dual Parity Redundancy
- 類似 RAID 5, 但儲存更多的 redundant 資訊來防止多顆硬碟失效的情況
- 使用 ECE code (i.e. Error Correction Code) 而非 single parity bit
- Parity bits 通常也 strips 到多顆硬碟中
- 複雜度及 overhead 高,除非資料真的很重要,一般不會採用這種實作
![](img/Ch10.raid6.jpg)
#### Hybrid RAID
- RAID 0+1: Stripe then replicate
- RAID 1+0: Replicate then stripe
![](img/Ch10.raid01.jpg)
![](img/Ch10.raid10.jpg)
## https://www.youtube.com/watch?v=JquP9NXoa0Y&list=PLS0SUwlYe8cxj8FCPRoPHAehIiN9Vo6VZ&index=37
## References
- [周志遠教授作業系統講義](https://ocw.nthu.edu.tw/ocw/index.php?page=course_news_content&cid=141&id=999)
- [作業系統 Operating System 筆記](https://hackmd.io/@Chang-Chia-Chi/OS)
- [Operating Systems Course Notes Main Page](https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/)
- [作業系統OS](https://hackmd.io/@Pl-eQT9CQaS0jhExKqL8_w/BkhOSR4jW)
- [Operating System Concept 9th edition](https://www.os-book.com/OS9/slide-dir/index.html)
- [Mr. Opengate.](https://mropengate.blogspot.com/2017/09/operating-system-concepts.html)
- https://hackmd.io/@YiZjennnnn/process_scheduling
- [MarkDown語法大全](https://hackmd.io/@mrcoding/ryZE7k8cN)