系统调用的三层机制(上)
用户态、内核态和中断
- 内核态:高执行级别,代码可以执行特权指令,访问任意的物理内存,CPU的执行级别对应的就是内核态。
- 用户态:与内核态相对应的低级别指令,代码能够掌控的范围会受到限制。
- 中断:中断处理是用户态进入内核态的主要方式。系统调用是特殊的中断(用户态程序在执行过程中,调用了一个系统调用,陷入了内核态)。
用户态和内核态显著的区分方法就是CS:EIP的指向范围。在内核态时,CS:EIP的值可以是任意的地址,在32位的x86机器上有4GB的进程地址空间,内核态下的这4GB的地址空间全都可以访问,但在用户态时,只能访问0x00000000~0xbfffffff的地址空间,0xc0000000以上的地址空间只能在内核态下访问。
CPU每条指令的读取都是通过cs:eip这两个寄存器。
- cs:代码段选择寄存器
- eip:偏移量寄存器。
cs寄存器的最后两位表明了当前代码的特权级。如图Linux内存空间:
寄存器上下文
- 寄存器上下文:从用户态切换到内核态时,必须保存用户态的寄存器上下文。
int指令触发中断机制会在堆栈上保存一些寄存器的值,会保存用户态栈顶地址、当时的状态字、当时的CS:EIP的值。同时会将内核态的栈顶地址、内核态的状态字放入CPU对应的寄存器,并且CS:EIP寄存器的值会指向中断处理程序的入口。
系统调用概述
系统调用的意义时操作系统为用户态进程与硬件设备进行交互提供了一组接口。
- 把用户从底层的硬件编程中解放出来。
- 极大的提高系统的安全性。
- 使用户程序具有可移植性。
操作系统提供的API和系统调用的关系
每个系统调用对应一个系统调用的封装例程,函数库再用这些封装历程定义出给程序员调用的API。
如图4-4所示,User Mode表示用户态,Kernel Mode表示内核态。xyz()为API函数,触发int $0x80()的中断,对应system_call内核代码的起点,即中断向量0x80对应的中断服务程序入口,内部会有sys_xyz()系统调用处理函数,再ret_from_sys_call。
触发系统调用及参数传递方式
- 触发系统调用:内核通过给每个系统调用一个编号来区分,即系统调用号,将API函数xyz()和系统调用内核函数sys_xyz()关联起来了。
- 参数传递方式
- 普通函数调用是通过将参数压栈的方式传递的。
- 系统调用从用户态切换到内核态,在两种执行模式下使用不同的堆栈,通过比较特殊的寄存器传递参数。
C代码中嵌入汇编代码触发一个系统调用
#include<stdio.h>
#include<unistd.h>
int main(){
int ret;
char * a = "5641";
char * b = "7894";
asm volatile(
"movl %2, %%ecx
"//系统调用传递第一个参数使用ECX寄存器为2
"movl %1, %%ebx
"//系统调用传递第一个参数使用ECX寄存器为1
"movl $0x09, %%eax
"//使用%eax传递系统调用号09,用16进制为0x09
"int $0x80
"//触发系统调用
"movl %%eax, %0"//通过EAX寄存器返回系统调用值
:"=m"(ret)
:"b" (a),"c"(b)
);
if(ret==0)
printf("link successfully
");
else
printf("Unable to link the file
");
return 0;
}
运行结果如图: