1.用户态、内核态和中断处理过程
1.1 用户态和内核态简介
一般现代CPU都有几种不同的指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态;而在相应的低级别执行状态下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动,这种CPU执行级别就对应着用户态。
例如:intel*86CPU有4种不同的执行级别0,1,2,3,Linux只使用了其中的0级和3级分别表示内核态和用户态。
1.2 区分用户态和内核态
显著的区分方法是看cs:eip,在内核态时,cs:eip可以是任意的值。
一般来讲在linux中,地址空间(逻辑地址)是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000——0xbfffffff的地址空间可在两种状态下访问。
1.3 中断处理
1.3.1 用户态进入内核态
用户态进入内核态的方式:中断、调用系统调用。
中断处理是从用户态进入内核态主要的方式,系统调用是一种特殊的中断。
1.3.2 寄存器的上下切换
用户态进入内核态时会发生寄存器的上下切换,要保存用户态的寄存器上下文。
中断/int指令会在堆栈上保存一些寄存器的值,如用户态栈顶地址、当前的状态字、当时的es:eip的值等。
中断发生后的第一件事就是保存现场(进入中断程序,保存需要用到的寄存器的数据),中断处理程序结束前最后一件事就是恢复现场(退出中断程序,恢复、保存寄存器的数据)。
1.3.2 中断处理的完整过程
//系统调用,保存cs:eip,当前堆栈段、栈顶、标志寄存器到内核堆栈,同时加载中断信号或系统调用,相关联的中断服务例程、ss:eip
interrupt(ex:int 0x80)-save cs:eip/ss:esp/eflags(current)to kernel stack,then load cs:eip(entry of specific ISR)and ss:eip(point to kernel stack).
//完成上述步骤之后,当前CPU在执行下一条指令,就在执行中断处理程序的入口,对堆栈的操作不再是用户态的堆栈,开始操作内核态堆栈
save_all
//内核代码,完成中断服务,发生进程调度。如果完成中断服务,没有发生进程调度,就会返回到原来的状态;如果发生进程调度,当前的状态
//就会暂时保存
restore_all
iret-pop cs:eip/ss:eip/eflags from kernel stack
2.系统调用概述
2.1系统调用的意义
系统调用:操作系统为用户态进程与硬件设备进行交互提供了一组接口。
把用户从底层的硬件编程中解放出来;极大的提高了系统安全性;使用户程序具有可移植性。
2.2应用程序编程接口(application program inteface,API)
API和系统调用
- API只是一个函数定义;
- 系统调用通过软中断向内核发出一个明确的请求;
- 不是每个API对应一个特定的系统调用。如API可能直接提供用户态的服务(如,一些数学函数);一个单独的API可能调用几个系统调用;不同的API可能调用了同一个系统调用。
- 返回值。大部分封装例程返回一个整数,其含义依赖与相应的系统调用;-1在多数情况下表示内核不能满足进程的请求。
图解:
函数xyz()是系统调用对应的API,API中封装了系统调用,会触发一个int 0x80的中断,0x80这个中断向量对应着system_call这个内核代码的入口起点。
2.3获取当前系统时间
#include <stdio.h> #include <time.h> int main(){ time_t tt; struct tm *t; //tt=time(NULL); asm volatile( "mov $0,%%ebx " "mov $0xd,%%eax " "int $0x80 " "mov %%eax,%0 " : "=m" (tt) ); t=localtime(&tt); printf("%d,%d,%d",t->tm_year+1900,t->tm_mon,t->tm_wday); return 0; }
分析嵌入式汇编代码:
首先ebx清0(系统调用传递的第一个参数使用ebx,这里是NULL),然后0xd放在eax中(eax是传递系统调用号,这里time是0xd(13)),
返回值通过eax寄存器返回,eax放在%0即tt这个变量。