作業系統的基本運作 (Operating-System Operations)
本系列文章內容參考自經典教材 Operating System Concepts, 10th Edition (Silberschatz, Galvin, Gagne)。本文對應章節:Section 1.4 Operating-System Operations。
系統啟動與事件驅動
電腦要開始運作,例 如在接上電源或重新開機後,需要一段初始程式才能啟動。這個初始程式稱為開機程式 (Bootstrap Program),通常非常簡單,存放在電腦硬體的韌體 (Firmware) 中,例如 EEPROM。開機程式的任務是初始化系統的各個層面,包括 CPU 暫存器 (Registers)、裝置控制器 (Device Controllers) 和記憶體內容。初始化完成後,開機程式必須知道如何找到作業系統的 Kernel,並將它載入記憶體中。
Kernel 開始執行之後,便可以開始為系統和使用者提供服務。有些服務是在 Kernel 之外由系統常駐程式 (System Daemons) 提供的,這些程式在開機時被載入記憶體,並在 Kernel 整個運作期間持續運行。在 Linux 上,第一個系統程式是 systemd,它負責啟動其他許多 Daemon。當這個啟動階段完成後,系統才算完全開機,並靜靜地等待某件事發生。
完全啟動後,OS 幾乎完全依靠事件 (Event) 驅動:有事情發生,OS 才介入處理;沒有事情發生,它就閒置等待。這些事件幾乎都以中斷 (Interrupt) 的形式到達,主要分為兩種:
| 事件類型 | 來源 | 說明 |
|---|---|---|
| Hardware Interrupt(硬體中斷) | 裝置控制器 | I/O 完成、硬體故障等 |
| Trap / Exception(陷阱 / 例外) | 軟體 | 程式錯誤(如除以零)或使用者程式主動執行 System Call |
Trap(或稱 Exception)是一種由軟體觸發的中斷,分兩種情境:
- 意外錯誤:程式執行了不合法的操作,例如除以零或存取不屬於自己的記憶體
- 主動請求:使用者程式透過 System Call,請求 OS 代為執行只有 OS 才有權限做的事
兩者都會讓 CPU 跳轉至 OS 中對應的處理程序。
1.4.1 多程式處理與多工 (Multiprogramming and Multitasking)
問題的起點:CPU 比 I/O 快太多了
要理解多程式處理為什麼存在,必須先理解一個物理現實:
CPU 執行指令的速度(nanosecond 等級)遠遠快於 I/O 裝置完成操作的速度(millisecond 甚至 second 等級)。
以讀取磁碟一個 Block 為例,CPU 等待的時間內, 可以執行數百萬條指令。
如果一次只執行一個程式,那麼每次這個程式等待 I/O,CPU 就只能傻傻地空轉等待,明明有大量計算能力,卻完全空置。這在系統設計上是極大的浪費。事實上,單一程式通常無法隨時讓 CPU 或 I/O 裝置保持忙碌狀態。
多程式處理 (Multiprogramming) 的解法
Multiprogramming 的解法是:同時把多個 Process 放在記憶體中,CPU 在某個 Process 等待 I/O 時,立刻切換去執行另一個 Process。
概念如下:作業系統同時在記憶體中保留多個 Process(如下圖所示)。OS 挑選其中一個開始執行。最終,該 Process 可能需要等待某個任務(例如 I/O 操作)完成。在非多程式系統中,CPU 會就此空轉。在多程式系統中,OS 直接切換到另一個 Process 並繼續執行。當那個 Process 也需要等待時,CPU 再切換到下一個,依此類推。最終,第一個 Process 完成了等待並取回 CPU。只要記憶體中至少有一個 Process 需要執行,CPU 就永不閒置。
下圖呈現了多程式處理的記憶體配置方式:作業系統佔據高位址,多個 Process 並排存放於記憶體中,等待 CPU 輪流執行:
記憶體中同時存放了 process 1 到 process 4,以及位於高位址的作業系統本身。這樣的配置讓 CPU 在任何一個 Process 等待 I/O 時,都能立刻切換到其他等待中的 Process 繼續工作,而不是乾等。
這個概念在生活中其實很常見。就像一位律師不會同一時間只服務一位委託人:當某個案件正在等待開庭或等待文件簽署時,律師可以去處理另一個案件。只要委託人夠多,律師就不會有閒置的時間。多程式處理在電腦系統中扮演的正是同樣的角色,用 Process 的切換來填補 I/O 等待的空隙,最大化 CPU 使用率。
下圖呈現了 CPU 在多個 Process 之間切換的時序邏輯:
CPU 不再被動等待某個 Process 完成 I/O,而是主動切換,讓運算能力持續被利用。
多工 / 分時 (Multitasking / Time-sharing)
Multitasking 是 Multiprogramming 的進一步延伸。差別在於:
- Multiprogramming:等 Process 主動等待 I/O,才切換到下一個 Process
- Multitasking:不管 Process 有沒有在等 I/O,CPU 每隔一小段時間就強制切換
為什麼要「強制切換」?考慮一種情境:當一個 Process 在執行時,它通常只執行很短一段時間後,就會完成或需要執行 I/O。這個 I/O 可能是互動式的,也就是輸出顯示給使用者看,輸入來自使用者的鍵盤、滑鼠或觸控螢幕。互動式 I/O 以「人的速度」運行,可能需要很長時間才能完成。輸入(例如打字)受限於使用者的打字速度,每秒七個字元對人類來說已經很快,但對電腦而言卻慢得驚人。如果只在 I/O 等待時才切換,CPU 在每一次等待鍵盤輸入的過程中都會空轉。強制切換讓每個使用者都感受到「電腦在快速回應我」,即使系統同時在跑幾十個 Process。
| 比較項目 | Multiprogramming | Multitasking |
|---|---|---|
| 切換時機 | 等 I/O 時才切換 | 定期強制切換 |
| 設計目標 | 提升 CPU 使用率 | 提升使用者互動回應速度 |
多工需要的配套機制
同時在記憶體中保留多個 Process,牽動了許多配套問題。Multitasking 系統實際上是一個高度複雜的系統,需要以下幾個關鍵機制互相配合才能正確運作:
| 問題 | 解法 | 對應章節 |
|---|---|---|
| 記憶體裝不下那麼多 Process | Virtual Memory(虛擬記憶體):讓 Process 的一部分不在實體記憶體中也能執行 | Ch10 |
| 多個 Process 同時想用 CPU | CPU Scheduling(CPU 排程):決定下一個 輪到誰 | Ch5 |
| Process 可能互相破壞對方的記憶體 | Memory Management(記憶體管理) | Ch9, Ch10 |
| Process 可能互相搶同一份資料 | Process Synchronization(同步機制) | Ch6, Ch7 |
這四個問題並非彼此獨立,而是相互關聯。例如,CPU Scheduling 決定了哪個 Process 先跑;Memory Management 確保各 Process 不會互相干擾記憶體;Process Synchronization 則確保共用資料的一致性。多工系統的複雜性,正是來自於必須同時妥善處理這四個面向。
在多工系統中,OS 還必須確保合理的回應時間。一個常見的做法是使用 Virtual Memory(虛擬記憶體),這是一種允許尚未完全載入記憶體的 Process 也能執行的技術(第十章詳述)。
Virtual Memory 的主要優點是讓使用者能夠執行比實際實體記憶體還大的程式,它將主記憶體抽象成一個大型、統一的儲存陣列,將使用者看到的「邏輯記憶體」與底層的「實體記憶體」分離。這個設計讓程式設計師不必擔心記憶體的儲存限制,也讓 OS 能夠彈性調度哪些資料留在記憶體中、哪些暫時搬到磁碟上,需要時再搬回來。
1.4.2 雙模式與多模式 (Dual-Mode and Multimode Operation)
問題的起點:沒有保護的系統會怎樣?
作業系統與使用者共享電腦系統的硬體和軟體資源。一個設計合理的作業系統必須確保,錯誤的(或惡意的)程式不會導致其他程式或作業系統本身執行錯誤。
想像一個系統裡,所有程式都有相同的權限,可以執行任何 CPU 指令。這代表:
- 任何程式都可以直接讀寫其他程式的記憶體(竊取資料、破壞執行狀態)
- 任何程式都可以直接控制 I/O 裝置(格式化磁碟、攔截鍵盤輸入)
- 任何程式都可以關閉中斷,讓 OS 永遠無法取回控制權
- 一個有 Bug 的程式可能直接讓整個系統崩潰
這不是可以接受的設計。為了確保系統能夠正確執行,我們必須能夠區分「OS 程式碼在執行」和「使用者定義的程式碼在執行」。大多數電腦系統採取的做法是提供硬體支援,讓不同的執行模式 (Mode of Execution) 能夠被區分。
早期曾有人嘗試一個直覺上看似簡單的方案:把 OS 放進一塊使用者程式和 OS 本身都無法修改的唯讀記憶體分割區,以此阻止任何人破壞 OS。但這個方案有兩個根本性的困難:
- OS 無法更新自己的動態資料:OS 運作時需要不斷寫入 Process 狀態表、記憶體配置紀錄、I/O 裝置狀態表等資訊。若連 OS 自己都不能寫入,這些執行時期必須動態維護的資料就無從更新,OS 根本無法正常運作。
- OS 無法修正錯誤或動態擴充:OS 本身可能含有 Bug,也可能需要在執行中載入新的驅動程式或更新 Service Routine。若程式碼區域完全唯讀,任何 patch 或動態載入核心模組都成為不可能,系統的維護性與擴充性喪失殆盡。
這說明了保護 OS 的方案不能是粗暴地「鎖死整塊記憶體」,而必須更精細地區分「誰在執行什麼」,這正是 Dual-Mode 機制的設計動機。
解法:硬體支援的執行模式 (Mode of Execution)
CPU 硬體增加了一個 Mode Bit(模式位元),指示 CPU 目前在哪種模式下執行:
| 模式 | Mode Bit | 可執行指令 |
|---|---|---|
| Kernel Mode(核心模式) | 0 | 所有指令,包括特權指令 |
| User Mode(使用者模式) | 1 | 一般指令,不含特權指令 |
這個 Mode Bit 是 CPU 硬體的一部分,軟體無法直接修改它(想改 Mode Bit 本身就是一條特權指令)。因此,OS 和使用者程式之間的權限隔離,是硬體層面強制執行的,不是靠軟體約定。
在系統開機時,硬體從 Kernel Mode 開始。OS 被載入後,以 User Mode 啟動使用者應用程式。每當發生 Trap 或 Interrupt 時,硬體便從 User Mode 切換到 Kernel Mode(即將 Mode Bit 設為 0)。因此,每當 OS 取得電腦的控制權時,它都處於 Kernel Mode。系統在把控制權交給使用者程式之前,總是會先切換回 User Mode(將 Mode Bit 設為 1)。