檔案描述符表(file-descriptor table)
再想像一次衣帽間的櫃台,但這次看看櫃台後面:一排編了號的掛鉤。0 號鉤、1 號鉤、2 號鉤,依此類推。你的號碼牌告訴店員該走到哪個鉤子,每個鉤子上掛著一張標籤,描述那裡實際掛著什麼。檔案描述符表就是那排掛鉤——每個執行中的程式都有一排自己私有的鉤子。
具體來說,每個行程都有自己的一個檔案描述符格子陣列,以我們稱為描述符的小整數作為索引。第 3 格存著 fd 3 目前所指的東西。每個格子並不直接存著檔案;它指向一個核心結構(常稱為開啟檔案描述,open file description),這個結構追蹤某個檔案的一次開啟實例——包含它目前的檔案偏移量,以及開啟時所用的旗標。那個結構又再指向檔案的索引節點,也就是核心對實際檔案的記錄。所以是有層次的:你的 fd 索引這張表,表中的項目指向一個開啟檔案結構,那個結構再指向檔案。當 open() 成功時,核心挑出編號最小的空格並回傳它的號碼;正是這條「最小空格」規則,讓重導向的把戲得以運作。
為何重要:這張表正是讓描述符成為「每個行程私有」且廉價的原因。當程式 fork 出子行程時,子行程得到這張表的一份複本,因而繼承了父行程開啟的檔案——這就是 shell 串接管線的方式。dup() 與 dup2() 不過是讓第二個格子指向與某個既有格子相同的開啟檔案結構。理解這張表——先是 fd、再是開啟檔案描述、再是索引節點——就能說明為何兩個描述符可以共用同一個檔案偏移量,而對同一路徑呼叫兩次 open() 卻不會。
剛啟動後,表中是 0 格 -> 標準輸入、1 格 -> 標準輸出、2 格 -> 標準錯誤。下一次 open() 會取得 3 格(最小的空格)。但若你先 close(0),那同一個 open() 就會改取 0 格。
「最小空格」規則正是重導向所拉動的那根槓桿。
由 dup() 產生的兩個描述符共用一個開啟檔案描述,因此共用同一個檔案偏移量;對同一路徑各自呼叫兩次 open() 則得到各自獨立的偏移量。把這兩種情況搞混,是讀寫位置出人意料的經典原因。