上一篇提到,一般程式通常在 User Mode 下執行,不能隨意存取硬體或執行具有高權限的操作。
但問題也跟著來了:
既然 User Mode 權限受到限制,程式到底怎麼使用 Kernel 提供的功能?
今天就讓我們來看System Call(系統呼叫)
假設今天想從一個已經開啟的檔案讀取資料,在系統中可能會看到:
char buffer[100];
ssize_t n = read(fd, buffer, 100);
從 fd 所代表的檔案讀取最多 100 bytes,放進 buffer。
但 read() 本身不能直接繞過作業系統去控制磁碟。
所以真正需要讀取資料時,必須向 Kernel 請求服務。
User Program
│
▼
read()
│
▼
System Call
│
──── User Mode / Kernel Mode ────
│
▼
Kernel
│
▼
File System / Device
所以 System Call 可以理解成是 User Program 向 Kernel 請求服務的受控制介面。
兩者其實有很大的差別。
一般的 Function Call,例如:
int add(int a, int b) {
return a + b;
}
呼叫 add() 時,程式仍然在 User Mode 中執行。
但 System Call 不一樣。
當程式需要 Kernel 提供服務時,CPU 必須透過處理器提供的特定機制,從 User Mode 進入 Kernel Mode,並開始執行 Kernel 中對應的處理程式。
所以:
Function Call
User Mode ──── add() ────> User Mode
System Call
User Mode ──── system call ────> Kernel Mode
當 CPU 透過 System Call 的入口進入 Kernel Mode 後,Kernel 就可以開始處理這次請求。
以:
read(fd, buffer, 100);
為例。這裡其實傳入了幾個資訊:
fd:要讀取哪一個已開啟的檔案或其他 I/O 物件?
buffer:資料讀出來之後,要放到 User Space 的哪個位置?
100:最多希望讀取多少 bytes?
Kernel 收到要求之後,並不是直接跑去磁碟「拿 100 bytes 回來」。
它首先需要檢查這次請求。
例如 fd 是否有效、目前是否允許從這個檔案描述符讀取,以及程式提供的 buffer 是否能安全地接收資料。
接著,Kernel 才會根據 fd 找到它所代表的已開啟物件,並繼續處理讀取要求。
read(fd, buffer, size)
│
▼ 進入 Kernel
│
▼
檢查參數
│
▼
找到 fd 對應的物件
│
▼
取得需要的資料
│
▼
將資料複製到 User Space 的 buffer
│
▼
回傳讀取結果
│
▼
回到 User Mode
那「取得需要的資料」又是什麼意思?
資料不一定每次都需要真的從磁碟重新讀取,如果需要的檔案資料已經存在 Kernel 管理的 cache 中,就可能直接從 cache 取得(Cache Hit);如果資料還不在記憶體中,才可能需要進一步進行 I/O (Cache Miss)。
而 I/O 的速度相對 CPU 慢很多,所以如果這次讀取必須等待,正在執行的 Thread 可能暫時無法繼續執行。OS 就有機會先讓 CPU 去處理其他工作。
等資料準備完成後,Kernel 再把資料提供給 User Program。
所以看似只有一行的:read(fd, buffer, 100);
背後其實包含了權限切換、參數檢查、Kernel 內部的檔案處理、可能的 I/O,以及資料回傳等一連串工作。
這也是 System Call 和普通 Function Call 很不一樣的地方。
User Mode 到底怎麼進入 Kernel?
一般程式不能直接跳進 Kernel 任意執行程式碼,而是必須透過 CPU 與 OS 所定義的 System Call 機制,從受到控制的入口切換成 Kernel。
但除了 System Call 之外,CPU 執行程式時還可能突然被其他事件打斷。
例如鍵盤輸入、硬體完成 I/O
這些情況又是怎麼讓 CPU 停下原本的工作,把控制權交給 Kernel?
下一篇就來拆這三個很容易搞混的東西: