Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。系统调用实际上是内核中的一些C函数,它们都以sys开头的,如sys_mkdir()。它们通过一个指令int 0x80(软中断)把控制权交给内核,即进入特权级执行。int 0x80指令会使“执行”跳转到系统调用在内核中定义的入口地址。这个位置是唯一确定的,且只可被用户进程读,不可写,这正是利用了“陷阱们”跳转的优点。
进程可以跳转到的内核在的位置叫做system_call。它通过查找系统调用表sys_call_table,找到希望调用的内核函数的地址,调用此函数后返回。Linux里面的每个系统调用是由一些宏,一张系统调用表,一个系统调用入口来完成的。
设定0x80号中断
系统启动后,初始化工作中较重要的一部分在start_kernel()函数中进行,函数start_kernel调用了函数trap_init并设置了各种中断服务程序入口。与系统调用相关的是宏set_system_gate(0x80,&system_call),set_system_gate函数调用层次图如下:
宏_set_gate()的作用是把addr地址值放入gate_addr所指向的内存单元中,使中断向量表中的0x80项保存中断服务程序system_call的入口地址。
系统调用现场保护
系统调用表sys_call_table,部分列出如下:
system_call是都有系统调用的入口。它的功能是保存所有寄存器,检验是否合法的系统调用,根据_sys_call_table中的偏移量把控制权转给真正的系统调用代码,系统调用完毕后调用_ret_from_sys_call(),返回到用户空间。
与中断一样,当进入系统调用时要用到宏过程SAVE_ALL保护现场(保存寄存器)。当系统调用返回时,要调用RESTORE_ALL恢复现场。SAVE_ALL分析如下:
SAVE_ALL保存的一帧现场所有的寄存器,与该过程所要传递的pt_regs结构中的成员一致。
宏过程RESTORE_ALL与SAVE_ALL几乎是实现完全相反的操作。
Linux系统调用的流程
1 系统调用过程
系统启动后,经过引导和实模式下的初始化,进入保护模式下的核心初始化,执行head.s。其中的startup_32代码段中调用Setup_idt。Setup_idt的功能是建立一个空的且具有256个项的中断向量表。
接着系统转入start_kernel()模块。在该模块中,调用trap_init()初始化中断向量表,并且把系统调用system_call项注册为0x80号中断的中断服务程序。
2 中断INT 0x80入口处理
中断INT 0x80入口system_call的汇编程序的主要功能是:
- 保存寄存器当前值(SAVE_ALL)
- 检验是否为合法的系统调用
- 根据系统调用表_sys_call_table和EAX持有的系统调用号找出并转入系统调用响应函数;
- 从该响应函数返回后,让EAX寄存器保存函数返回值,跳转至ret_from_sys_call
- 最后,在执行位于用户程序中系统调用命令后面余下的指令之前,若INT 0x80的返回值为非,则直接按类型type返回;否则,将INT 0x80的返回值取其绝对值,保留在errno变量中,返回-1.
system_call是整个系统调用公共入口部分。