LINUX内核设计思想之系统调用

来源:互联网 发布:js 数组 remove 编辑:程序博客网 时间:2024/05/21 17:08

通过系统调用,是用户空间访问内核的唯一手段,除异常和陷入之外,它们是内核唯一的合法入口.

 

5.1 系统调用的示意代码

例如获取当前进程的PID,用户空间只需要调用如下代码:

User Space:

getpid()

实质上,内核是这样响应此系统调用的.

Kernel Space:

Asmlinkage long sys_getpid(void)

{

Return current->tgid;

}

:

首先,函数声明中的asmlinkage的意义是用于通知编译器仅从栈中提取该函数的参数.所有的系统调用都需要这个限定词;

其次,系统调用getpid()在内核被定义为sys_getpid().LINUX中所有系统调用都应该遵守这种前缀(sys_)命名.

 

5.1.1 系统调用号

LINUX,每个系统调用被赋予一个唯一的系统调用号,用于关联相应的系统调用.这个系统调用号是被用来指明到底是执行哪个系统调用,否则,内核早就混乱了.

内核记录了系统调用表中的所有已经注册过的系统调用列表.存储在sys_call_table.它与系统结构有关,一般在entry.s中定义.

 

5.2 系统调用的实现原理

用户空间的程序无法直接执行内核的代码,它们是没办法去直接调用内核空间中的函数的.所以,应用程序应该必某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,让内核代表用户执行.通知内核的机制是靠软中断实现的.

 

5.2.1 指定恰当的系统调用

因为所有的系统调用陷入内核的方式都一样,很明显仅仅陷入内核空间是不够的,还需要指定相应的系统调用.前面已经说过,每个系统调用都有一个唯一的系统调用号.因此,把系统调用号也传递下去索引便可找到相应的系统调用.根据系统调用号索引到相应的系统调用函数这个工作由system_call()函数来完成.

函数system_call()首先会将给定的系统调用号与NR_syscalls作比较来检查其有效性;其次,执行相应的系统调用,示意代码如下:

Call *sys_call_table()

 

 

X86system_call源码(linux2.6.10)如下:

ENTRY(system_call)

pushl %eax # save orig_eax

SAVE_ALL

GET_THREAD_INFO(%ebp)

# system call tracing in operation

testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)

jnz syscall_trace_entry

cmpl $(nr_syscalls), %eax

jae syscall_badsys

syscall_call:

call *sys_call_table(,%eax,4)

movl %eax,EAX(%esp) # store the return value

syscall_exit:

示意代码如下:

read() ----> [read()封装例程---->]system_call()[base on "sys_call_table"]---->sys_read()

 

5.3 如何添加自己的系统调用函数

由上述分析可知,LINUX从用户空间到内核代表用户空间程序执行的流程如下:

用户编写自己的程序-->系统调用-->找到相应的系统调用函数代表用户程序执行.如上述的read()示意代码.

一、用户编写自己的程序去引发系统调用有两种实现方式:C库和LINUX本身提供的宏(_syscalln);(如上述的read())

二、System_call()校验系统调用号的合法性;

三、实现代表用户空间程序执行的系统调用函数.(如上述的sys_read())

基于上述三个步骤,知道我们要添加我们自己的系统调用函数涉及到三部分:

.用户可以陷入系统调用的代码实现;

.添加系统调用号;

.实现代表用户空间执行的系统调用函数

 

5.3.1 用户可以陷入系统调用的代码实现

通常,系统调用靠C库支持.但是,如果是我们定制的系统调用,C库是不可能已经支持的.这时候,我们只能用LINUX本身提供的宏(_syscalln)来实现.示意代码如下:

#define __NR_foo 283

__syscall0(long,foo)

 

int main()

{

Long stack_size;

 

Stack_size = foo();

Printf("The kernel stack size is %ld\n",stack_size);

Return 0;

}

[:]_syscalln中的"n"是有命名规则的,(2+2*n)表征此函数参数的个数.

 

5.3.2 添加系统调用号

修改系统调用表:

Arch/i386/kernel/entry.s

ENTRY(sys_call_table)

.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */

.long sys_exit

.long sys_fork

.long sys_read

.long sys_write

.long sys_open /* 5 */

... ...

.long sys_mq_timedreceive /* 280 */

.long sys_mq_notify

.long sys_mq_getsetattr

.long sys_foo

 

Asm-i386/unistd.h

#define __NR_restart_syscall      0

#define __NR_exit   1

#define __NR_fork   2

#define __NR_read   3

#define __NR_write   4

#define __NR_open   5

#define __NR_close   6

#define __NR_waitpid   7

... ...

#define__NR_foo283

 

5.3.3 实现代表用户程序执行的系统调用函数体

例如我们可以将其实现放在kernel/sys.c文件中.如下:

Asmlinkage long sys_foo(void)

{

Return THREAD_SIZE;

}

原创粉丝点击