系统调用

int 0x80方式

eax寄存器中为调用号,ebx、ecx、edx、esi等寄存器则依次为参数

(1) 由于INT指令发生了不同优先级之间的控制转移,所以首先从TSS(任务状态段)中获取高优先级的核心堆栈信息(SS和ESP);

(2) 把低优先级堆栈信息(SS和ESP)保留到高优先级堆栈(即核心栈)中;

(3) 把EFLAGS,外层CS,EIP推入高优先级堆栈(核心栈)中。

(4) 通过IDT表项的0x80处,加载CS,EIP(控制转移至中断处理函数)

STACK 
----------------------
返回用户态的EIP
用户态的CS
用户态的EFLAGS
用户态的ESP
用户态的SS(和DS相同)

然后就进入了中断0x80的处理函数system_call了,在该函数中首先使用了一个宏SAVE_ALL,该宏的定义如下所示:

#define SAVE_ALL / 
cld; / 
pushl %es; / 
pushl %ds; / 
pushl %eax; / 
pushl %ebp; / 
pushl %edi; / 
pushl %esi; / 
pushl %edx; / 
pushl %ecx; / 
pushl %ebx; / 
movl $(__KERNEL_DS),%edx; / 
movl %edx,%ds; / 
movl %edx,%es;

该宏的功能一方面是将寄存器上下文压入到核心栈中,对于系统调用,同时也是系统调用参数的传入过程。 因为在不同特权级之间控制转换时,INT指令不同于CALL指令,它不会将外层堆栈的参数自动拷贝到内层堆栈中。所以在调用系统调用时,必须先象前面的例子里提到的那样,把参数指定到各个寄存器中,然后在陷入核心之后使用SAVE_ALL把这些保存在寄存器中的参数依次压入核心栈,这样核心才能使用用户传入的参数。

接着调用system_call_table

call *SYMBOL_NAME(sys_call_table)(,%eax,4) 

这里是以EAX(即前面提到的系统调用号)作为偏移,在系统调用表sys_call_table中查找处理函数入口地址,并跳转到该入口地址。

sysenter 方式

在新版本的内核中,加入了sysenter方式,sysenter 指令之前有一个 mov edx,esp的操作,后续进入Ring0通过edx定位参数

关键是在 Ring3 的代码调用了 sysenter 指令之后,CPU 会做出如下的操作:

1. 将 SYSENTER_CS_MSR 的值装载到 cs 寄存器

2. 将 SYSENTER_EIP_MSR 的值装载到 eip 寄存器

3. 将 SYSENTER_CS_MSR 的值加 8(Ring0 的堆栈段描述符)装载到 ss 寄存器。

4. 将 SYSENTER_ESP_MSR 的值装载到 esp 寄存器

5. 将特权级切换到 Ring0

6. 如果 EFLAGS 寄存器的 VM 标志被置位,则清除该标志

7. 开始执行指定的 Ring0 代码 ,同上int方式的接口函数 system_call

在 Ring0 代码执行完毕,调用 sysexit 指令退回 Ring3 时,CPU 会做出如下操作:

1. 将 SYSENTER_CS_MSR 的值加 16(Ring3 的代码段描述符)装载到 cs 寄存器

2. 将寄存器 edx 的值装载到 eip 寄存器

3. 将 SYSENTER_CS_MSR 的值加 24(Ring3 的堆栈段描述符)装载到 ss 寄存器

4. 将寄存器 ecx 的值装载到 esp 寄存器

5. 将特权级切换到 Ring3

6. 继续执行 Ring3 的代码

Last updated