Home
探索 Uedu
學生控制台
註冊會員/登入
研究知情同意中心
教師控制台
課程設定
支援與訊息
Uptime 數據

UeduGPTs

--

Jupyters

4

UG26 CISOSE26
臺北 AQI 46 · 臺中 AQI 26 · 臺南 AQI 21 · 高雄 AQI 33

AI 回覆桌面通知

AI 助教回覆完成時顯示桌面通知

聊天訊息通知

同學在討論區發送訊息時通知

聲音通知

每當有新通知時播放提示音

作業系統概念

作業系統概念

從資源管理者與抽象提供者兩個身分出發,看懂核心模式、系統呼叫與四大子系統,以及模式切換成本如何驅動系統設計。

同時開著十幾個程式,誰在背後當交通警察?

打開電腦,你可能同時開著瀏覽器、音樂播放器、文書軟體,背景還有防毒程式與雲端同步在跑。這些程式各自以為自己獨佔了整台機器:它們都「看到」一大塊連續的記憶體,都能把資料寫進「檔案」,都能在螢幕上畫圖。但事實上,你的電腦可能只有一顆(或數顆)處理器、有限的記憶體、一顆共用的硬碟。十幾個程式怎麼可能同時和平共處,不互相踩到對方的資料?

答案是:有一個看不見的「交通警察」在背後協調一切,它就是作業系統(Operating System, OS)。Windows、macOS、Linux、Android、iOS 都是作業系統。它沒有華麗的介面(介面通常是另一層應用程式),但少了它,每一個應用程式都得自己去操控硬碟的磁頭、管理記憶體的每一個位元組、輪流使用 CPU——那將是一場災難。這篇文章要帶你看懂:作業系統到底在做什麼,以及它如何在「保護」與「效率」之間取得平衡。

作業系統的兩個身分:資源管理者與抽象提供者

要理解作業系統,先記住它扮演的兩個核心角色。

第一個角色是資源管理者(resource manager)。 電腦的硬體資源是有限且必須共享的:CPU 的運算時間、記憶體空間、硬碟的讀寫頻寬、網路卡。當多個程式都想用 CPU 時,是誰決定先讓哪個程式跑、跑多久?是作業系統。它像一位調度員,把有限的資源公平(或依優先序)地分配給彼此競爭的程式,並確保一個失控的程式不會把整台機器拖垮。

第二個角色是抽象提供者(abstraction provider)。 真實硬體的操作極其繁瑣:要從硬碟讀一個檔案,你得知道資料在第幾個磁柱、第幾個磁區,還要處理磁碟控制器的訊號。作業系統把這些骯髒的細節包起來,對上層程式提供乾淨、統一的抽象概念。於是程式設計師只要呼叫「打開檔案 data.txt」,完全不必管底下是 SSD 還是傳統硬碟、是哪一家廠牌。常見的抽象包括:

  • 行程(process):把「正在執行的程式」抽象成一個獨立個體,讓每個程式以為自己獨佔 CPU。
  • 虛擬記憶體(virtual memory):讓每個程式以為自己擁有一整片連續的記憶體位址空間。
  • 檔案(file):把雜亂的磁碟區塊抽象成有名字、可讀可寫的位元組序列。

作業系統概念示意圖

一條清楚的界線:核心模式與使用者模式

既然作業系統要保護硬體不被亂搞,它就必須擁有比一般程式更高的權限。為此,現代 CPU 在硬體層級就提供了至少兩種執行模式(execution mode)

  • 核心模式(kernel mode),又稱特權模式(privileged mode)或監督模式(supervisor mode)。在這個模式下,程式可以執行任何指令、直接存取所有硬體、改動記憶體管理設定。作業系統的核心(kernel)就跑在這個模式。
  • 使用者模式(user mode)。一般的應用程式都跑在這裡。在使用者模式下,某些「特權指令(privileged instructions)」是被禁止的——例如直接操作 I/O 裝置、修改頁表、關閉中斷。如果一個使用者程式試圖執行這些指令,CPU 會立刻擲出一個例外(exception),把控制權交還給作業系統處理(通常就是把這個程式終止)。

這條界線是整個系統安全的基石。想像一下:如果任何應用程式都能直接改寫磁碟控制器或別人的記憶體,那麼一個有 bug(或惡意)的程式就能摧毀整台機器、竊取別人的資料。核心模式與使用者模式的硬體隔離,確保了即使某個應用程式崩潰或被入侵,作業系統與其他程式仍能受到保護。 這也是為什麼說作業系統的安全建立在硬體支援之上,而非僅靠軟體善意。

跨越界線的橋樑:系統呼叫

