标签:
1.1 用户态和内核态简介
一般现代CPU都有几种不同的指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态;而在相应的低级别执行状态下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动,这种CPU执行级别就对应着用户态。
例如:intel*86CPU有4种不同的执行级别0,1,2,3,Linux只使用了其中的0级和3级分别表示内核态和用户态。
显著的区分方法是看cs:eip,在内核态时,cs:eip可以是任意的值。
一般来讲在linux中,地址空间(逻辑地址)是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000——0xbfffffff的地址空间可在两种状态下访问。
用户态进入内核态的方式:中断、调用系统调用。
中断处理是从用户态进入内核态主要的方式,系统调用是一种特殊的中断。
用户态进入内核态时会发生寄存器的上下切换,要保存用户态的寄存器上下文。
中断/int指令会在堆栈上保存一些寄存器的值,如用户态栈顶地址、当前的状态字、当时的es:eip的值等。
中断发生后的第一件事就是保存现场(进入中断程序,保存需要用到的寄存器的数据),中断处理程序结束前最后一件事就是恢复现场(退出中断程序,恢复、保存寄存器的数据)。
//系统调用,保存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
系统调用:操作系统为用户态进程与硬件设备进行交互提供了一组接口。
把用户从底层的硬件编程中解放出来;极大的提高了系统安全性;使用户程序具有可移植性。
API和系统调用
图解:
函数xyz()是系统调用对应的API,API中封装了系统调用,会触发一个int 0x80的中断,0x80这个中断向量对应着system_call这个内核代码的入口起点。
#include <stdio.h> #include <time.h> int main(){ time_t tt; struct tm *t; //tt=time(NULL); asm volatile( "mov $0,%%ebx\n\t" "mov $0xd,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=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这个变量。
标签:
原文地址:http://www.cnblogs.com/boyiliushui/p/5473601.html