1、一般现代CPU都有几种不同的指令执行级别
2、在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态
3、而在相应地低级别执行状态下,代码的掌控范围会受到限制。只能在对应级别允许范围内活动
4、Intel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级分别来表示内核态和用户态
---------------------------------------
1、cs寄存器的最低两位表明了当前代码的特权级
2、CPU每条指令的读取都是通过cs:eip这两个寄存器:其中cs是代码段选择寄存器,eip是偏移量寄存器。上述判断是由硬件完成
3、一般来说,在Linux中,地址空间是一个显著地标志:0xc0000000以上的地址空间只能在内核态下访问,0x0000000 - 0xbfffffff的地址空间在两种状态下都可以访问,简单来说,用户态进程位于内存的0~3G空间中, 内核代码则位于内存3G以上的部分。(这里的地址空间是逻辑地址而不是物理地址 - CPU的MMU来进行地址转换)
--------------------------------------
寄存器上下文
1、从用户态切换到内核态时,必须要保存用户态寄存器的上下文
2、中断/int指令会在堆栈上保存一些寄存器的值。如:用户态栈顶地址、当时的状态字、当时的cs:eip的值
--------------------------------------
1、interrupt(ex: int 0x80) - save
cs:eip/ss:eip/eflags(current) to Kernel stack, then load cs:eip(entry of a specific ISR) and ss:eip(point to Kernel stack)
2、SAVE_ALL
-...//内核代码,完成中断服务,发生进程调度
3、RESTORE_ALL
4、iret - pop cs:eip/ee:eip/eflag from Kernel stack
解释:CS:eip指向当前中断处理程序的入口,对于系统调用来说,则指向sys_call函数
----------------------------------------
系统调用的意义
1、操作系统为用户态进程与硬件设备进行交互提供了一组接口-系统调用。把用户从底层的硬件编程中解放出来。
2、应用编程接口(application program interface,API)和系统调用是不同的。API和系统调用的关系:将系统调用封装成一个API接口。系统调用通过软中断向内核发出一个明确的请求。
3、libc库定义的一些API引用了 封装例程(wrapper routine,唯一目的就是发布系统调用)
-一般每个系统调用对应一个封装例程
-库再用这些封装例程定义出给用户的API
4、不是每个API都对应一个特定的系统调用
-API可能直接提供用户态服务,如一个数学函数
-一个单独的API可能调用几个系统调用
-不同的API可能调用了同一个系统调用
5、返回值
-大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1在多数情况下表示内核不能满足进程的请求
-libc中定义的errno变量包含特定的出错码
-------------------------------------------------------
int 0x80对应内核代码入口起点
sys_xyz() -> 中断服务程序
系统调用三层皮:xyz、system_call和sys_xyz
-------------------------------------------------------
1、当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
-Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常
2、传参
-内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数。使用eax寄存器传递。
-------------------------------------------------------
1、系统调用也需要输入输出参数,例如:
-实际的值
-用户态进程地址空间的变量的地址
-甚至是包含指向用户态函数的指针的数据结构的地址
2、system_call是Linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
-一个应用程序调用fork封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即_NR_FORK)。
-这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
-进入sys_call之后,立即将eax的值压入内核堆栈
3、寄存器传递参数具有如下限制
-每个参数的长度不能超过寄存器的长度,即32位
-在系统调用号(eax)之外,参数的个数不能超过6个(ebx、ecx、edx、esi、edi、ebp),超过6个就把每一个寄存器作为一个指针,指向一段内存 进入到内核态之后,可以访问所有的内存地址空间。