問題來了:使用者程式跑在沒有特權的使用者模式,但它常常需要做一些只有核心才能做的事——讀檔案、配置記憶體、建立新的行程、傳送網路封包。它要怎麼「請核心幫忙」?

答案是系統呼叫(system call)。系統呼叫是使用者程式向作業系統核心請求服務的唯一合法管道。它的運作方式是:使用者程式執行一條特殊的「陷阱指令(trap instruction)」(在 x86-64 上是 syscall,舊架構上可能是軟體中斷),CPU 收到這條指令後,會做三件事:

  1. 切換到核心模式(提升權限)。
  2. 跳到核心預先設定好的固定進入點——程式不能跳到核心的任意位置,只能跳到核心允許的入口,這保證了核心對自己的行為有完全掌控。
  3. 核心依照程式傳入的「系統呼叫編號」與參數,執行對應的服務,完成後切換回使用者模式,把結果交還給程式。

這套設計巧妙之處在於:使用者程式無法直接「進入」核心做任何事,它只能透過一份預先定義好的服務清單(系統呼叫介面)來請求。這就像你不能直接闖進銀行金庫,只能在櫃台填好制式表單請行員辦理——櫃台(系統呼叫介面)正是受控的邊界。

下表是幾類常見的系統呼叫:

類別 範例(POSIX) 作用
行程控制 fork, exec, exit, wait 建立、執行、結束行程
檔案管理 open, read, write, close 開啟讀寫檔案
裝置管理 ioctl, read, write 操作 I/O 裝置
資訊維護 getpid, time 取得系統資訊
通訊 pipe, socket, send, recv 行程間或網路通訊

值得注意的是,你平常寫程式時很少「直接」呼叫系統呼叫。當你在 C 語言寫 printf,或在 Python 寫 print,背後其實是函式庫(library)層層包裝,最終才透過 write 這個系統呼叫把字串送進核心。函式庫提供方便的介面,系統呼叫才是真正越過那道權限界線的那一步。

動手看一個例子

讓我們用一個最小的 C 程式,追蹤一次「印出文字」實際發生了什麼。

#include <unistd.h>

int main() {
    // write 是直接對應系統呼叫的函式
    // 參數:檔案描述子 1 代表標準輸出, 字串, 長度
    write(1, "Hello, OS!\n", 11);
    return 0;
}

在 Linux 上,你可以用 strace 工具觀察一個程式發出了哪些系統呼叫:

$ strace ./hello
execve("./hello", ["./hello"], ...)     = 0
...(一堆載入函式庫的系統呼叫)...
write(1, "Hello, OS!\n", 11)            = 11
exit_group(0)                          = ?

每一行都是一次跨越使用者/核心界線的系統呼叫。write(1, ...) 那一行就是我們程式碼裡那次呼叫:它請核心把 11 個位元組寫到檔案描述子 1(標準輸出,通常是終端機)。回傳值 11 表示成功寫入了 11 個位元組。

注意:連程式啟動時載入函式庫、結束時的 exit_group,全都是系統呼叫。一個看似單純的程式,從生到死的每一個與外界互動的動作,都得經過作業系統。

作業系統的四大子系統

作業系統內部龐大,但其職責大致可分為四個核心子系統,恰好對應它要管理的四類資源。

1. 行程管理(process management)。 負責建立與終止行程、決定哪個行程現在能用 CPU(這個決策叫排程(scheduling))、處理行程間的同步與通訊。由於 CPU 數量遠少於行程數量,作業系統用極快的速度在行程之間切換(情境切換, context switch),製造出「同時執行」的錯覺,這叫做並行(concurrency)

2. 記憶體管理(memory management)。 追蹤哪些記憶體正在使用、由誰使用,並透過虛擬記憶體機制讓每個行程擁有獨立的位址空間。當實體記憶體不夠時,它會把暫時用不到的資料搬到硬碟(分頁, paging),騰出空間給需要的行程。

3. 檔案系統(file system)。 把硬碟上的位元組組織成檔案與目錄的階層結構,管理檔名、權限、存取控制,並決定資料實際存放在磁碟的哪些區塊。它讓「儲存」這件事變得可命名、可組織、可長期保存。

4. 輸入/輸出(I/O)管理。 透過裝置驅動程式(device driver)統一各式各樣硬體(鍵盤、滑鼠、網卡、印表機)的存取方式,並用緩衝(buffering)、快取(caching)等技巧提升效率。驅動程式是核心中與特定硬體溝通的模組,這也是為什麼換新硬體常需要「安裝驅動」。

這四者並非各自獨立,而是緊密協作。例如你執行一次檔案下載:I/O 子系統從網卡收資料、記憶體子系統配置緩衝區、檔案系統把資料寫入磁碟、行程管理決定何時讓你的下載程式繼續跑。一次日常操作,背後是四大子系統的合奏。

