~x86 特权级:操作系统+CPU实现保护机制,应用程序不能随意访问OS空间
~x86 MMU 内存管理单元:实现内存的映射,将虚拟的连续的逻辑地址空间投射到分散的物理空间。
1. x86 特权级:(关键:几种特权级/处于什么特权级,如何知道/特权级切换的实现)
~有0,1,2,3四个特权级,level0:kernel,level3:applications,
Linux一般用0和3两个级别足够。
应用程序在访问数据段,访问页表,进入中断服务例程(ISRs),检查失败会产生访问错误。
如何进行特权级检查:
段选择子 Segment Selector 位于段寄存器中,
RPL 位于数据段, CPL位于代码段,结合起来与段描述符中的DPL 进行比较。
段描述符:表示一个段的特征,DPL表示了这个数据段或代码段的特权级。将RPL(CPL)和DPL进行比较,看特权级是否符合。
中断门,陷阱门和段描述符都有对应的DPL,产生中断,发生陷入或者访问内存的时候会产生对应的RPL(CPL)
RPL 位于段寄存器 DS ES FS GS
CPL 位于 段寄存器 CS SS
发出请求方:当前代码段的特权级(CPL) 要数据的特权级(RPL)
(接受)访问段:中断——访问代码段,内存访问——数据段。都有一个DPL(表示了访问目标的特权级)
访问门时: CPL <= DPL(门) & CPL >= DPL(段)(访问门时,代码段的CPL 要小于 门,可以通过门,通过门后要访问特权级高的代码。
访问段时:MAX(CPL,RPL)<= DPL(段) 使用方 特权级要高才能访问。
~特权级的切换
基于中断实现特权级的转换:
中断:
中断描述符表建立中断门
产生中断(异常,外中断,软中断)后,内核栈压入了SS ES(被打断的程序堆栈信息)EFLAGS(标志位,是否溢出,+/-等) CS EIP(返回继续执行的代码地址) Error Code(错误代码) 这个栈是内核态ring0的栈。
如图:
特权级的转换:
如何实现ring0到ring3转换?
构造一个用户态的栈
内核程序发生中断:将Error Code EIP CS(CPL=0)EFLAGS压入内核栈,不存在栈的转换。
使其返回到用户空间:模仿ring3产生中断时候的现场:将CS代码段的CPL寄存器改变为3:
执行指令iret,就可以跳回用户态。同时形成新的栈:
~ ring3 to ring0
通过对内核栈的修改:
执行IRET指令,依然返回到ring0
_________
Q:从用户态转换到内核态执行时候,如何找到CS SS位于什么位置?
CS EIP 表明指令地址:中断描述符表中可以找到。
堆栈信息在什么地方? IDT中没有,而在TSS(task state segment)任务状态段:保存了各种特权级的堆栈信息。
保存了3个特权级的堆栈信息,CPU 根据CS SS 设置新的堆栈。
因此,当进程从用户态进入内核态后,CS EIP的值根据 IDT 设置,SS ESP的值根据TSS设置。
OS 需要设置好 IDT 和 TSS的信息。
TSS表位于内存中,获得的方式如下:
使用GDT,GDT中有TSS Descriptor:
TSS Descriptor 中保存了TSS的地址,从而可以找到TSS内容。
IDT TSS GDT 需要OS软件去填写。
在GDT表中建立好TSS Descriptor之后,设置一个特殊的寄存器 Task Register,保存段选择子,基地址等信息。
通过找Task Register 或者 GDT 表都可以找到TSS内容。
分配内存——初始化(特别是设置SS0 ESP0)——GDT中填写TSS 描述符——设置TSS selector(Task Register)