《Unix高階程式設計學習筆記》 1
系統呼叫簡介
- 為使用者空間提供了一種硬體的抽象介面
- 保證了系統的穩定和安全
- Linux中程式都執行在虛擬系統中
POSIX、API、C庫
系統呼叫
- 系統呼叫通常但不絕對,以
long
型別的返回值(為與64位的硬體體系結構保持相容) - 表示對錯:負數表示錯誤,0值表示成功,出錯將錯誤碼寫入
errno
全域性變數,通過perror()庫函式翻譯成使用者理解的錯誤字串。 - 巨集定義:
SYSCALL_DEFINE0(###)
- 含義: 定義一個在核心中無引數的系統呼叫 用例:
//定義核心中的系統呼叫getpid()的實現
//展開前
SYSCALL_DEFINE0(getpid)
{
return task_tgidvnr(current); //return current->tgid
}
//展開後
asmlinkage long sys_getpid(void){
return task_tgidvnr(current); //return current->tgid
}
複製程式碼
用例:
//定義核心中的系統呼叫bar()的實現
//展開前
SYSCALL_DEFINE0(bar)
//展開後
asmlinkage long sys_bar()
複製程式碼
注:使用者空間返回int
,核心空間返回long
系統呼叫號
用於關聯絡統呼叫。使用者程式空間的程式執行一個系統呼叫時,使用系統呼叫號指明到底執行哪個系統呼叫,且不會提及系統呼叫的名稱。
特點:
- 一旦分配便不會再有任何改變;
- 系統呼叫號一旦刪除系統呼叫號也不會被回收;
sys_ni_syscall()
返回-ENOSYS
,專門填補無效系統呼叫;- sys_call_table 記錄所有已註冊過的系統呼叫,在
x86-64
中,定義於arch/i386/kernel/syscall_64.c
中,併為每個系統呼叫指定唯一的系統呼叫號
系統呼叫號的效能
很高:上下文切換時間短,進出核心被優化得簡潔高效,系統呼叫處理程式和系統呼叫簡潔
系統呼叫處理程式
告訴核心需要切換到核心態,讓核心代表應用程式在核心空間執行系統呼叫 實現機制: 系統呼叫處理程式:引發一個異常促使系統謝歡到核心態去執行異常處理程式,叫syscall_call()
指定恰當的系統呼叫
- x86上將系統呼叫號通過eax傳遞給核心
- 再執行
system_call()
- 系統呼叫號與
NR_syscalls
相比較,大於等於NR_syscalls則返回-ENOSYS,否則執行相應的系統呼叫
call *sys_call_table(,%eax, 8)
//8代表系統呼叫表中表項以64位型別存放,則結果等於 系統呼叫號×4 用於查詢系統呼叫位置。
//x86-32則用4代替8
複製程式碼
引數傳遞
x86-32
系統中前五個引數放置在ebx, ecx, edx, esi和edi
中- 六個及六個以上則用單獨的暫存器存放指向所有這些引數在使用者空間的指標
系統呼叫的實現
實現系統呼叫
- 明確此係統函式應該做什麼
- 不提倡採用多用途的系統呼叫,考慮引數、返回值、錯誤碼
- 介面應儘量為未來多做考慮,並考慮可移植性
引數驗證
- 檢查他們的引數是否合法有效
- 檢查使用者提供的指標是否有效
系統呼叫上下文
繫結一個系統呼叫的最後步驟
編寫完一個系統呼叫後,將其註冊誠正式的系統呼叫
- 在系統呼叫表的最後加入一個表項其中系統呼叫號即為該系統呼叫在所有系統呼叫中的次序
- 對所有支援的各種體系結構,系統呼叫號必須定義於
<asm/unistd.h>
中 - 系統呼叫必須被編譯進系統核心,即放進kernel/下的一個相關檔案
從使用者空間訪問系統呼叫
通常,系統呼叫靠C庫支援,如果僅僅寫出系統呼叫,glibc庫並不支援,怎麼辦呢
答:巨集_syscalln()
,其中n範圍為0~6代表傳遞給系統呼叫的引數個數,功能為設定好暫存器並呼叫陷入指令
例子:
//open()系統呼叫定義
long open(const char* filename, int flags, int mode)
複製程式碼
不靠庫支援,直接呼叫此係統呼叫的巨集形式
#define NR_open 5
_syscall3(long, open, const char*, filename, int, flags, int, mode)
複製程式碼
在Android系統中系統呼叫的應用
在Android系統中,_syscall()
被syscall()
替代,且每一種Android支援的系統呼叫的引數形式都在Android原始碼中被SYSCALLS.txt記錄,我們可以根據系統呼叫的知識,並結合SYSCALLS.txt,可以很輕易地寫出自己對應的的syscall()。