操作系統管理軟硬件資源,用戶進程只能直接或間接的通過系統調用訪問系統資源,而用戶進程與內核運行於不同的權限空間,需要進行用戶態到內核態的轉變,這一轉變是通過系統中斷實現。現以getpid為例:
在用戶態通過API接口編程如下:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { return printf("pid is %d\n", getpid()) > 0; }
getpid 為應用編程接口(API)函數,提供統一標准接口,實現是通過系統調用獲得進程ID。
系統調用函數由唯一的調用號標識,x86構架下在文件 arch/x86/include/asm/unistd.h 指定,實現文件根據32位系統與64為系統的不同分別實現在相同目錄的unistd_32.h和unistd_64.h文件,現通過64位系統分析,32位系統原理相同。
#define __NR_getpid 39
__SYSCALL(__NR_getpid, sys_getpid)
可以看到getpid系統調用號為 39, 當然這在不同的系統上並不一定相同,getpid API函數的封裝正是封裝了種種不同。
系統初始化時創建名為 sys_call_table 的表項,其中存儲了系統調用函數,而__NR_*調用號正是表項索引,sys_call_table的定義 32構架位於 arch/x86/kernel/syscall_table_32.S,sys_call_table 為起始地址依次存儲一系列函數地址。
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long ptregs_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
...
64位構架位於arch/x86/kernel/syscall_64.c,這裡干脆定義成了一個sys_call_ptr_t類型數組,其初始化由宏__SYSCALL完成。
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
*Smells like a like a compiler bug -- it doesn't work
*when the & below is removed.
*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/unistd_64.h>
};
系統調用時,根據 sys_call_table[__NR_getpid] 獲得 sys_getpid 地址調用之。如此,我們便可通過修改 sys_call_table 的初始化和__NR_*的定義來增加自己的系統調用和修改原來系統調用的執行方式。
倘若增加我們自己的系統調用,那接下來一問題是我們如何調用?getpid函數由glibc庫封裝,接下來需要看如何直接調用該內核函數。當然,通過源碼可以看到getpid的實現即為 sys_getpid 函數,但因為調用需要進行特權級的轉變,所以直接調用sys_getpid函數是行不通的。