Linux系统调用

来源:互联网 发布:iphone8怎么卸载软件 编辑:程序博客网 时间:2024/06/06 08:49

一、系统调用

系统调用发生在用户进程通过调用特殊函数(例如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,&divide_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//如果超过了,就跳到badsyssyscall_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寄存器中返回到用户态。