系统调用:
系统调用是内核的入口 ,借助这一机制,进程可以请求内核以自己的名义去执行某些动作。以应用程序编程接口(API)的形式,内核提供一系列API供应用程序调用
在深入系统调用的运作方式之前需得理解下面几点:
- 系统调用将处理器从用户状态切换到内核状态,以便cpu访问受到保护的系统内核
- 系统调用的组成是固定的,每个系统调用都有一个唯一的数字标识符,程序通过名称来标识系统调用
- 每个系统调用可以辅之以一套参数,对用户空间(亦即进程的虚拟内存空间)与内核空间之间传递信息加以规范
从编程的角度讲,系统调用和调用c语言的函数很像,然而,在执行系统调用时,会比c语言的函数调用复杂很多,下面是系统调用的详细过程:
- 应用程序通过调用c语言函数库中的外壳函数(wrapper)来发起系统调用。
- 对系统调用中断来处理例程来讲,外壳函数必须保证所有的系统调用的参数可用。通过堆栈这些参数传入外壳函数后,外壳函数会将这些参数复制到寄存器。
- 所有系统调用进入内核的方式都相同,所以内核需要区分每个系统调用,为此,外壳函数会把系统调用的编号复制到一个特殊的cpu(%eax)
- 外壳函数调用系统中断指令(int 0x80),引发处理器从用户态切换到内核态,并执行系统中断的中断矢量所指向的代码。
较新的x86_32硬件平台实现了sysenter指令,比传统的int 0x80进入内核的速度更快,2.6内核和glibc2.3.2以后的版本都支持sysenter指令
- 为了响应中断0x80,内核会调用system_call()例程,来处理这次中断。
1、 在内核栈中保存寄存器的值 在内核栈中保存寄存器的值
2、审核系统调用编号的有效性。
3、以系统调用编号对存放所有调用服务例程的列表(内核变量sys_call_table)进行索引,发现并调用相应编号的系统例程。若系统调用例程带有参数,那么首先检查参数的有效性,例如,会检查地址指向用户空间的内存的位置是否有效。随后该服务例程会执行必要的任务,这可能涉及对特定参数中指定地址处的值的修改,以及在用户内存和内核内存中传递数据,最后,该服务例程会将结果返回给system_call()例程。
- 若系统调用例程的返回值表明调用有误,外壳函数会将该值用来设置全局变量errno,然后外壳函数会返回到调用函数,并同时返回一个整数0或者-1,0表示成功,-1表示失败。通过查看全局变量erron来获取错误信息。