注意:本文參考x86_64結構下的2.6.34源代碼。
聲明與註冊
系統調用作爲內核與用戶進程的接口,充當了中間人的角色,使內核儘可能安全的滿足用戶進程的請求。linux內核提供了300個系統調用(參考源碼目錄中./arch/x86/include/asm/unistd_64.h文件),每個系統調用均擁有一個唯一且不變的系統調用號,舉個getpid系統調用的例子:
#define __NR_getpid 39
__SYSCALL(__NR_getpid, sys_getpid)
根據arch/x86/kernel/syscall_64.c文件中關於__SYSCALL的宏定義
#define __SYSCALL(nr, sym) extern asmlinkage void sym(void);
預處理後的代碼如下所示:
extern asmlinkage void sys_getpid(void);
會有細心的讀者發現__SYSCALL中的nr參數並沒有使用,後面會詳細講到這個問題。
但此時僅聲明瞭函數。接下來要進行函數的註冊,將函數地址保存在調用列表(sys_call_table)中。
這裏需要特別說明一下,linux內核使用了對同一個頭文件,進行兩次預處理的方式完成,這種方式比較少見。以下代碼爲arch/x86/kernel/syscall_64.c文件的部分代碼(刪除了無關的代碼),分別進行了兩次__SYSCALL的宏定義,且分別載入兩次頭文件asm/unistd_64.h,這麼做的目的是進行函數的聲明後複用代碼進行註冊。在編譯的預處理階段,頭文件會在聲明處展開,第一個unistd_64.h展開後,__SYSCALL宏定義全部被替換成函數聲明,第二個unistd_64.h被展開後,__SYSCALL宏定義被替換成地址註冊。
#define __SYSCALL(nr, sym) extern asmlinkage void sym(void) ;
#undef _ASM_X86_UNISTD_64_H
#include <asm/unistd_64.h>
#undef __SYSCALL
#define __SYSCALL(nr, sym) [nr] = sym,
#undef _ASM_X86_UNISTD_64_H
typedef void (*sys_call_ptr_t)(void);
extern void sys_ni_syscall(void);
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/unistd_64.h>
};
仍然以getpid函數調用舉例,通過第二次對頭文件的展開,此時nr參數被替換成39,函數註冊完成。
[39] = sys_getpid,
至此,函數的聲明與註冊代碼如下形式(省略了其他函數):
extern asmlinkage void sys_getpid(void);
typedef void (*sys_call_ptr_t)(void);
extern void sys_ni_syscall(void);
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &sys_ni_syscall,
[39] = sys_getpid,
};
系統調用的實現
系統調用getpid實現了獲取當前進程的pid,實現代碼位於kernel/timer.c文件中,如下所示:
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
其中的宏定義SYSCALL_DEFINE0定義於include/linux/syscalls.h,如下所示:
#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void)
預編譯後,代碼變爲如下形式,這是一個無參數的系統調用。另外需要注意,內核實現中,所有的系統調用都應遵守sys_funcname的形式。
asmlinkage long sys_getpid(void)
{
return task_tgid_vnr(current);
}