【linux】系統調用解析

注意:本文參考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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章