重點回顧

  • 作業系統有兩個核心身分:資源管理者(公平分配 CPU、記憶體、I/O 等有限資源)與抽象提供者(用行程、虛擬記憶體、檔案等抽象隱藏硬體細節)。
  • CPU 在硬體層級提供核心模式使用者模式兩種權限等級;核心模式能做任何事,使用者模式被禁止執行特權指令。這條界線是系統安全與隔離的基石。
  • 系統呼叫是使用者程式請求核心服務的唯一合法管道,透過陷阱指令切換到核心模式、跳到固定進入點,完成後再切回使用者模式。
  • 你呼叫的 printfprint 等函式,最終都是透過函式庫包裝、再以系統呼叫越過權限界線。
  • 作業系統四大子系統:行程管理、記憶體管理、檔案系統、I/O 管理,分別對應四類資源,且彼此緊密協作。

深入探討(研究所視角)

前面說系統呼叫是「請核心幫忙」,聽起來輕鬆,但它其實是有成本的,而且這個成本在系統設計上舉足輕重。

核心/使用者模式切換的真實開銷。 一次系統呼叫並不只是「呼叫一個函式」那麼便宜。CPU 要從使用者模式切到核心模式,過程包括:擲出陷阱、儲存使用者程式的暫存器狀態、切換堆疊指標到核心堆疊、做參數驗證與權限檢查,完成後再復原狀態切回去。在現代 x86-64 上,一次最簡單的系統呼叫(例如 getpid)約耗費數百個 CPU 週期(量級在 $100$–$1000$ cycles),對照之下,一次普通的函式呼叫只要幾個週期。換言之,系統呼叫比一般函式呼叫貴了兩到三個數量級

更糟的是間接成本。模式切換往往會污染 CPU 的快取(cache)與分支預測器、可能造成 TLB(Translation Lookaside Buffer)失效。2018 年揭露的 Meltdown 與 Spectre 漏洞,迫使作業系統採用 KPTI(Kernel Page-Table Isolation)等緩解措施,把核心與使用者的頁表分開,使得每次系統呼叫都要額外切換頁表、刷新 TLB,系統呼叫成本因此再上升一截。這說明安全與效能之間存在深刻的張力。

這個成本如何驅動系統設計? 正因為跨界昂貴,許多重要的系統設計技巧都圍繞著「減少系統呼叫次數」而生:

  • 批次化(batching)。 與其呼叫 100 次 write 各寫 1 個位元組,不如在使用者空間先用緩衝區攢滿,再一次 write 寫 100 個位元組——這正是 C 標準函式庫 stdio 緩衝機制的動機。
  • io_uring 等新介面。 Linux 近年引入的 io_uring 用共享記憶體的環狀佇列(ring buffer)讓使用者與核心交換大量 I/O 請求,大幅減少陷阱次數,是高效能伺服器的利器。
  • mmap 共享記憶體。 透過記憶體映射,行程可直接存取某段資料而不必每次都走 read/write 系統呼叫。

核心架構的根本權衡。 系統呼叫的成本也牽動了作業系統最經典的架構辯論:單核心(monolithic kernel)微核心(microkernel)

  • 單核心(如 Linux)把行程管理、記憶體、檔案系統、驅動全塞進核心空間。子系統之間互相呼叫只是普通函式呼叫,極快;缺點是核心龐大,任一驅動的 bug 都可能拖垮整個核心。
  • 微核心(如 MINIX、seL4)只把最基本的功能(行程排程、記憶體、行程間通訊)留在核心,把檔案系統、驅動等搬到使用者空間當作一般行程。優點是模組化、容錯(一個驅動掛了不影響核心)、易於形式化驗證;缺點是子系統之間溝通必須透過訊息傳遞(message passing),而每一次訊息傳遞又涉及模式切換的成本——這正是早期微核心效能不彰、被 Linus Torvalds 在著名的 1992 年論戰中批評的核心原因。

可以說,「系統呼叫/模式切換的成本」這個微觀現象,一路向上決定了從函式庫緩衝策略、到 I/O 介面設計、再到整個核心架構哲學的宏觀抉擇。理解這個成本,是理解現代作業系統一切權衡的鑰匙。

最後,這些概念與其他主題環環相扣:模式切換的硬體基礎連結到計算機組織中的中斷與例外機制;行程排程牽涉演算法與佇列理論;虛擬記憶體與分頁是記憶體階層的延伸;而系統呼叫介面的安全邊界,則是資訊安全中「最小權限原則(principle of least privilege)」的硬體實踐。作業系統正是計算機科學各支柱交會的樞紐。

AI 共讀助教正在陪你讀:作業系統概念
嗨!我是這篇文章的共讀助教,只根據〈作業系統概念〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。