一、系统调用
系统调用发生在用户进程通过调用特殊函数(例如open、fork)以请求内核提供服务的时候。此时,用户进程被暂时挂起,内核检验用户请求,尝试执行,并将结果反馈给用户进程,接着用户进程重新启动。
二、基本原理
系统调用实际上是内核中的一些函数,它们是以sys开头的,如sys_fork().系统调用是由可屏蔽中断或异常来唤醒的,通过一个指令int 0x80(软中断)把控制权交给内核,还用寄存器来传递参数,EAX寄存器用于表示系统调用的调用号,当系统调用返回时,EAX又作为调用结果的返回值。
讲一讲中断
操作系统一般是通过中断来从用户态切换到内核态的。中断就是一个硬件或软件发出的请求,要求CPU暂停当前工作去处理其他事情。
中断有两个属性:中断号和中断处理程序。不同中断有不同的中断号,一个中断处理程序对应一个中断号。
中断有两种类型,一种是硬件中断,这种中断来自于硬件的异常或者其他事件的发生,如电源掉电、键盘被按下等。另一种是软件中断,是一条指令,使用这条指令可以触发某个中断并执行其中断处理程序。
在内核中,有一个数组称为中断向量表。这个数组的第n项包含了第n号中断处理程序的指针。当中断到来时,CPU会暂停当前执行的代码,根据中断号,在中断向量表中找到对应的中断处理程序,并调用它。中断处理程序执行完成之后,CPU会继续执行之前的代码。
三、内核实现分析
1.在程序中调用一个系统调用时,是以一个函数的形式调用的,但这个函数只是对内核中系统调用的封装。例如程序调用fork(),在内核中是以一个宏定义它的:_syscall0(type,name) 是用于定义一个没有参数的系统调用的封装。以此类推, _syscall1(type,name,type1,arg1)是用于带一个参数的系统调用。
对于fork的系统调用=》syscall0(pid_t,fork)
下面的代码是I386版本在unistd.h的_syscall0的定义:
#define _syscall0(type,name) \//type是系统调用的返回值类型,系统name是系统调用的名称
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \ //表示调用0x80号中断
: "=a" (__res) \ //表示用eax输出返回值数据并存储在_regs
: "0" (__NR_##name)); \ //"0"表示由编译器选择和输出相同的寄存器来传递参数,这里是eax;__NR_##name 是进行对系统调用名的连接,以便在内核中的系统调用表找到相应的系统调用号
__syscall_return(type,__res); \ //用于检查系统调用的返回值
}
2.在执行了上面代码的“int 0x80”中断之后,CPU会到中断向量表查找第0x80号元素所对应的中断处理程序。
中断向量表在Linux源代码的traps.c中定义:
void __init trap_init(void)
{
......
set_trap_gate(0,÷_error);
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3, &int3);
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
......
set_system_gate(SYSCALL_VECTOR,&system_call);
......
}
3.从上面这段代码可以看出系统调用对应的中断号是SYSCALL_VECTOR,它在irq_vector.h里定义#define SYSCALL_VECTOR 0x80
,所以根据中断向量表找到了下一步要执行的函数system_call。它在i386版本的Linux源代码entry.S中定义:
ENTRY(system_call)
pushl %eax // 把存储在eax寄存器中的系统调用号压栈,保存ORIG_EAX寄存器里的值
SAVE_ALL //将所有寄存器的值拷贝压人内核栈
GET_THREAD_INFO(%ebp)//从ebx寄存器中取得指向当前任务的指针
......
cmpl $(nr_syscalls), %eax//检查eax中的系统调用号是否超过系统调用允许的最大号
jae syscall_badsys//如果超过了,就跳到badsys
syscall_call:
call *sys_call_table(,%eax,4)//从系统调用表中找到要调用的函数
movl %eax,EAX(%esp) //将系统调用的返回值存在eax中
......
restore_all:
RESTORE_ALL//恢复之前SAVE_ALL保存的寄存器
call *sys_call_table(,%eax,4) 在系统调用fork的时候就是在 call sys_fork()函数了。
最后把返回值存在eax寄存器中返回到用户态。