# 作業系統 2025_謝仁偉 ## Chapter 1: Introduction - **作業系統**(Operating System):介於使用者與電腦硬體之間的程式,用於: - 執行使用者程式 - 讓解決問題更容易 - 使系統好用 - 有效率地運用硬體 - 電腦系統分四部分,由低到高為: - **硬體**(Hardware):CPU、記憶體、I/O設備 - **作業系統**(Operating system) - **應用程式**(Application programs):文字編輯器、瀏覽器、資料庫、遊戲 - **使用者**(Users):人、機器、其他電腦 ![image](https://hackmd.io/_uploads/HkFrhkJ2gl.png) - OS沒有絕對的定義,一般有兩個職責: - **資源分配者**:管理所有資源,並在競爭時公平分配 - **控制程式**:防止錯誤與不當使用。 - 開機或重啟時,會載入開機程式(Bootstrap Program),這程式會初始化系統、載入作業系統核心(Kernel)、開始執行。 - CPUs和裝置控制器透過匯流排來共享記憶體 - 裝置完成任務會中斷(Interrupt)CPU,讓 OS 處理下一步。中斷機制可暫停目前程式,轉而執行中斷服務程式(ISR) - trap/exception是軟體引發的中斷(錯誤、請求) - 作業系統是由中斷驅動,不會一直主動檢查所有事件,而是靠「中斷」來推動工作 - OS會保存CPU狀態(暫存器、程式計數器),判斷中斷的類型(polling、vector),做出對應的處置 ==(會考)== ![capture_temp](https://hackmd.io/_uploads/SJT-BaJ2xx.jpg) - **同步IO**:CPU 必須等待裝置完成 - **非同步IO**:I/O開始後先返回使用者程式,I/O會被放到等待佇列中,等待系統呼叫,使用者不能直接下I/O指令 - OS透過裝置表(Device-status table)來更新I/O條目,紀錄類別、地址、狀態 - **主記憶體**:隨機、揮發 - **二級儲存區**:非揮發 - **SSD**(Solid-state disks):比硬碟快、非揮發 - **快取**(Caching):將資訊複製到更快的儲存系統中,主記憶體可以被視為二級儲存的快取 - ![capture_temp](https://hackmd.io/_uploads/r1tiHA1nle.jpg) - **多處理器(Multiprocessor)系統**: - 有多個 CPU - 優點:==(會考)== 1. Increased throughput(提高吞吐量) 2. Economy of scale(規模經濟效益) 3. Increased reliability / fault tolerance(提高可靠性) - 又稱作平行系統(parallel systems)、緊密耦合系統(tightly-coupled systems) - **Graceful Degradation**:在部分元件發生故障時,仍能維持有限的服務,而不是完全停止運作 ==(會考)== - **Fault Tolerant**:在故障發生時仍能正常運作 ==(會考)== - 多處理器系統分為: - **非對稱**(Asymmetric):每個處理器負責特定任務 - **對稱**(Symmetric):所有 CPU 可執行所有任務 - 電腦系統組成的定義 ==(會考)==: - **CPU(中央處理器)**:執行指令的硬體 ==(The hardware that executes instructions)== - **Processor(處理器)**:一個包含一(多)個 CPU 的實體晶片 ==(physical chip that contains one or more CPUs.)== - **Core(核心)**:CPU 的基本運算單元 ==(The basic computation unit of the CPU)== - **Multicore(多核心)**:在同一個 CPU 上包含多個運算核心 ==(Including multiple computing cores on the same CPU.)== - **Multiprocessor(多處理器)**:包含多個處理器的系統 ==(Including multiple processors)== - **叢集系統**(Clustered System): - 多台電腦(節點)透過儲存區域網路(san,storage-area network)共享儲存資源 - 可用於高可用性(High Availability)或高效能運算(HPC) - 熱備援(Hot-standby)模式可在主伺服器故障時自動接手 - **非對稱叢集**(Asymmetric Clustering):其中一台機器處於熱備援(hot standby)模式 - **對稱叢集**(Symmetric Clustering):多個節點同時執行應用程式,並互相監控狀態 - **多程式設計**(Multiprogramming/Batch system):讓 CPU 交替執行多個工作,避免閒置 - **分時系統**(Time Sharing/multitasking): - CPU 以極高頻率在不同工作之間切換,讓使用者能夠同時操作多個程式 - 若有多個工作要執行,由CPU排成決定順序 - 若行程無法全部放入主記憶體,透過交換(swapping) 將部分程式暫時移出再載入 - 虛擬記憶體(virtual memory) 允許執行不完全載入至記憶體的程式 - OS 由「中斷」驅動: - **硬體中斷**:裝置發出的信號 - **軟體中斷**(Trap / Exception):軟體錯誤(如除以零)、要求 OS 服務、無限迴圈、程式互相干擾等問題 - **雙模式**(Dual-mode): - 讓 OS 保護自己與其他系統元件 - 兩種模式為**使用者模式**(User Mode)、**核心模式**(Kernel Mode) - 模式位元由硬體提供,用來判斷當前執行權限,核心模式下才能執行特權指令 - **系統呼叫**(System Call)會從使用者模式切換到核心模式 - 某些 CPU 支援多重模式,例如虛擬機管理模式(VMM Mode) - User Mode 切換至 Kernel Mode ![image](https://hackmd.io/_uploads/rkz04VjTlg.png =100%x) - **行程**(Process):執行中的程式 - 程式是靜態的、行程是動態的 - 結束時需釋放資源 - 單執行緒(Single-threaded)行程只有一個指令計數器(program counter),多執行緒(Multi-threaded)行程有多個指令計數器 - 在多處理器環境下,必須確保每份資料一致(cache coherency) - **輸入輸出子系統**(I/O Subsystem): - I/O記憶體管理,包含 ==(會考)==: - **緩衝(Buffering)**:在傳輸資料時暫存資料 - **快取(Caching)**:將部分資料儲存在更快的儲存區中以提高效能 - **周邊設備同步操作(Spooling)**:用硬碟當作中介緩衝區,讓 I/O(輸入輸出)與 CPU 能同時運作 ==(讓一個task的輸出和另一task的輸入同時進行)== - 把 I/O 任務先暫存在硬碟或磁碟檔案中,等裝置有空再執行,讓慢速裝置(例如印表機、磁帶機)能與高速的 CPU 同步運作 - 通用裝置介面(Device Driver Interface) - 特定硬體的驅動程式 - 保護與安全: - **保護**(Protection):控制行程或使用者對資源的存取權限 - **安全**(Security):防禦內部與外部攻擊,如病毒、DDoS、身份竊取。 - 系統透過User ID與group ID來控制存取權 - 權限提升(Privilege Escalation)允許程式臨時獲得更高權限 - Virtualization ==(會考)==: - **虛擬化**(Virtualization):一台實體電腦分成多個獨立環境,使主機 OS(Host)同時運行多個客體 OS(Guest) - **模擬**(Emulation):用於模擬不同的架構(如 PowerPC → x86),但速度慢 - **VMM**(Virtual Machine Manager):提供虛擬化服務 ![image](https://hackmd.io/_uploads/B1EZzLspee.png =100%x) - **分散式系統**(Distributed Systems): - 由多台實體電腦透過網路(TCP/IP)連接而成,常見形式有: - Local Area Network (LAN,區域網):房間、校園 - Wide Area Network(WAN,廣域網):城市間、國家間 - Metropolitan Area Network(MAN,都會網):同個城市內的建築 - Personal Area Network (PAN,個人網):如藍牙 - **核心資料結構**(Kernel Data Structures): - OS 內部使用各種資料結構: - 單向、雙向、循環鏈結串列 - 二元搜尋樹(BST)與平衡樹(如紅黑樹) - 雜湊表(Hash Map):用雜湊函數快速查找 [key:value] - 位元圖(Bitmap):用 0/1 表示多個項目的狀態 ![image](https://hackmd.io/_uploads/SycrSIjplg.png =100%x) ![image](https://hackmd.io/_uploads/Sy7UrIjalx.png =100%x) ![image](https://hackmd.io/_uploads/H1xwrLi6gx.png =100%x) - OS 運算環境: - **傳統運算**(Traditional):獨立桌機或工作站,但大多與網路連接 - **行動運算**(Mobile):手機與平板,具 GPS、感測器等 - **用戶端-伺服器**(Client-Server):伺服器提供資料或檔案服務 - **計算伺服器**(Compute Server):提供運算服務,如資料庫查詢 - **檔案伺服器**(File Server):提供檔案存取介面以儲存與擷取檔案 - **對等式網路**(P2P):節點彼此都是客戶端也是伺服器,如 Skype - **雲端運算**(Cloud Computing): - **IaaS**(基礎建設即服務,Infrastructure as a Service):提供硬體資源(伺服器、儲存) - **PaaS**(台即服務,Platform as a Service):提供應用平台(資料庫服務) - **SaaS**(軟體即服務,Software as a Service ):提供應用軟體(Google Docs) - **公有雲**(Public Cloud):任何人都可付費使用 - **私有雲**(Private Cloud):公司內部自行運行 - **混合雲**(Hybrid Cloud):兩者結合 - **即時與嵌入式系統**(Real-Time Embedded): - 具有時間限制(如汽車控制器、醫療裝置) - 必須在固定時間內完成任務 ## Chapter 2: Operating-System Structures - 作業系統提供的服務: - **使用者介面**(UI):提供圖形介面(GUI)、命令列介面(CLI)或觸控介面(Touch-Screen Interface) - **程式執行(Program Execution)**:載入程式到記憶體並執行 - **I/O 操作(I/O Operations)**:控制與管理輸入/輸出裝置 - **檔案系統操作(File-System Manipulation)**:讀寫檔案、建立與刪除、存取控制 - **通訊(Communication)**:行程之間或遠端系統之間交換資訊(訊息傳遞、共用記憶體) - **錯誤偵測(Error Detection)**:偵測並回報硬體或軟體錯誤 - **資源分配(Resource Allocation)**:為行程分配 CPU、記憶體、裝置、檔案等資源 - **紀錄(Accounting)**:追蹤資源使用量,用於評估效能 - **保護與安全(Protection and Security)**: - **保護(Protection)**:確保所有對系統資源的存取都受到控制 - **安全(Security)**:防止外部使用者或惡意程式入侵系統 ![image](https://hackmd.io/_uploads/Hkka-tspxe.png =100%x) - **命令列介面**(CLI/command interpreter):使用者輸入文字命令,例如 Linux shell 或 Windows cmd - **圖形介面**(GUI):使用視窗、滑鼠、圖示,如 macOS、Windows - **系統呼叫**(System Calls): - 應用程式與作業系統之間的程式設計介面 - 通常以高階語言(C 或 C++)撰寫 - 多數程式不會直接呼叫系統呼叫,而是透過**高階應用程式介面(API,Application Programming Interface)** 間接存取 - 每個系統呼叫都有一個對應的編號,系統呼叫介面(System-call interface)會以這些編號作為索引建立表格 ![image](https://hackmd.io/_uploads/ry0QBFjTgg.png =100%x) - 三種系統呼叫常見參數傳遞方式 ==(會考)==: 1. 暫存器傳遞:直接在 CPU 暫存器中傳參數,若參數過多,暫存器數量可能不足 ==(將參數存在 CPU register中傳送)== 2. 記憶體區塊傳遞:將參數存在記憶體中的一個表格或區塊,再把該區塊的位址存入暫存器 - Linux 混合使用這兩種,五個以內的參數放暫存器,超過五個就用區塊 ==(將參數存在 memory 的 block,傳 block 的 address)== 3. 堆疊傳遞:程式把參數壓入堆疊,OS 再從堆疊取出,不限制參數的數量與長度 ==(程式把參數push進堆疊,再由 OS POP)== - 系統呼叫的種類 ==(會考,例如舉五個例子)==: - **Process Control(行程控制)**: - create process, terminate process(建立、終止行程) - load, execute(載入與執行) - get process attributes, set process attributes(取得與設定屬性) - wait for time, wait event, signal event(等待事件或時間) - allocate and free memory(分配與釋放記憶體) - Dump memory if error(錯誤傾印) - Debugger for determining bugs, single step execution(除錯) - Locks for managing access to shared data between processes(使用鎖以控制共享資料存取) - **File Management(檔案管理)**: - create file, delete, open, close file(建立、刪除、開啟、關閉檔案) - read, write, reposition(讀取、寫入、重新定位檔案指標) - get and set file attributes(取得或設定檔案屬性) - **Device Management(裝置管理)**: - request device, release device(請求、釋放裝置) - read, write, reposition(讀寫裝置資料) - get device attributes, set device attributes(設定裝置屬性) - logically attach or detach devices(邏輯上掛載或卸除裝置) - - **Information Maintenance(資訊維護)**: - get time or date, set time or date, get system data, set system data(取得或設定時間、日期、系統資訊) - get and set process, file, or device attributes(修改行程、檔案或裝置屬性) - **Communications(通訊)**: - create, delete communication connection(建立與刪除通訊連線) - send, receive messages(若使用訊息傳遞模式(message passing),可依主機或行程名稱傳送與接收訊息) - Shared-memory model create and gain access to memory regions(共享記憶體模式下可建立並存取記憶區域) - **Protection(保護)**: - Control access to resources(控制資源存取) - Get and set permissions(取得與設定權限) - Allow and deny user access(允許或拒絕使用者存取) - **系統程式**(System Programs):提供使用者開發與執行的便利環境 - **檔案管理**:建立、刪除、複製、列印、清單管理 - **狀態資訊**:查詢時間、記憶體用量、使用者數量,或效能、日誌、除錯資料 - **檔案修改**:文字編輯器、搜尋與轉換指令。 - **程式語言支援**:編譯器、組譯器、除錯器。 - **程式載入與執行**:載入器、連結器、除錯系統。 - **通訊工具**:訊息傳遞、網頁瀏覽、郵件、遠端登入、檔案傳輸。 - **背景服務**: - 開機時啟動,提供排程、錯誤紀錄、列印等功能。 - 在使用者模式運作,稱為 daemon(守護行程) 或 subsystem(子系統)。 - **應用程式**:使用者自行執行的程式,不屬於作業系統本身 - OS 設計沒有單一最佳解,需先定義目標與規格,兩類目標: - **使用者目標**:好用、易學、可靠、安全、快速 - **系統目標**:易設計、易維護、具彈性、可靠、無錯且高效 - 設計原則 ==(會考)== - **Policy**:**What** will be done? - **Mechanism**: **How** to do it? - 使用高階語言更容易**移植(port)**但稍慢。 - **模擬**可讓 OS 跑在非原生硬體上。 - 作業系統結構: - **MS-DOS**: - 沒有模組劃分 - 功能層交錯、界線不明 - 為節省空間,犧牲了模組化與安全性 ![image](https://hackmd.io/_uploads/HJcofosTll.png =100%x) - **UNIX**: - 分為系統程式與核心兩層 - 核心提供檔案系統、排程、記憶體管理等 ![image](https://hackmd.io/_uploads/H1DRfso6xl.png =100%x) - **Layered Approach(分層設計)**: - 系統分成多層,由底層硬體至最高層使用者介面 - 每層僅能使用下層的服務 - 優點:模組化高、容易維護 ![image](https://hackmd.io/_uploads/BJ_W7oipgx.png =100%x) - **Microkernel(微核心)**: - 將非核心功能移至使用者空間 - 核心僅保留基本的記憶體、行程、通訊功能 - 優點:可靠、安全、可移植 - 缺點:效能稍低 - **Modules(模組化核心)**: - 現代 OS(如 Linux、Solaris)採可載入核心模組設計 - 物件導向概念,每個模組可獨立載入、卸載、互通 - 比分層結構更靈活 - **Hybrid Systems(混合式系統)**: - 多數現代 OS 都是混合設計 - Linux / Solaris:單核心(monolithic)+模組化 - Windows:主要為單核心,但含微核心元素 - macOS X:Mach 微核心 + BSD + 動態核心模組 +(Kernel Extensions) + Aqua 介面 - 除錯與效能調整: - 除錯是找出並修復錯誤的過程 - OS 會記錄錯誤日誌(log files) - **應用程式錯誤**可生成**核心傾印檔**(core dump) - **系統錯誤**可生成**崩潰傾印**(crash dump) - 效能調整(Performance Tuning)可藉由觀察追蹤紀錄與統計樣本進行 - 系統開機(System Boot): - 儲存在韌體(ROM)中的開機載入器(Bootstrap Loader)會定位核心、載入記憶體並啟動 - 常見開機載入器:GRUB,可選不同核心與版本 ## Ch3 Processes ### Process - 正在執行中的程式(job 與 process 幾乎可互換),活動狀態由兩項內容表示: - **程式計數器**(program counter):記錄下一行要執行的指令位址 - **處理器暫存器**(processor’s registers):儲存目前運算狀態 - 一個行程的記憶體配置依位址 **由低到高** 可分四個部分(**會考**): - **程式碼區**(text section):儲存可執行**程式碼** ==(code)== - **資料區**(data section):儲存**全域變數** ==(global variables)== - **堆積區**(heap):儲存執行期間**動態配置**的記憶體(例如 `malloc()` 或 `new`)==(dynamic memory allocation)== - **堆疊**(stack):函式呼叫**暫存資料**(函式參數、返回位址、區域變數)==(temporary data)== ![image](https://hackmd.io/_uploads/S1K-GJxRlx.png =100%x) - **Program** 是被動實體,**Process** 是主動實體 ### 行程狀態(Process State)==(會考)==: - **new**(建立中):正在被建立 - **running**(執行中):CPU 正在執行指令 - **waiting**(等待中):等待某事件發生 - **ready**(就緒):等待被分配到 CPU - **terminated**(結束):已完成執行 ![image](https://hackmd.io/_uploads/HJgp7Je0xx.png =100%x) - new → ready:被接納(admitted) - ready → running:被排程( scheduler dispatch) - running → waiting:因 I/O 或事件等待(I/O or event wait) - waiting → ready:I/O 或事件完成(I/O or event completion) - running → ready:因中斷(interrupt) - running → terminated:執行結束(exit) ### 行程控制區塊(Process Control Block, PCB) - 紀錄行程相關資訊,又稱任務控制區塊(task control block): - Process state(行程狀態):例如 running、waiting 等 - Program counter(程式計數器):記錄下一個要執行的指令位址 - CPU registers(CPU 暫存器):儲存行程在 CPU 上的暫存資料 - CPU scheduling information:排程資訊,例如優先權、排程佇列指標 - Memory-management information:記憶體分配資訊 - Accounting information:紀錄 CPU 使用量、執行時間、時間限制等。 - I/O status information:輸入輸出裝置使用情況、開啟的檔案清單等 ### Process Scheduling - 目標:最大化 CPU 使用率 - **行程排程器**(Process scheduler):從所有可執行的行程中選擇下一個要分配給 CPU 的行程 - 系統維護多個 scheduling queues(排程佇列): - Ready queue:準備好執行、等待被分配 CPU 的行程 - Wait queue:正在等待某事件(例如 I/O 完成)的行程 ![image](https://hackmd.io/_uploads/HJev9klRlx.png =100%x) ### 排程器(Schedulers)(==會考==) - **短期排程器**(**Short-term Scheduler** 或 **CPU Scheduler**): ==(決定哪個process被放入CPU)== - **負責選擇下一個要執行的行程並分配 CPU** - 必須非常快速 - **長期排程器**(**Long-term Scheduler** 或 **Job Scheduler**): ==(決定哪個process被放入ready queue)== - **負責決定哪些行程要被放入 ready queue** - 被觸發的頻率較低(秒或分鐘級)。 - 控制系統中的多程式程度(degree of multiprogramming) - **中期排程器**(**Medium-term Scheduler**): ==(當負載過高時,將 process 從 memory 移到 disk ,需要時再移回來)== - 當系統的多程式程度過高時,可以用來降低負載 - 暫時從記憶體中移除某些行程,存放到磁碟,等需要時再載回記憶體繼續執行,稱為交換(Swapping)。 ![image](https://hackmd.io/_uploads/HktNhyl0xl.png =100%x) - 行程可以分為兩種類型: - **I/O 密集行程**(I/O-bound process):花較多時間在輸入輸出操作(I/O),CPU 執行時間短但頻繁 - **CPU 密集行程**(CPU-bound process):花較多時間做運算(computation),CPU 執行時間長但次數少。 ### 上下文切換(Context Switch) - 當 CPU 從一個行程切換到另一個行程時,需要: - 儲存舊行程的狀態 - 載入新行程的已儲存狀態 - 上下文被儲存在行程控制區塊(PCB)中 ### 行程建立(Process Creation) - **父行程**(Parent Process)可以建立**子行程**(Child Processes),子行程也可以再建立其他行程,形成行程樹(Tree of Processes) - 每個行程都有行程識別碼(Process Identifier, PID) - A Tree of Processes in Linux: ![image](https://hackmd.io/_uploads/B11ZyllRgx.png =100%x) - 新行程的記憶體位址空間有兩種可能: - 子行程是父行程的完全複製(duplicate) - 子行程載入一個新的程式來執行 - **fork()**:用來建立新行程 - **exec()**:通常在 fork() 之後使用,以新程式取代原行程的記憶體內容 ![image](https://hackmd.io/_uploads/rysfeglRxx.png =100%x) ### 行程終止 - 行程執行完最後一條語句後,會呼叫 exit() 系統呼叫請 OS 刪除自己 - 狀態資料透過 wait() 回傳給父行程 - 父行程可使用 abort() 系統呼叫強制終止子行程 - 父行程可使用 wait() 等待子行程終止,並取得其結束狀態與行程識別碼(PID) - 連鎖終止(Cascading Termination):某些OS中,若父行程結束,所有子孫行程都會被終止 - **殭屍行程**(Zombie Process)(==會考==) - 行程已經結束,但父行程尚未呼叫 wait()的子行程 - 父行程呼叫 wait() 時被釋放 - **孤兒行程**(Orphan Process)(==會考==) - 父行程在未呼叫 wait() 前就終止的子行程 - OS會將其父行程改為 init 行程,init 行程會定期呼叫 wait() 回收它們 ### 行程間通訊(Interprocess Communication) - 在一個系統內,不同行程可以是: - 獨立的(independent) - **合作的**(cooperating) - 合作行程(Cooperating Process) - 原因包括(==會考==): - **資訊共享**(Information sharing) - **計算加速**(Computation speedup) - **模組化**(Modularity) - 透過行程間通訊(Interprocess Communication, IPC) 來互動,有兩種主要模型(==會考==): - 共享記憶體(Shared memory) - 訊息傳遞(Message passing) ![image](https://hackmd.io/_uploads/HyGANleRlx.png =100%x) - 生產者、消費者問題(Producer-Consumer Problem) - 生產者(Producer)行程負責產生資料 - 消費者(Consumer)行程負責取用資料 - 依照緩衝區(buffer)的設定可分為: - 無界緩衝區(unbounded buffer):緩衝區容量理論上無限大 - 有界緩衝區(bounded buffer):緩衝區大小固定,以循環陣列(circular array)方式實作 ### 訊息傳遞(Message Passing) - 一種行程用來通訊與同步行為(synchronize their actions)的機制 - IPC(行程間通訊)機制提供兩種操作: - 傳送訊息:send(message) - 接收訊息:receive(message) - 當行程 P 和 Q 想要通訊時,它們需要: - 建立一個通訊連結(communication link) - 透過 send/receive 交換訊息 - 直接通訊(Direct Communication):行程必須明確指定對方的名稱 - send(P, message):傳訊息給行程 P - receive(Q, message):從行程 Q 接收訊息 - 間接通訊(Indirect Communication):訊息透過郵箱(mailbox)或埠口來收發 - 每個 mailbox 有唯一的 ID - 只有共享同一個 mailbox 的行程才能互相通訊 - 同步機制(Synchronization) ==(會考)== - **阻塞式**(Blocking)為 **同步(Synchronous)** 傳輸: - 阻塞式傳送(Blocking send):傳送方會被卡住,直到訊息被接收為止 - Blocking receive(阻塞式接收):接收方會被卡住,直到有訊息可用為止 - **非阻塞式**(Non-blocking)為 **非同步(Asynchronous)** 傳輸: - 非阻塞式傳送(Non-blocking send):傳送方送出訊息後立即繼續執行。 - 非阻塞式接收(Non-blocking receive):接收方會立刻收到: - 一則有效訊息(valid message) - 空訊息(null message) - **通訊端**(Sockets) - 一個通訊的端點(endpoint),一個連線包含一對 socket - IP 位址:哪一台電腦 - Port(埠號):哪個應用程式 - IP 位址 127.0.0.1(loopback) 代表本機系統 - Java 中有三種類型的 socket: - **Connection-oriented (TCP)**:有連線導向的通訊(可靠、有順序) - **Connectionless (UDP)**:無連線導向的通訊(快速但不保證送達) - 遠端程序呼叫(Remote Procedure Calls,RPC) - Stubs(存根):用戶端的代理程式,代表伺服端上的實際程序 - 資料表示透過 External Data Representation (XDL) 格式處理 - 包含 Big-endian(高位元組在前) 與 Little-endian(低位元組在前) - 遠端通訊比本地通訊更容易發生錯誤,因此通常設計兩種傳遞策略 ==(會考)==: - **exactly once**:每個 RPC 只執行一次 - **at most once**:每個 RPC 最多執行一次(可能丟失) ## Ch4 Threads & Concurrency ### 執行緒(threads) - 程序內的執行單元,不同任務可以由不同的執行緒分別執行 - 大多數現代應用程式都是多執行緒(multithreaded) - 優點 ==(會考)==: - **回應性**(Responsiveness):當程式的一部分被阻塞時,其他執行緒仍可繼續執行 - **資源共享**(Resource Sharing):多個執行緒共享同一程序的資源,比起使用共享記憶體或訊息傳遞更容易 - **經濟性**(Economy):建立與切換執行緒比建立與切換程序便宜得多 - **可擴展性**(Scalability):程序能在多處理器架構上同時利用多核心運算 ### 多核心、多處理器(Multicore, Multiprocessor) - **平行系統(Parallel system)**:能同時執行多個任務 - 資料平行(Data parallelism):分配相同資料到多個核心上執行,每個核心執行相同的操作 - 任務平行(Task parallelism):分配不同的執行緒到不同核心上,每個執行緒執行不同的任務 - **並行系統(Concurrent system)**:任務之間交錯執行,非同時進行 - **晶片多執行緒**(Chip Multithreading, CMT) - 每條硬體執行緒都有自己的架構狀態,因此可被視為一個邏輯 CPU(Logical CPU) - 例如CPU有4核心 x 2 硬體執行緒 = 8 個邏輯 CPU ![image](https://hackmd.io/_uploads/rJ2GsqlAxx.png =100%x) - 並行 vs. 平行 ==(會考)== - 並行(Concurrent):多個任務在單核心交錯執行 - 平行(Parallel):多個任務於不同核心同時執行 ![image](https://hackmd.io/_uploads/rkPLi9lRxx.png =100%x) - Amdahl’s Law ==(會考)== - 計算在增加核心數量時,應用程式性能可提升的上限 - S:序列化比例(serial portion) - N:處理核心數量(number of processing cores) ![image](https://hackmd.io/_uploads/ryiYnqgAle.png) ### 多執行緒模型(Multithreading Models) - 執行緒支援可以出現在使用者層級(User level)或核心層級(Kernel level) - 使用者層級的稱為使用者執行緒(User Threads) - 核心層級的稱為核心執行緒(Kernel Threads) - 使用者執行緒與核心執行緒之間有三種對應模型 ==(會考)==: - 多對一(Many-to-One Model) - 一對一(One-to-One Model) - 多對多(Many-to-Many Model) - 雙層(Two-level Model) - **多對一(Many-to-One Model)** - **許多**使用者層級執行緒會對應到**單一**核心執行緒 - 若其中一個執行緒被阻塞(blocking),則所有執行緒都會被阻塞 - 多個執行緒無法同時平行執行 - 目前只有極少系統使用此模型 ![image](https://hackmd.io/_uploads/BJhTpigAxx.png =100%x) - **一對一(One-to-One Model)** - **每個**使用者層級執行緒都對應到**一個**核心層級執行緒 - 建立一個使用者執行緒時,也會自動建立對應的核心執行緒 - 與多對一模型相比,可支援更多的並行(concurrency) - 但若核心執行緒數量太多,會對系統效能造成負擔 - 範例:Windows、Linux、Solaris 9 之後版本 ![image](https://hackmd.io/_uploads/HJDtAigCxx.png =100%x) - **多對多(Many-to-Many Model)** - **多個**使用者層級執行緒對應到**少於或等於**數量的核心層級執行緒 - 開發者可以自由建立所需的使用者執行緒數量,核心執行緒則可在多處理器上平行(parallel)執行 - 當某個執行緒執行阻塞系統呼叫時,核心可以改排程其他執行緒執行 - 較少採用:實作困難、現今核心執行緒數量 ![image](https://hackmd.io/_uploads/rymNlnlCeg.png =100%x) ### 執行緒函式庫(Thread Libraries) - 提供建立與管理執行緒的 API,有兩種實作方式: - 完全在使用者空間實作,不依賴核心支援 - 核心層級函式庫,由作業系統直接支援 - 主要有三種執行緒函式庫: - POSIX Pthreads(可為使用者層或核心層函式庫) - Windows Threads(核心層函式庫) - Java Threads(以主機系統上的執行緒函式庫實作) - Pthreads(POSIX 執行緒) - 可以是使用者層級或核心層級的實作 - 是規範(specification),不是實作(implementation) - 常見於 UNIX 系統:如 Solaris、Linux、macOS - Windows Threads - WaitForMultipleObjects() 函式用於等待多個執行緒完成,需傳入四個參數: - 要等待的物件數量 - 指向物件陣列的指標 - 一個旗標(flag),用來指示是否要等待所有物件都完成才繼續 - 超時時間(timeout),可以設定為具體時間或 INFINITE - Java Threads - 由 JVM(Java Virtual Machine) 管理 - 透過底層作業系統的執行緒模型實作 - 兩種建立方式: - 繼承 Thread 類別(class) 並覆寫其中的 run() 方法 - 實作 Runnable 介面 ### 隱式多執行緒(Implicit Threading) - 顯式多執行緒過多會使程式正確性更難維護 - 開始由編譯器與執行期函式庫自動建立與管理執行緒 - 常見三種方法: - Thread Pools - OpenMP - Grand Central Dispatch(GCD) - Thread Pools(執行緒池) - 建立一組固定數量的執行緒,等待被分派任務 - 優點: - 更快速 - 限制執行緒數量,避免系統負載過重。 - 將「要執行的任務」與「建立執行緒的細節」分離,讓排程策略更彈性 - OpenMP(開放式多處理) - 編譯器指令(compiler directives) 和 API,支援 C、C++、Fortran - 用於在共享記憶體環境(shared-memory environment)中實現平行化程式 - 定義平行區塊,這些區塊可以同時在多核心上執行 - Grand Central Dispatch(GCD) - Apple 為 macOS 與 iOS 開發的技術 - serial queue(序列佇列): - 任務以 FIFO(先進先出)順序執行,一次只跑一個 - 每個行程都有一個 main queue(主佇列) - concurrent queue(並行佇列): - 仍然是 FIFO,但允許多個任務同時執行 ### 執行緒區域儲存(Thread-Local Storage, TLS) ==(會考)== - 允許每個執行緒擁有自己的資料副本 - 與一般區域變數不同:區域變數僅在函式呼叫期間有效,**TLS 在整個執行緒生命週期內都可見** - **相較於靜態變數(static),每條執行緒都有獨立副本** ## Ch5: CPU Scheduling ### 多重程序(Multiprogramming) - 目標是讓系統中隨時都有某個程序在執行,以達到最大化 CPU 使用率 - 程序由一連串的 **CPU–I/O 週期(CPU–I/O Burst Cycle)** 組成,每個周期包括: - CPU burst(CPU 密集計算階段) - I/O burst(等待輸入輸出階段) ### CPU Scheduler - 短期排程器(Short-term scheduler):從就緒佇列(ready queue)中挑選一個程序分配給 CPU 執行 - 排程決策可能發生在以下四種情況: 1. 程序從執行狀態(running)→ 等待狀態(waiting) 2. 程序從執行狀態(running)→ 就緒狀態(ready) 3. 程序從等待狀態(waiting)→ 就緒狀態(ready) 4. 程序終止(terminate) - **非搶先式(nonpreemptive)或合作式(cooperative)排程** :排程只在情況 1 與 4 發生 - **搶先式(preemptive)**:排程在所有情況發生 - 當多個程序共用資料時,搶先式排程可能導致競爭條件(race condition) - 若程序在修改關鍵核心資料時被搶先(例如 I/O 佇列),而核心或驅動程式同時讀寫該資料,將造成錯誤 - 因為中斷(interrupt)可隨時發生,且核心不能忽略它, 所以被中斷影響的程式區段必須受到保護 ### 派遣器(Dispatcher) - 負責把 CPU 控制權交給短期排程器選出的程序 - 派遣延遲(Dispatch latency):派遣器從停止一個程序到開始另一個程序所需的時間 ### Scheduling - Scheduling Criteria(排程評估準則)==(會考)== - CPU 利用率(CPU utilization):讓 CPU 儘量保持忙碌 - 吞吐量(Throughput):每單位時間內完成的程序數 - **周轉時間**(Turnaround time):完成一個程序所花的總時間(程序到達 => 完成) - **等待時間**(Waiting time):程序在就緒佇列中等待的時間(程序進入Ready queue => 得到CPU開始執行的**總時間**) - **回應時間**(Response time):從送出請求到第一次回應出現的時間(程序到達 => 得到CPU開始執行) ### First- Come, First-Served (FCFS) Scheduling(先來先服務排程) ==(會考)== ![image](https://hackmd.io/_uploads/B1Ta10x0gl.png =100%x) ![image](https://hackmd.io/_uploads/HJVgZ0xRle.png=100%x) - 車隊效應(Convoy Effect):當短任務排在長任務後面時,所有短任務都必須等待。 ### Shortest-Job-First (SJF) Scheduling(最短工作優先) ==(會考)== - 選擇最短的程序先執行 - 最佳(optimal)演算法,可得到最小平均等待時間 - 難點:系統無法預知下一次 CPU burst 的長度 ![image](https://hackmd.io/_uploads/S1Ph-Al0xl.png=100%x) ### Shortest Remaining Time First(最短剩餘時間優先) ==(會考)== - 可搶先版本的SJF - 利用先前 CPU 執行期的長度進行指數平均法(exponential averaging),估計下一次 CPU 執行期的長度: 1. 𝑡ₙ = 第 n 次實際的 CPU 執行期長度 2. τₙ₊₁ = 下一次預測的 CPU 執行期長度 3. α,滿足 0 ≤ α ≤ 1 4. 定義:τₙ₊₁ = α·𝑡ₙ + (1 − α)·τₙ - 當 α = 0 時:最近的觀測值(tₙ)不影響結果 - 當 α = 1 時:只考慮最近一次的 CPU burst ![image](https://hackmd.io/_uploads/S14uF0xClx.png) ### Priority Scheduling ==(會考)== - 每個程序會被分配一個優先權值(priority number),數字越小、優先權越高 - 分為可搶先(Preemptive)、不可搶先(Nonpreemptive) - 問題:Starvation(飢餓):低優先權程序可能永遠等不到執行機會。 - 解法:Aging(老化):隨著等待時間增加,逐漸提高程序的優先權 ![image](https://hackmd.io/_uploads/ryo7qCeCex.png) ### Round Robin (RR) ==(會考)== - 每個程序都獲得一小段 CPU 時間(稱為 time quantum q),當時間到達時,該程序會被搶先(preempted)並移到就緒佇列尾端 - 效能分析: - 若 q 很大:變成 FIFO(先來先服務) - 若 q 太小:上下文切換開銷太高 ![image](https://hackmd.io/_uploads/BycYR0gReg.png) - 通常 RR 的平均週轉時間比 SJF 高,但回應時間(response time)較佳 - q 應該比上下文切換時間大 ![image](https://hackmd.io/_uploads/H1N111W0le.png) ### Multilevel Queue(多層佇列排程) - 就緒佇列(ready queue) 被分成多個獨立的佇列,例如: - foreground(前景):互動型程序(interactive),使用RR - backgound(背景):批次程序(batch),使用FCFS ![image](https://hackmd.io/_uploads/B1dYJybCxl.png) - Multilevel Feedback Queue(多層回饋佇列):一個程序可以在不同佇列間移動,可藉此實現老化(aging)機制 ### Thread Scheduling - 使用者層執行緒: - PCS(Process-Contention Scope):CPU 的競爭發生在同一個程序的執行緒之間 - 核心層執行緒: - SCS(System-Contention Scope):系統中所有執行緒共同競爭 CPU ### Multiple-Processor Scheduling - 同質處理器(homogeneous processors):每顆 CPU 的能力相同 - 多處理器排程有兩種方式: 1. 非對稱多處理(Asymmetric multiprocessing): - 只有一顆 CPU 負責系統資料存取與排程 - 其他 CPU 只執行工作,減少共享資料的衝突 2. 對稱多處理(Symmetric multiprocessing, SMP): - 每顆 CPU 都能自行排程 - 所有 CPU 共用一個就緒佇列,或各自有私有佇列 - 目前最常見 - Processor affinity(處理器親和性):程序傾向在同一顆 CPU 上持續執行 - Soft affinity(軟性親和性):儘量讓程序留在同一顆 CPU,但在負載平衡時仍可移動 - Hard affinity(硬性親和性):程序明確指定可運行的 CPU 子集合 - NUMA(Non-Uniform Memory Access):雖然系統中的所有 CPU 可共用一個記憶體空間,但每個 CPU 對本地記憶體的存取速度更快,若要存取其他 CPU 的記憶體,速度會較慢 - Load Balancing(負載平衡):讓工作平均分配,確保所有 CPU 保持忙碌 - Push migration:由特定任務定期檢查各處理器的負載,若發現不平衡,就**推送**工作到閒置的處理器 - Pull migration:閒置的處理器主動**拉取**工作,從忙碌的 CPU 移轉部分任務過來 ### CPU Scheduling - Soft real-time systems(軟即時系統):不保證關鍵任務會準時被排程 - Hard real-time systems(硬即時系統):任務必須在截止時間前完成 ### Rate Monotonic (RM) Scheduling(速率單調排程) - 優先權依據任務週期的倒數決定: - 週期短:優先權高 - 週期長:優先權低 ![image](https://hackmd.io/_uploads/rkHvV1b0gl.png=100%x) ### Earliest Deadline First Scheduling(最早截止時間優先) - 優先權依照「截止時間」分配: - 截止時間越早:優先權越高 - 截止時間越晚:優先權越低 ![image](https://hackmd.io/_uploads/HyagrkWRxx.png) ## Ch6: Synchronization Tools - Race Condition:多個行程共同讀寫一個資料,會因為交錯執行而產生問題(e.g. i++會被拆成三個指令) ![capture_temp](https://hackmd.io/_uploads/r1Jqyn5WWe.jpg) - Critical section(臨界區): - 每個行程中需要共享資料的區域 - 一次只能有一個行程進入 - entry section:嘗試排隊進入臨界區 - critical section:讀寫共享資料,一次只有一個行程進入 - exit section:離開臨界區要做的處理 - remainder:不需要共享資料的程式碼區段 ![capture_temp](https://hackmd.io/_uploads/ByCFL2qZbl.jpg) - 臨界區問題的解法需要滿足以下三個條件: - Mutual Exclusion(互斥):某個行程在臨界區中,其他行程都不能進入自己的臨界區 - Progress(進度性):決定下一個行程能進臨界區的決定不能無限拖延 - Bounded Waiting(有限等待):每個請求的等待次數有上限 - 作業系統核心分為兩種: - Preemptive(可搶先):行程在核心模式執行時,仍然允許被搶先 - Non-Preeptive(不可搶先):進入核心模式後行程會持續執行,直到離開核心模式(早期使用) ### Peterson’s Solution (==會考==) - 針對兩個行程競爭的寫法,假設讀寫是原子操作 - 行程共用: - turn:輪到哪一個行程可以進入臨界區 - flag[2]:表示該行程是否準備進入臨界區(true) ![capture_temp](https://hackmd.io/_uploads/SyuJT3cbWx.jpg) - 互斥、進度、有限等待皆成立 ### Synchronization Hardware - 利用上鎖來保護臨界區 - Atomic:不可中斷的操作 ![capture_temp](https://hackmd.io/_uploads/S1zHJp9-bl.jpg) ### test_and_set Instruction (==會考==) - ![capture_temp](https://hackmd.io/_uploads/Syz13tC--l.jpg) 1.指令以原子方式執行 2.回傳參數target原本的值 3.把參數target的新值設為True - 用法:lock是共享變數 ![capture_temp](https://hackmd.io/_uploads/HyZnhtR-Ze.jpg) ### compare_and_swap Instruction: 1.指令以原子方式執行 2.回傳參數value原本的值 3.只有當value等於expected,value才會改成new_value - 用法:lock是共享變數,初始為0 ![capture_temp](https://hackmd.io/_uploads/HkEpaFA-Zg.jpg) ### Bounded-Waiting Mutual Exclusion: - 考慮n個process互斥且有限等待的解法,像是在排隊等號碼牌(待補充) ![capture_temp](https://hackmd.io/_uploads/ByaTRYCZ-x.jpg) ### Mutex Locks(互斥鎖): - 互斥鎖是用來保護臨界區的一種方式 - 先呼叫acquire()取得鎖,結束後再用release()釋放鎖,兩個函式為原子操作 - 缺點:需要busy waiting,因此也被稱為spinlock(自旋鎖) ![capture_temp](https://hackmd.io/_uploads/SJm_l9AZ-x.jpg) ### Semaphore(號誌) - 同步工具,比mutex lock更進階 - S:一個整數變數,同時允許多少個資源被使用 - 使用wait()和signal()兩個原子操作來做存取 ![capture_temp](https://hackmd.io/_uploads/SkiEo90-We.jpg) - Counting semaphore:S的整數值沒有限制範圍 - Binary semaphore:S的整數值為0或1,功能與mutex lock相同 - 事件順序控制範例: ![capture_temp](https://hackmd.io/_uploads/SJ0RWoCW-l.jpg) - 必須保證沒有兩個process在同一個Semaphore執行wait()或signal(),這導致變成一個新的critical section problem - Implementation with No Busy Waiting: - block:將呼叫這個操作的peocess放到waiting queue中 - wakeup:從wating queue中拿出一個process,把它放會到ready queue ![capture_temp](https://hackmd.io/_uploads/By4PBoRbZe.jpg) - Deadlock(死結):兩個程序無限互相等待對方 ![capture_temp](https://hackmd.io/_uploads/SJ_4VyNzbe.jpg) - Starvation(飢餓):indefinite blocking(無限期阻塞),某個程序永遠不會被喚醒,永遠無法進入臨界區 - Priority Inversion(優先順序反轉): - 當低優先序程序擁有高優先序所需的lock,會導致高優先程序被延遲 - 解決方法:使用Priority-Inhertance protocol(優先繼承協定),讓低優先序暫時提升優先權 ### Problems with Semaphores(==會考==) - 錯誤使用semaphores可能導致是時間錯誤,只在特定執行順序下發生,不一定可以重現 - e.g. signal(mutex)...wait(mutex):會導致多個程序同時進入臨界區(破壞互斥) - e.g. wait(mutex)...wait(mutex):第二次wait()會導致程序永久堵塞,因為semaphore永遠不可用 - e.g. 漏掉wait(mutex)或signal(mutex),可能造成mutual exclusion(互斥失效)或permanent block(永久阻塞) ### Monitors(監控器) - 高階同步機制,提供方便且安全的方式讓多個程序共享資源 - 一種抽象資料型別(abstract data type),內部變數只能被Monitor中的程序存取 - 任一時間只允許一個程序在Monitor內執行 ![capture_temp](https://hackmd.io/_uploads/rJTTllVGWe.jpg) ![capture_temp](https://hackmd.io/_uploads/rybz7bxNMWg.jpg) ### Condition Variables - condition x,y; - 在一個條件變數上允許兩種操作: - x.wait():呼叫這個操作的process會被暫停,一直等到有人對x呼叫x.signal() - x.signal():喚醒其中一個先前呼叫x.wait()而睡著的process,如果不存在則沒有效果 - 可以想成排隊的概念,x,y=某個隊伍,程序是在等待得人 ![capture_temp](https://hackmd.io/_uploads/ryLEHgEz-x.jpg) - 如果 process P 呼叫 x.signal(),而 process Q 目前在 x.wait() 中被暫停,會有兩種情況發生: - Signal and wait:P 呼叫 signal 後,自己等待,直到 Q 離開 monitor 或 Q 去等其他條件 - Signal and continue:P 呼叫 signal 後,自己繼續執行,Q 要等到 P 離開 monitor 後才會拿到機會進入 ![capture_temp](https://hackmd.io/_uploads/rk0RdgEfWl.jpg) - process 在進入 monitor 前必須先執行 wait(mutex),離開 monitor 時必須執行 signal(mutex) - signal-and-wait-scheme: - 發出 signal 的 process 可以使用 next 這個 semaphore 來暫停自已 - 變數 next_count 用來記錄目前有多少 process 被暫停在 next 上 - 對 monitor 中的每個 procedure F 改成下圖,可以保證 monitor 內的互斥(==會考排列、填空或改錯==) - Variables: - semaphore mutex; // (initially = 1) - semaphore next; // (initially = 0) - int next_count = 0; ![capture_temp](https://hackmd.io/_uploads/rkvLObVGZg.jpg) - x.wait 和 x.signal 的實做: ![capture_temp](https://hackmd.io/_uploads/BkYqFZEfZe.jpg) ![capture_temp](https://hackmd.io/_uploads/SyLiFZEzbg.jpg) - 用 FCFS 來喚醒條件變數上排隊的 process通常不夠好,因此會用有優先權的方式 x.wait(c) 的寫法, c 是 priority number(優先權數字),數字越小代表優先權越高 - Single Resource Allocation(單一資源配置): - 使用優先權數字來把單一資源分給多個競爭的程序,數字代表某個 process 打算使用這個資源的最長時間 - R 是型別 ResourceAllocator 的一個實例 ![capture_temp](https://hackmd.io/_uploads/rycm6-EMbg.jpg) - 用 monitor 來配置單一資源: ![capture_temp](https://hackmd.io/_uploads/ryejpZ4fWg.jpg) ## Ch8 Deadlocks - 每個行程使用資源的步驟都是:request(請求)、use(使用)、release(釋放) - DeadLcok Characterization:死結發生的條件是以下四個條件同時(simultaneously ==考單字==)成立(==會考簡答,6分寫標題,12分加說明==) 1. **Mutual exclusion(互斥)**:某些資源一次只能給一個process用,e.g.印表機 2. **Hold and wait(持有且等待)**:程序已經拿到至少一個資源,但還需要等別人的資源 3. **No preemption(不可搶佔)**:資源只能等持有持有的程序完成才能釋放 4. **Circular wait(循環等待)**:P0等p1的資源、P1等p2的資源......Pn等p0的資源,形成閉環 - 例如以下的 Mutex Locks 會發生死結: ![capture_temp](https://hackmd.io/_uploads/B1zr7QEGWx.jpg) - Resource-Allocation Graph:死結可以用系統配置圖來描述 - 頂點 (Vertices): - $P$:所有行程的集合 $\{P_1, P_2, ...\}$ - $R$:所有資源類型的集合 $\{R_1, R_2, ...\}$ - 邊 (Edges): - 請求邊 (Request Edge):$P_i \rightarrow R_j$ (行程 P 請求資源 R) - 分配邊 (Assignment Edge):$R_j \rightarrow P_i$ (資源 R 已分配給行程 P) ![image](https://hackmd.io/_uploads/ByiYiIUzZx.png) - 資源分配圖範例: ![image](https://hackmd.io/_uploads/HkY1hU8fZx.png) - 有死結的資源分配圖: ![image](https://hackmd.io/_uploads/BkzW2UUGWe.png) - 有循環但無死結的圖 ![image](https://hackmd.io/_uploads/SkkrhIIGWg.png) - **死結判斷方式**: - 如果圖中沒有循環 -> 沒有死結 - 如果途中有循環: - 若每種資源只有一個實例 -> 必有死結 - 若每種資源有多個實例 -> 可能有死結 - 處理死結的方法: - 做預防和避免,確保系統永遠不進入死結 - 允許死結發生再做修復 - 無視問題,假裝死結不存在,大多作業系統採用的辦法(包含Windows 和 Linux(UNIX)) - **預防死結**: - **互斥(Mutual Exclusion)**:對於不可共用的資源(如印表機)必須保持互斥,但可共用資源(如唯讀檔案)則不需要 - **持有並等待(Hold and Wait)**: - 規定行程在執行前必須一次申請完所有資源,或申請資源時手上不能有其他資源 - 缺點:資源利用率低,且可能導致飢餓(Starvation) - **不可搶佔(No Preemption)**:如果申請不到新資源,就必須釋放手上所有資源,之後再全部重新申請 - **循環等待(Circular Wait)**:將所有資源編號,強制行程只能依照編號順序(例如由小到大)申請資源 - **避免死結**: - 需要行程預先提供資訊(例如最多需要多少資源) - 演算法會動態檢查資源分配狀態,確保永遠不會出現循環等待 - 安全狀態(Safe State):(==會考==) - 如果系統能找到一個安全序列 (Safe Sequence) $<P_1, P_2, ..., P_n>$,讓所有行程都能順利執行完畢,系統就是處於安全狀態 - 就算 $P_i$ 要的資源現在不夠,只要等前面的 $P_j$ 用完歸還,$P_i$ 最終還是能拿到資源 - **死結判斷方式**: - 安全狀態 -> 沒有死結 - 不安全狀態 -> 可能有死結 - 避免死結演算法:確保系統永遠保持在安全區 ![image](https://hackmd.io/_uploads/BkFuedUfWl.png) - **宣告邊(Claim Edge)**: - 虛線,表示行程未來可能會請求該資源 - 當請求發生時,虛線變實線 - 只有當把請求邊變成分配邊不會導致圖中出現循環時,才允許分配 ![image](https://hackmd.io/_uploads/rkoP-dUG-e.png) ![image](https://hackmd.io/_uploads/S1Eu-OUGZx.png) ### Banker’s Algorithm (==會考==) - 適用於多重資源實例 - 行程需預先宣告最大需求(Maximum use) - 行程請求資源時若不安全則需等待 - Banker’s Algorithm 資料結構: - Available: 目前系統還剩多少資源 - Max: 每個行程最多需要多少資源 - Allocation: 目前已經給每個行程多少資源 - Need: 每個行程還需要多少資源才能完成($Need = Max - Allocation$) ![image](https://hackmd.io/_uploads/ryOST38fZl.png) #### Safety Algorithm - 安全性演算法:檢查當前狀態是否安全,用來找找安全序列,步驟如下: 1. 設定工作向量 Work:一開始 Work = Available (目前的閒置資源) 2. 設定 Finish 向量:所有 Process 的 Finish[i] = false (還沒做完) 3. 尋找可執行的 Process: - 從頭掃描,找到一個 Process $P_i$ 滿足: 1. Finish[i] == false (還沒做完) 2. Need[i] <= Work (它缺的資源,我們現在夠給它) 4. 模擬執行與回收: - 如果找到了這樣的 $P_i$,假設它執行完畢,釋放它手上的資源:$$Work = Work + Allocation[i]$$ - 標記 Finish[i] = true - 回到步驟 3,繼續找下一個 5. 判斷結果: - 如果所有 Process 都能被標記為 true,則系統處於 Safe State - 找到的順序 (例如 $<P_1, P_3, P_4, P_2, P_0>$) 即為 Safe Sequence 2 ![image](https://hackmd.io/_uploads/Hku0a3LGZx.png) #### Resource-Request Algorithm - 資源請求演算法 - 當行程 $P_i$ 提出請求時: 1. 檢查請求是否超過它宣告的最大值(Request[i] <= Need[i]) 2. 檢查系統有沒有足夠資源 (Request[i] <= Available) 3. 假裝分配給它,更新狀態: - Available = Available - Request[i] - Allocation[i] = Allocation[i] + Request[i] - Need[i] = Need[i] - Request[i] 4. 執行安全性演算法: - 如果是安全的,就給資源給 $P_i$ (Granted) - 不安全,收回分配的資源,讓 $P_i$ 繼續等待 ![image](https://hackmd.io/_uploads/H175k6IGZl.png) #### Banker’s Algorithm範例(==會考==) ![image](https://hackmd.io/_uploads/H1NC1aLzWl.png) ![image](https://hackmd.io/_uploads/BJACy6LfWe.png) ![image](https://hackmd.io/_uploads/BkRylTUG-e.png) ### Deadlock Detection - 若是允許系統進入死結狀態,就需要偵測演算法和恢復機制 - 單一實例系統: - 維護一張等待圖(wait-for graph): - 圖中的節點(Nodes)代表行程(Processes) - $P_i \rightarrow P_j$ 表示行程 $P_i$ 正在等待行程 $P_j$(釋放資源) - 定期執行演算法來搜尋圖中是否存在循環(cycle),如果圖中存在循環,就代表存在死結 - 偵測圖中循環的演算法所需的運算量級為 $n^2$,其中 $n$ 是圖中頂點(即行程)的數量 ![image](https://hackmd.io/_uploads/HkC9fTUfZe.png) - 多重實例系統: - Available(可用資源向量):一個長度為 $m$ 的向量,表示每種資源類型目前還剩下多少可用 - Allocation(已分配矩陣):一個 $n \times m$ 的矩陣,定義了目前已經分配給每個行程的各類資源數量 - Request(請求矩陣): - 一個 $n \times m$ 的矩陣,表示每個行程目前正在請求的資源 - 如果 $Request[i][j] = k$,代表行程 $P_i$ 正在請求 $k$ 個 $R_j$ 類型的資源實例 #### Detection Algorithm(==會考==) - $n$:行程 (Process) 的數量 - $m$:資源種類 (Resource types) 的數量 - Available:目前可用的資源向量 - Allocation:每個行程目前已持有的資源矩陣 - Request:每個行程目前正在請求的資源矩陣 - 執行步驟: 1. 初始化: - 設定暫存向量 $Work$ (代表當前可用的模擬資源量) 初始化為 $Available$ - 設定向量 $Finish$ (標記行程是否能完成) 的初始值: - 若 Allocation[i] $\neq 0$ (該行程持有資源),則設 Finish[i] = fals - 若 Allocation[i] $== 0$ (該行程沒佔用任何資源),則設 Finish[i] = true 2. 尋找可執行行程: - 尋找一個索引 $i$ (行程 $P_i$) 同時滿足以下兩個條件: 1. Finish[i] == false (尚未標記為完成) 2. Request[i] <= Work (該行程目前的請求量 $\le$ 系統目前的可用量) - 如果找不到這樣的 $i$,跳到步驟 4。 3. 回收資源: - 假設該行程 $P_i$ 獲得資源並執行完畢,將其資源歸還給系統: - Work = Work + Allocation[i] - Finish[i] = true - 回到步驟 2 繼續檢查下一個行程 4. 判斷死結: - 檢查 $Finish$ 向量 - 如果存在某個 $i$ 使得 Finish[i] == false,則代表系統處於死結狀態 - 所有 Finish[i] == false 的行程 $P_i$ 都是死結的一部分。 ![image](https://hackmd.io/_uploads/SJAfaC8zZg.png) ![image](https://hackmd.io/_uploads/BJQm60Izbl.png) ![image](https://hackmd.io/_uploads/By2r6AIzZg.png) ![image](https://hackmd.io/_uploads/ryz8pAIMbl.png) - 如果太常偵測死結,系統開銷會太大,如果太久沒偵測的話,影響的範圍會越來越大 ### Recovery from Deadlock(從死結恢復) - 終止行程: - 方法一:殺掉所有死結行程 - 方法二:一次殺一個,殺到死結解開為止 - 選擇程序標準:優先權、已執行時間、已用資源等 - 從死結恢復需要考慮以下三個問題: 1. 選擇受害者(Selecting a victim):成本最小化 2. 回滾(Rollback):把行程退回到安全狀態再重啟 3. 飢餓(Starvation):要確保同一個行程不會倒楣一直被選中當受害者 ## CH9 Main Memory - 記憶體單元只會看到一連串的位址與讀取請求(addresses + read requests),或者是位址加上數據與寫入請求(address + data and write requests) - 存取暫存器只需要一個 CPU 時脈週期 - 存取主記憶體可能需要多個週期,這會導致 CPU 停頓(stall) - 快取(Cache) 位於主記憶體和 CPU 暫存器之間 - 一對基底(base、起點)和限制(limit、長度)暫存器定義了邏輯位址空間,限制每個程式的存取範圍 ![image](https://hackmd.io/_uploads/r1-HK-ufZx.png) ![image](https://hackmd.io/_uploads/BJZqFZdGWl.png) ### Address Binding(位址綁定) - 程式以二進制形式存在磁碟上,執行時將其放入記憶體,若沒有支援,則載入到位址 0000 - 位址在程式生命週期的不同階段有不同的表示方式: - 原始碼位址(Source code):通常是符號(例如變數 count) - 編譯碼位址(Compiled code):綁定到可重定位(relocatable)位址 - 連結器或載入器(Linker/Loader):將可重定位位址綁定到絕對位址 - 每個綁定過程都是將一個位址空間映射到另一個位址空間 - 指令和數據綁定到記憶體位址可以在三個不同階段發生: - 編譯時期(Compile time): - 如果在編譯時就已經知道記憶體位置,就可以產生絕對代碼(absolute code) - 如果位置變了就需要重新編譯 - 載入時期(Load time): - 如果編譯時不知道行程將駐留在記憶體的何處,編譯器必須產生可重定位代碼(relocatable code) - 最終的綁定會延遲到載入時期 - 如果起始位址改變,只需要重新載入使用者程式碼 - 執行時期(Execution time): - 如果行程在執行過程中可以從一個記憶體區段移動到另一個區段,則綁定會延遲到執行時期 - 需要硬體支援位址映射 - 使用者程式的多步驟處理: (==考填空==) - 編譯時期(Compile time):原始程式(Source program) -> 編譯器(Compiler) -> 目的檔(Object file) - 載入時期(Load time):目的檔 + 其他目的檔 -> 連結器(Linker) -> 執行檔(Executable file) - 執行時期(Execution time/Run time):執行檔 + 動態連結函式庫(Dynamically linked libraries) -> 載入器(Loader) -> 記憶體中的程式 ![image](https://hackmd.io/_uploads/BJPkSfuM-l.png) - 「邏輯位址空間」綁定到單獨的「實體位址空間」的概念,是正確記憶體管理的核心 - 邏輯位址(Logical address):虛擬位址(virtual address),由 CPU 產生 - 實體位址(Physical address):記憶體單元看到的位址 - 編譯、載入時期:邏輯位址和實體位址相同 - 執行時期:邏輯位址和實體位址不同 - 邏輯位址空間:程式產生的一組所有邏輯位址 - 實體位址空間:對應於這些邏輯位址的一組所有實體位址 ### Memory-Management Unit (MMU) - 記憶體管理單元:在執行時期將虛擬位址映射到實體位址的硬體裝置 - 簡單方案:在使用者行程產生位址並送往記憶體時,將重定位暫存器(Relocation register)的值加到每一個位址上 - 基底暫存器(Base register)在此稱作重定位暫存器 - 使用者程式處理的是邏輯位址,不會看到真實的實體位址 ### Dynamic Relocation Using a Relocation Register - 常式(Routine)直到被呼叫時才載入 - 未使用的常式永遠不會被載入,有更好的記憶體空間利用率 - 所有常式都以可重定位的載入格式保存在磁碟上 - 當需要大量程式碼來處理不常發生的情況時特別有用 ![image](https://hackmd.io/_uploads/rJ-uLBuGbe.png) ### Dynamic Linking and Shared Libraries - 動態連結函式庫(Dynamically linked libraries,DLLs):系統函式庫,在程式執行時才連結到使用者程式 - **靜態連結(Static linking)**:系統函式庫和程式碼由載入器合併成一個二進位程式映像 - **動態連結(Dynamic linking)**:連結被推遲到執行時期 - 通常用於系統函式庫,例如標準 C 語言函式庫 - 這些函式庫可以在多個行程之間共享,因此主記憶體中只需有一個 DLL 的實例,DLLs 也被稱為共享函式庫(shared libraries) - DLLs 可以擴展到函式庫更新(例如錯誤修復),函式庫可以被新版本取代,所有引用該函式庫的程式將自動使用新版本 ### Swapping - 一個行程可以暫時從記憶體被置換(swapped)到備用儲存裝置(backing store),然後再帶回記憶體繼續執行,使得行程的總實體記憶體空間可以超過實際的實體記憶體 - 備用儲存裝置(Backing store):快速的磁碟,夠大以容納所有使用者的記憶體映像副本,必須提供對這些映像的直接存取 - Roll out, roll in:用於優先級排程演算法的置換變體,低優先級行程被換出,以便載入並執行高優先級行程 - 置換時間的主要部分是傳輸時間,總傳輸時間與置換的記憶體量成正比 - 系統維護一個就緒佇列(ready queue),包含那些記憶體映像在磁碟上的就緒行程 - 現代作業系統通常不再使用標準置換,因為所需時間太長 ![image](https://hackmd.io/_uploads/H163F8_G-l.png) - 如果下一個要上 CPU 的行程不在記憶體中,需要換出(swap out)一個行程並換入(swap in)目標行程 - 上下文交換(Context switch)時間會變得非常長,e.g. 100MB 的行程置換到傳輸率為 50MB/sec 的硬碟需要4秒 - 置換不能用於待處理的 I/O,或者總是透過核心(kernel)的緩衝區進行傳輸,然後再從核心複製到行程的位址空間,這稱為雙重緩衝(double buffering),會增加負擔 - ### Contiguous Allocation(連續配置) - 主記憶體通常分為兩個分區(partitions): - 常駐作業系統(Resident OS),通常放在低位址記憶體,包含中斷向量 - 使用者行程則放在高位址記憶體 - 每個行程包含在記憶體的一個單一連續區段中 - 使用重定位暫存器來保護使用者行程彼此不受影響,也不受作業系統程式碼和資料改變的影響 - 基底暫存器(Base register):包含最小實體位址的值 - 限制暫存器(Limit register):包含邏輯位址的範圍(每個邏輯位址必須小於限制暫存器) - MMU 動態映射邏輯位址 - 這允許核心程式碼是暫時的 (transient) 且核心大小可以改變 ![image](https://hackmd.io/_uploads/rJSh0UuMZl.png) ### Multiple-partition Allocation(多重分區配置) - **可變分區大小**(Variable-partition sizes):根據特定行程的需求調整大小,用於提高效率 - **孔洞**(Hole):一塊可用的記憶體區塊,各種大小的孔洞散布在記憶體各處 - 當一個行程到達時,系統會從一個夠大的孔洞中配置記憶體給它 - 當行程離開時,它會釋放其分區,相鄰的空閒分區會被合併 - 作業系統維護: 1. 已配置的分區(allocated partitions) 2. 空閒的分區(free partitions) ![image](https://hackmd.io/_uploads/HkRKz85MWx.png) - 如何動態分配儲存位址: - 首次配適(First-fit):配置「第一個」夠大的孔洞 - 最佳配適(Best-fit):配置「最小」但夠大的孔洞,必須搜尋整個清單,除非清單依大小排序 - 剩餘孔洞最小 - 最差配適(Worst-fit):配置「最大」的孔洞,必須搜尋整個清單 - 剩餘孔洞最大 - First-fit 和 Best-fit > Worst-fit ### Fragmentation(碎片化)(==會考==) - **外部碎片(External Fragmentation)**:總記憶體空間足以滿足請求,但它不是連續的 - **內部碎片(Internal Fragmentation)**:配置的記憶體可能比請求的記憶體稍大,但多餘部分未被使用 - 緊縮(Compaction):重新排列記憶體內容,將所有空閒記憶體集中成一個大區塊,來減少外部碎片 ### 分段(Segmentation) - 支援使用者觀點的記憶體管理機制 - 一個程式是區段(segments)的集合 - 一個區段是一個邏輯單元,例如:主程式、程序、函數、方法、物件、區域變數、全域變數、堆疊、陣列 ![image](https://hackmd.io/_uploads/BJJ32Ucfbx.png) ![image](https://hackmd.io/_uploads/rJ333UqMWl.png) - 邏輯位址:由二元組(two tuple)組成,<區段編號(segment-number), 位移量(offset)> - **區段表(Segment table)**:映射二維的實體位址,每個表項目有: - **基底(base)**:包含區段在記憶體中開始的實體位址 - **限制(limit)**:指定區段的長度 - **區段表基底暫存器(Segment-table base register,STBR)**:指向區段表在記憶體中的位置 - **區段表長度暫存器(Segment-table length register,STLR)**:指示程式使用的區段數量 - 如果區段編號 s < STLR,則 s 是合法的 - 保護(Protection):每個區段表項目紀錄 - 驗證位元 (validation bit):0 代表非法區段 - 讀/寫/執行權限 (read/write/execute privileges) ![image](https://hackmd.io/_uploads/S1oRywczWx.png) ### Paging(分頁) - 行程的實體位址空間可以是不連續的,只要有可用的實體記憶體,就可以配置給行程 - 優點:避免了外部碎片、避免了記憶體區塊大小不一的問題 - 缺點:仍然會有內部碎片 - 頁框(frames):實體記憶體分割成固定大小的區塊 - 分頁(pages):邏輯記憶體分割成同樣大小的區塊 - 要執行大小為 N 個分頁的程式,需要找到 N 個空閒頁框並載入程式 - 分頁表(page table):將邏輯位址轉換為實體位址 - 位址轉換機制: - **分頁編號**(Page number, p):用作分頁表的索引,分頁表包含每個分頁在實體記憶體中的基底位址 - **分頁位移**(Page offset, d):與基底位址結合,定義送往記憶體單元的實體記憶體位址 ![image](https://hackmd.io/_uploads/Bk6UUwqMWe.png) ![image](https://hackmd.io/_uploads/r1QFLv5Gbx.png) ![image](https://hackmd.io/_uploads/ryv6IwqfZe.png) ![image](https://hackmd.io/_uploads/ryHiiP5Gbx.png) ![image](https://hackmd.io/_uploads/BJO6iPcz-g.png) ![image](https://hackmd.io/_uploads/B1Xb2v5zZe.png) - 分頁表保存在主記憶體中 - **分頁表基底暫存器(Page-table base register,PTBR)** 指向分頁表 - **分頁表長度暫存器(Page-table length register,PTLR)** 指示分頁表的大小 - 每一次資料/指令存取需要兩次記憶體存取,查分頁表+拿資料/指令 - **轉譯後備緩衝區(TLBs, translation look-aside buffers)** :或稱關聯式記憶體(associative memory),使用特殊的快速查找硬體快取來解決兩次記憶體存取的問題 - 固定(Wired down):重要資訊會被固定在TLB上 - 位址空間識別碼(ASIDs):識別每個行程位址,避免拿錯 ![image](https://hackmd.io/_uploads/rJzkCvqzZg.png) ### 有效存取時間(EAT) (==會考==) - 命中率(Hit ratio)$\alpha$:在關聯式暫存器(TLB)中找到分頁編號的機率 - 有效存取時間(Effective Access Time):$\alpha$ $\times$ 存取時間 + (1 - $\alpha$) $\times$ 2倍存取時間 - 考慮 $\alpha = 80\%$,記憶體存取時間為 10ns: - $EAT = 0.80 \times 10 + 0.20 \times 20 = 12ns$ 2 - 考慮 $\alpha = 99\%$ - $EAT = 0.99 \times 10 + 0.01 \times 20 = 10.1ns$ ### 記憶體保護(Memory Protection) - 透過保護位元 (protection bit) 來指定為唯讀(read-only)或讀寫(read-write) - **有效-無效位元(Valid-invalid bit)** 附在分頁表的每個項目上: - Valid(有效):表示該分頁在行程的邏輯位址空間內,是合法的分頁 - Invalid(無效):表示該分頁不在行程的邏輯位址空間內 - 或使用分頁表長度暫存器(page-table length register,PTLR) ![image](https://hackmd.io/_uploads/H1Zmhtcz-l.png) ### 共享分頁(Shared Pages) - **共享代碼**(Shared code): - 一份唯讀(read-only)或重入(reentrant) 程式碼在行程間共享 - 類似於多個執行緒共享同一個行程空間 - **私有代碼與數據**(Private code and data): - 每個行程保存一份獨立的代碼和數據副本 - 私有代碼和數據的分頁可以出現在邏輯位址空間的任何地方 ![image](https://hackmd.io/_uploads/Hy6z155zZl.png) ### 分頁表(Page Table)(==會考==) - 直接建立分頁表需要巨大的主記憶體連續空間 - 解法:使用階層式分頁(Hierarchical Paging)或雜湊分頁表(Hashed Page Tables)或反向分頁表(Inverted Page Tables) #### 階層式分頁(Hierarchical Paging) - 將邏輯位址空間拆解成多個分頁表,例如兩層分頁表 - 兩層分頁表(two-level page table): - 外部分頁表(Outer page table):只有一個,項目指向內層分頁表 - 分頁表的分頁(Page of page table):內層表格,項目指向真正的記憶體 Frame ![image](https://hackmd.io/_uploads/rJoYfqcGWg.png) - 一個邏輯位址(在 32-bit 機器,1K 分頁大小)被分為: - 一個 22 bits 的分頁編號,分為: - 一個 12-bit 的分頁編號($p_1$) - 一個 10-bit 的分頁位移($p_2$) - 一個 10 bits 的分頁位移 - **前向映射分頁表**(forward-mapped page table): - $p_1$(12 bits):外部分頁表的索引 - $p_2$(10 bits):內部分頁表的位移 - d(10 bits):分頁位移 ![image](https://hackmd.io/_uploads/ryjMNqqzWx.png) ![image](https://hackmd.io/_uploads/BJB74qqfbx.png) - 兩層分頁表對於 64 bit 不太夠用,須拆成三層,但會導致效率變差 ![image](https://hackmd.io/_uploads/rkEK495Gbe.png) #### 雜湊分頁表(Hashed Page Tables) - 虛擬分頁編號透過雜湊(hushed)進入一個分頁表 - 該分頁表包含一個元素鏈結串列(linked list),雜湊到同一位置的元素串在一起 - 每個元素包含:(1) 虛擬分頁編號、(2) 映射的頁框值、(3) 指向下一個元素的指標 - 虛擬分頁編號在鏈結中比對、尋找匹配,如果找到則提取對應的實體頁框 - 64-bit 的變體是叢集分頁表(clustered page tables): - 類似雜湊表,但每個項目指向多個分頁(例如 16 個),而非 1 個 - 對於稀疏(sparse)位址空間特別有用 ![image](https://hackmd.io/_uploads/ryVtIccGWe.png) #### 反向分頁表(Inverted Page Tables) - 追蹤所有實體分頁,記憶體的每個實體分頁只有一個項目 - 項目包含:存放在該實體位置的虛擬位址,以及擁有該分頁的行程資訊(PID) - 優點:減少儲存分頁表所需的記憶體 - 缺點:當分頁引用發生時,搜尋表格的時間增加 ![image](https://hackmd.io/_uploads/ryCBucqfWg.png) ## Ch10 Virtual Memory - 虛擬記憶體(Virtual Memory): - 將「使用者邏輯記憶體」與「實體記憶體」分開,只有部分程式需要存在記憶體中才能執行 - 「邏輯位址空間」可以比「實體位址空間」大得多 - 允許位址空間被數個行程共享 - 允許更有效率的行程建立 - 更多程式可以同時並行、載入或置換行程所需的 I/O 更少 - 虛擬記憶體可以透過請求分頁(Demand paging)、請求分段(Demand segmentation)實作 ![image](https://hackmd.io/_uploads/B1ldHjcGWg.png) ### 虛擬位址空間(Virtual address space) - 行程如何儲存在記憶體中的邏輯視圖 - 邏輯位址空間設計為堆疊從最大邏輯位址開始並「向下」增長,而堆積則「向上」增長 - 兩者之間未使用的位址空間是洞(hole) - 允許稀疏(sparse)的位址空間,留下的洞可用於增長、動態連結函式庫(DLLs)等 ![image](https://hackmd.io/_uploads/ryrJ0o5fZe.png) ![image](https://hackmd.io/_uploads/H1rLCj9GWg.png) ### 請求分頁(Demand Paging) - 在載入時將整個行程帶入記憶體,或者只有當需要時才將分頁帶入記憶體 - 減少 I/O、減少記憶體需求、更快的回應、更多使用者 - 懶惰置換器(Lazy swapper):除非分頁被需要,否則絕不將分頁置換入記憶體 - Pager:處理分頁的置換器 ![image](https://hackmd.io/_uploads/BJfn13cG-x.png) ### 有效-無效位元(Valid-Invalid Bit) - 每個分頁表項目都有一個有效-無效位元,V 代表在記憶體中,i 代表不在記憶體中 - 最初,所有項目的位元都設為 i - MMU 位址轉換期間,如果分頁表項目中的位元是 i 會觸發分頁錯誤(Page Fault) ![image](https://hackmd.io/_uploads/HkXCIhcM-x.png) ### 分頁錯誤(Page Fault) - 如果有個參考指向一個分頁,第一次參考到該分頁時會觸發作業系統的 trap: 1. 檢查是否為無效的參考,是的話終止,否則開始交換分頁 2. 找到一個空閒的頁框,透過排程磁碟操作,將分頁置換到頁框中 3. 設定位元為 v 4. 重新啟動指令 ![image](https://hackmd.io/_uploads/BkvhvncMZx.png) - 純請求分頁(Pure demand paging):記憶體中沒有任何分頁,接著每個其他分頁都在第一次存取時發生 page fault - 請求分頁的效能(==有可能考排序==) ![image](https://hackmd.io/_uploads/rJmyc29f-l.png) - 服務時間分成三個部分: - 服務分頁錯誤中斷:很快 - 讀取分頁:花很多時間 - 重新啟動行程:很快 - 分頁錯誤率(Page Fault Rate)$0 \le p \le 1$ - 如果 $p = 0$,沒有分頁錯誤 - 如果 $p = 1$,每次參考都發生錯誤 - 有效存取時間(EAT):$EAT = (1 - p) \times \text{記憶體存取時間} + p \times (\text{分頁錯誤負擔} + \text{換出時間} + \text{換入時間})$ ![image](https://hackmd.io/_uploads/SJ_Hi3cGWl.png) ### Copy-on-Write - 寫入時複製(Copy-on-Write, COW): - 允許父行程和子行程最初在記憶體中共享相同的分頁 - 如果任一行程寫入共享分頁,則會建立該共享分頁的副本 ![image](https://hackmd.io/_uploads/rJGG2h9G-l.png) ![image](https://hackmd.io/_uploads/H13fn2cGZx.png) ### 頁面置換(Page Replacement) - 透過頁面置換來防止記憶體過度分配 - 使用修改位元(modify/dirty bit)來減少頁面傳輸的負擔,只有被修改過的頁面才需要寫回磁碟 - Process 1 需要載入分頁 B,但hysical Memory 已經滿了: ![image](https://hackmd.io/_uploads/ByIB66cfbg.png) - 基本頁面置換: 1. 在磁碟上找到所需分頁的位置 2. 找到一個空閒頁框: - 如果有空閒頁框,就使用它 - 如果沒有空閒頁框,使用頁面置換演算法選擇一個受害者頁框(victim frame),受害者是髒 (dirty)的話就寫回磁碟 3. 將所需分頁帶入空閒頁框;更新分頁表和頁框表 4. 透過重新啟動造成 trap 的指令來繼續行程 ![image](https://hackmd.io/_uploads/Hk0ZyC5Mbg.png) - 頁框配置演算法(Frame-allocation algorithm)決定: - 給每個行程多少個頁框? - 要置換哪些頁框? - 頁面置換演算法 (Page-replacement algorithm): - 希望在第一次存取和重新存取時都有最低的分頁錯誤率 - 後續的範例都使用此參考字串:7,0,1,2,0,3,0,4,2,3,0,3,0,3,2,1,2,0,1,7,0,1,3,0,3,2,1,2,0,1,7,0,1 ### 演算法 #### FIFO Algorithm (==會考==) - 先進先出(First-In-First-Out,FIFO)演算法:最早來的最先被移除 - Belady's Anomaly(貝拉迪異常):增加更多頁框反而導致更多分頁錯誤 (==會考==) ![image](https://hackmd.io/_uploads/BJfQZAczWg.png) #### Optimal Algorithm (==會考==) - 最佳化演算法(Optimal Algorithm):置換「在最長時間內不會被使用」的分頁 - 缺點:無法實作,只能作為評估其他演算法的標準 #### LRU Algorithm (==會考==) - 最近最少使用演算法(Least Recently Used Algorithm):置換「最長一段時間沒有被使用」的分頁 - 主流演算法,效果比 FIFO 好 ![image](https://hackmd.io/_uploads/ryS570qfZl.png) - 實作方式: - 計數器實作:每個分頁項目有個計數器,需置換時,搜尋表格找最小值(較慢) ![image](https://hackmd.io/_uploads/SyUVECqMWx.png) - 堆疊實作:用雙向鏈結串列保存分頁編號堆疊,分頁被參考時移到頂端(較快,不需要搜尋,但麻煩) - LRU 和 OPT 是 **Stack Algorithm**:當分配給行程 $n$ 個頁框 (frames) 時,記憶體中的頁面集合,永遠是分配 $n+1$ 個頁框時的子集,不會發生 Belady's Anomaly (==會考==) ##### LRU 近似演算法 - 附加參考位元演算法(Additional-Reference-Bits Algorithm): - 每個分頁關聯一個位元,初始為 0,被參考時設為 1 - 定期將位元右移到歷史紀錄中 - 二次機會演算法(Second-Chance Algorithm):又稱時鐘演算法(clock algorithm) - FIFO 加上硬體提供的參考位元 - 如果要置換的頁面: - 參考位元 = 0:置換它 - 參考位元 = 1:設為 0,檢查下一個分頁,給它第二次機會 ![image](https://hackmd.io/_uploads/HyvQHA9GZl.png) - Enhanced Second-Chance Algorithm:增強型二次機會演算法,同時使用參考位元(reference bit)和修改位元(modify bit)來改進 考慮有序對(reference, modify): - (0, 0):最近沒用過也沒修改 -> 最佳置換對象 - (0, 1):沒用過但修改過 -> 須寫回磁碟,次佳 - (1, 0):最近用過但乾淨 -> 可能很快又要用 - (1, 1):最近用過且修改過 -> 最不該置換 #### Counting Algorithms - 計數演算法:以下兩個算法效果普通且少用 - 最不常使用(Least Frequently Used,LFU)演算法:置換計數最小的分頁 - 最常使用(Most Frequently Used,MF)演算法:置換計數最大的分頁 #### Page-Buffering Algorithms - 頁面緩衝演算法:始終保留一個空閒頁框池(pool of free frames) - 當發生 page fault,像以前一樣選擇受害者 - 但在寫出受害者之前,先將所需分頁讀入空閒池中的頁框,藉此提升速度 ### 頁框的配置(Allocation of Frames) - 每個行程都需要最少數量的頁框,最大數量為系統中的總頁框數 - 兩種主要的配置方案: - 固定配置(fixed allocation) - 優先權配置(priority allocation) #### 固定配置(fixed allocation) - 均等配置(Equal allocation): - e.g. 如果有 100 個頁框和 5 個行程,每個行程分給 20 個頁框 - 保留一些作為空閒頁框緩衝池 - 比例配置(Proportional allocation): - 依照行程的大小來動態配置 - 公式:$a_i = (s_i / S) \times m$ - $s_i$ = 行程 $i$ 的大小 - $S$ = 所有行程總大小 - $m$ = 總頁框數 - e.g. 總共 62 頁框,P1 = 10、P2 = 127 - P1 分到 $10/137 \times 62 \approx 4$ - P2 分到 $127/137 \times 62 \approx 57$ #### 優先權配置(priority allocation) - 使用比例配置方案,但依照優先權而非行程大小 - 如果行程 $P_i$ 產生分頁錯誤: - 選擇置換它自己的一個頁框 - 或者選擇置換一個優先權較低的行程的頁框 ### 全域 vs. 區域置換(Global vs. Local Replacement) - 全域置換(Global replacement): - 行程從所有頁框的集合中選擇一個置換頁框 - 一個行程可以奪走另一個行程的頁框 - 行程執行時間可能變異很大,但吞吐量較高,所以較常見 - 區域置換(Local replacement): - 每個行程只從它自己分配到的頁框集中選擇 - 每個行程的效能較一致 - 但可能導致記憶體利用率不足 ### 非均勻記憶體存取(Non-Uniform Memory Access) - 非均勻記憶體存取(NUMA):記憶體存取速度會變動 - 記憶體配置在「靠近」執行該執行緒的 CPU 處效能最佳 ### 抖動(Thrashing) (==會考==) - **抖動(Thrashing)**:行程忙於換入換出分頁,而非執行工作 - 如果一個行程沒有「足夠」的分頁,分頁錯誤率會非常高 - Page fault 取得分頁 => 置換現有的頁框 => 很快又需要被置換掉的頁框 - 導致低 CPU 利用率 - 作業系統以為 CPU 沒事做,加入另一個行程到系統中 (==會考==)![image](https://hackmd.io/_uploads/rJM_jsoG-l.png) - 請求分頁(Demand Paging): (==會考==) - 區域性模型(Locality model):當行程執行時,它會從一個區域移動到另一個區域 - 執行中的程式通常由數個不同的區域組成,它們可能會重疊 - 區域大小總和 > 總記憶體大小 => Thrashing ### Working-Set(工作集) - $\Delta \equiv$ 工作集視窗 (working-set window) $\equiv$ 固定數目的分頁參考(例如 10,000 個指令) - $WSS_i$ (行程 $P_i$ 的工作集大小) = 在最近 $\Delta$ 時間內參考到的分頁總數 - 如果 $\Delta$ 太小,無法包含整個區域性 - 如果 $\Delta$ 太大,會包含數個區域性 - 如果 $\Delta = \infty$,將包含整個程式 - $D = \Sigma WSS_i \equiv$ 對頁框的總需求 - 如果 $D > m$(總需求 > 總頁框數),則發生 Thrashing - 暫停或換出其中一個行程 #### 追蹤工作集 (==會考==) - 範例:$\Delta = 10,000$ - 計時器每 5,000 個時間單位中斷一次 - 為每個分頁在記憶體中保留 2 個位元 - 每當計時器中斷時,複製並將所有參考位元設為 0 - 如果記憶體中的任一位元 = 1 -> 該分頁在工作集中 - 改進:使用 10 個位元並每 1,000 個時間單位中斷一次 #### 分頁錯誤頻率(Page-Fault Frequency,PFF) - 比工作集模型更直接控制 Thrashing 的方法 - 建立「可接受的」分頁錯誤頻率(PFF)率,並使用區域置換策略 - 如果實際頻率太低(沒什麼錯誤),代表分給它的格子太多了 => 行程失去頁框 - 如果實際頻率太高(一直錯誤),代表格子不夠用 -> 行程獲得頁框 ![image](https://hackmd.io/_uploads/Sk1bAjofZl.png) #### 工作集與分頁錯誤率(Working Sets and Page Fault Rates) - 行程的工作集與其分頁錯誤率之間有直接關係 - 隨時間改變出現工作集、波峰和波谷 (==會考 working set==) ![image](https://hackmd.io/_uploads/r1gFAssfbx.png) ### 記憶體映射檔案(Memory-Mapped Files) - 記憶體映射檔案 I/O 允許檔案 I/O 被視為一般的記憶體存取,透過將磁碟區塊映射到記憶體分頁 - 將記憶體映射檔案技術用於所有 I/O - 有些 OS 使用記憶體映射檔案來處理標準 I/O ![image](https://hackmd.io/_uploads/rkbky3jfWg.png) ![image](https://hackmd.io/_uploads/rJelk2iGWx.png) ### 夥伴系統(Buddy System) (==會考==) - 從由實體連續分頁組成的固定大小區段中配置記憶體 - 使用 2 的次方配置器來配置記憶體(無條件進位到下一個最高的 2 的次方) - 當需要的配置比可用的還小時,目前的區塊會分裂成兩個 夥伴 (buddies) (下一個較低的 2 的次方) - 繼續分裂直到有合適大小的區塊可用 - e.g. 假設有 256KB 的區塊可用,核心請求 21KB - 分裂成 $A_L$ 和 $A_R$,各 128KB - 其中一個進一步分裂成 $B_L$ 和 $B_R$,各 64KB - 其中一個進一步分裂成 $C_L$ 和 $C_R$,各 32KB ,其中一個用來滿足請求 - 優點:可以快速將未使用的區塊合併(coalesce)X成較大的區塊 - 缺點:碎片化 ![image](https://hackmd.io/_uploads/r1ix03izWl.png) ### Slab 配置器(Slab Allocator)(==會考==) - Slab:一或多個實體連續的分頁(==會考==) - Cache:一或多個 Slabs 組成(==會考==) - 每個唯一的核心資料結構都有一個單獨的 Cache - 每個 Cache 填滿了物件(objects):該資料結構的實例(==會考==) - 當 Cache 建立時,填滿了標記為空閒(free)的物件 - 當結構被儲存時,物件標記為已使用(used) - 如果 Slab 中的已用物件滿了,下一個物件從空閒 Slab 配 - 如果沒有空閒 Slabs,則配置新的 Slab - 優點:沒有碎片化(剛好符合物件大小)、記憶體請求滿足速度快 ![image](https://hackmd.io/_uploads/SJUIyTif-l.png) ### 其他考量 #### 預先分頁(Prepaging) - 用來減少行程啟動時發生的大量分頁錯誤 - 在參考之前,預先將行程需要的所有或部分分頁載入 - 但如果預先載入的分頁沒被使用,I/O 和記憶體就浪費了 #### 分頁大小(Page Size) - 碎片化(Fragmentation):頁面越大,最後一頁浪費的空間(內部碎片)越多 - 分頁表大小(Page table size): 頁面越小,頁數越多,分頁表越大(浪費記憶體) #### TLB 覆蓋範圍(TLB Reach)(==會考==) - TLB Reach:透過 TLB 可以存取的記憶體總量 - TLB Reach = (TLB Size) $\times$ (Page Size) - 理想情況下,每個行程的工作集(Working set)都儲存在 TLB 中 - 增加分頁大小:可能導致碎片化增加,因為並非所有應用程式都需要大分頁 - 提供多種分頁大小:允許需要大分頁的應用程式有機會使用它們,而不會增加碎片化 #### I/O 互鎖(I/O Interlock) - I/O 互鎖(I/O Interlock):分頁有時必須被鎖定(locked)在記憶體中 - 用於從裝置複製檔案的分頁必須被鎖定,防止被頁面置換演算法選中並驅逐 - 釘選(Pinning)分頁以將其鎖定在記憶體中