這次講到的部分, 目前在Linux Foundation OpenBMC當中比較少會用到或需要去更改,但如果有需要寫Bridge IC相關的,OpenBIC的code,就會比較有感覺。其實早期Meta OpenBMC也很常需要寫File IO相關的code在Application,像是Firmware update/ Sensor Monitoring ...etc都是要讀取檔案, 或是透過Open Device Path去處理。那我們就再一起來複習一下使用者空間 (Userspace) 與 核心空間 (Kernel Space) 邊界設計哲學。
File Descriptor
想像你的程式 (Process) 走進一家餐廳 (Linux Kernel)。核心會為每個行程維護一份專屬的檔案清單 (File Table) 。當你的程式開啟一個檔案,核心就會從這個清單中取出一個未使用的整數作為該檔案的 FD,並回傳給你。FD 就是你用來索引和參考這個檔案的號碼牌 。預設的三大標準 FD:- FD 0:標準輸入 (stdin)
- 這是歷史慣例所訂定的檔案描述子
0。
- 這是歷史慣例所訂定的檔案描述子
- FD 1:標準輸出 (stdout)
- 檔案描述子
1,通常對應到你的終端機顯示器 。 - 你在 C 程式中使用
printf()的輸出,預設就是送往 FD 1 。
- 檔案描述子
- FD 2:標準錯誤 (stderr)
- 檔案描述子
2,它也通常導向到你的終端機顯示器 。 - 它的作用是允許你將程式中的錯誤訊息與正常的程式輸出分流處理,方便你單獨捕捉錯誤 。
- 檔案描述子
System Call vs Buffered I/O
在 C 語言中,我們常看到兩組看似相似、但其實層級不同的檔案操作函式:
1. Buffered I/O(緩衝式輸入輸出)
使用帶有 f 前綴 的函式,例如:
fopen(), fread(), fwrite(), fclose()
這些函式屬於 C 標準函式庫(C Standard Library),主要運作在 使用者空間(Userspace)。它們會回傳一個 File Pointer (FILE *),並且定義於 man page 的 Section 3。可以把它想成是系統呼叫(system call)的「包裝(wrapper)」:在底層仍然會呼叫 open()、read()、write(),但額外在使用者空間建立了一層 buffer(緩衝區) 來加速存取。
2. System Call(系統呼叫)
使用 不帶 f 前綴 的函式,例如:
open(), read(), write(), close()
這些函式是直接與 Linux Kernel 互動的介面。它們會回傳一個整數型別的 File Descriptor (FD),用來唯一識別該進程所開啟的檔案。這些函式定義於 man page 的 Section 2。
緩衝區機制(Buffering Mechanism)
當你使用fread()讀取資料時,即使只要求讀取一個位元組,它可能會一次透過系統呼叫從核心讀入一整塊資料(例如 4KB),並暫存在使用者空間的緩衝區中。 之後若程式再要求更多資料,就能直接從緩衝區讀取,而不需要再進行額外的 system call。這樣做的目的,是為了 減少使用者空間與核心空間之間的切換(context switch)次數,提升 I/O 效能。
開啟與建立檔案 (open)
在 Linux 系統程式設計中,open() 是一切檔案操作的開端。
它的作用是:將一個檔案路徑(pathname)與一個 File Descriptor(檔案描述子, FD)建立連結。
int fd = open("file.txt", O_RDWR | O_CREAT, 0644);
這個函式呼叫可以拆解成兩個關鍵部分:
- Flags(開啟模式)
flags參數用來決定你要以何種方式開啟檔案。可以使用 bitwise OR (|) 運算符組合多個模式: - O_RDONLY:Read only(唯讀)
- O_WRONLY:Write only(唯寫)
- O_RDWR:Read and write(可讀可寫)
- O_CREAT:若檔案不存在則建立新檔
- O_APPEND:每次寫入都從檔尾追加
- O_SYNC:啟用同步 I/O:寫入動作必須等到資料實際寫入磁碟後才算成功
為什麼O_SYNC重要?
一般情況下,write()完成時資料可能還停留在核心緩衝區,若系統異常關機,資料可能尚未寫入磁碟。 使用O_SYNC可強制所有寫入同步到實體儲存裝置,確保資料一致性(但會犧牲部分效能)。
- Mode(權限設定)
當使用O_CREAT建立新檔案時,必須同時指定mode參數來設定檔案的權限。這個參數以八進位(octal)形式表示檔案的存取權限,對應 Linux 的三組使用者分類: 使用者 (User)、群組 (Group)、以及其他人 (Other)。
每組權限由三個位元組成,分別代表「讀 (r)」、「寫 (w)」、「執行 (x)」。數值上,讀是 4,寫是 2,執行是 1,三者加總後形成該組的權限。
例如0644代表: - 使用者(owner)擁有讀寫權限(6 = 4 + 2),
- 群組與其他人則僅有讀取權限(4)。
strace:看見使用者與核心的對話
在學習 Linux File I/O 時,最能讓人真正體會「使用者空間(User Space)」與「核心空間(Kernel Space)」之間互動細節的工具,就是 strace。可以把 strace 想像成一個專門為 Linux 系統呼叫(System Calls)設計的 Protocol Analyzer。
它能即時顯示出你的程式在執行期間,究竟對核心發出了哪些系統呼叫、傳入了哪些參數、又收到了什麼回傳值。 這意味著你不需要修改任何程式碼,就能直接觀察整個使用者空間與核心空間之間的「對話紀錄」。舉個例子,假設你執行一個讀取空檔案的簡單程式,strace 可能會輸出以下內容:
open("empty_file", O_RDWR|O_CREAT, 0666) = 3
read(3, "", 1024) = 0
從這兩行輸出,我們可以完整看到系統互動的過程:
首先,程式呼叫 open() 來開啟檔案,核心成功建立連線並回傳一個 File Descriptor(這裡是 3)。
接著,程式對這個 FD 3 執行 read() 操作,核心則回傳 0,代表這個檔案沒有內容可讀,也就是已經到達檔案結尾(EOF)。
這種觀察方式非常直觀,因為它讓你能夠從「系統層級」理解程式在做什麼。
更厲害的是,strace 並不需要程式的原始碼——即使只有一個已編譯完成的二進位執行檔(binary executable),也能被 strace 監看。大家可以試試看,假設你有一個簡單的 C 程式,只做一件事:開啟一個檔案並嘗試讀取內容。
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("empty.txt", O_RDWR | O_CREAT, 0666);
char buf[100];
read(fd, buf, sizeof(buf));
close(fd);
return 0;
}
你將這段程式編譯並執行:
gcc readfile.c -o readfile
./readfile
表面上,程式什麼都沒輸出;但如果我們在前面加上 strace,你就能看到它與核心之間的一場「對話」:
strace ./readfile
你可以看一下你的終端機會出現怎樣的輸出?
這篇就先到這裡,大家趕快動手試試看呦!


















