核心內部與作業系統建構

系統呼叫表

當程式陷入核心請求服務時,核心抵達的是單一個進入點,卻必須把數百種不同的請求——開檔案、取得時間、建立行程——各自導向正確的程式碼。系統呼叫表(system-call table)就是它的辦法:一個簡單的編號陣列,第 N 格存放著處理第 N 號系統呼叫的核心函式位址。把它想成辦公室電話系統的按鍵選單:你不需要替每個部門拉一條獨立電話線;你撥一個號碼、按下分機號,總機就把你接到正確的座位。

具體來說,這張表是一個以系統呼叫編號為索引的函式指標陣列。使用者端與核心端事先就編號達成共識(在 x86-64 Linux 上,read 是 0、write 是 1、openat 是 257,依此類推)。當 trap 指令落入核心時,分派程式碼讀取使用者放在暫存器裡的編號,把它當成索引去查表,並呼叫存在那裡的函式——本質上只用一次陣列查找(O(1))就把一個數字變成正確的動作。這套編號被視為穩定的契約:一個編號一旦指派給某個呼叫,就永不挪作他用,因為無數已經編譯好的程式都仰賴它。

它之所以重要,是因為這張表是核心服務的總目錄,也是知名的安全攻擊目標:rootkit 歷來會劫持這張表,換上自己的指標,使得一個正常的呼叫(例如讀取目錄)暗中執行隱藏檔案的惡意程式碼——這正是為什麼現代核心把這張表設為唯讀並監看竄改。一個常見的誤解是以為系統呼叫在執行期是靠名字找到的;其實是靠編號找到的,那個人類可讀的名字只是原始碼與 libc 包裝函式裡的方便稱呼。

把這張表想成:table[0] = sys_read、table[1] = sys_write、table[2] = sys_open、……。一個想寫入的程式把 1 放進呼叫編號暫存器,然後陷入核心。分派器計算 table[1]、找到 sys_write、並呼叫它。要為核心新增一個呼叫,你只要指派下一個空編號給它,並把它的函式放進那一格即可。

來自使用者空間的一個編號,索引進一個處理常式指標陣列——一次查找即完成分派。

系統呼叫編號是依架構而定的:同一個名字(例如 write)在 x86-64、ARM64 與 32 位元 x86 上可能有不同的編號。把某架構的編號寫死在程式碼裡,在另一架構上就會呼叫到錯誤的處理常式——這正是為什麼程式使用具名的 libc 包裝函式,而不是原始編號。

又稱
syscall tablesys_call_tabledispatch table系統呼叫分派表