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