有4个层次的特权级,从高到低依次是:0级、1级、2级、3级。切换特权级是指从0级转移到1级、或从1级转移到3级。总之,是指从一个特权级转移到了另一个不一样的特权级。函数
学习特权级切换,关键知识点是:学习
call
和iret
。只有使用调用门才能从低特权级转移到高特权级,更具体地说,是使用语句call 门选择子
。操作系统
门选择子的结构和段选择子一致,只不过它指向的是门描述符而不是段描述符。code
门描述符和段描述符占用的内存空间相同,都是8个字节,64个bit。可后者包含的元素是:进程
使用调用门从低特权级转移到高特权级的代码是:ip
push ax push bx ;SelectorGate 指向一个高特权级的目标代码段 call SelectorGate:0
入栈了2个元素,2
就是上文提到的ParamCount
。能够把它理解成函数的参数。在门描述符中,ParamCount
占用5个bit位,能表示的最大值是2的5次方-1,即31。这意味着,使用一个调用门,最多能入栈31个元素。内存
假定,上面的代码的能成功从低特权级代码段L转移到高特权级代码段H。L和H是不一样的代码段,各自的堆栈也不一样。这不是必须的。L和H共用一个堆栈,也不是不行。这样的话,在H中操做堆栈可能会破坏L中的堆栈(总之,存在这种可能)。因此,代码段拥有独立的堆栈更好。工作流
那么,问题来了。入栈操做发生在L中,被入栈元素存在于L的堆栈LS,在H中怎么从LS中获取数据呢?也许存在方法,但必定很繁琐。现实中,CPU会自动把LS中的元素复制到H的堆栈中。并不是复制LS中的所有元素,而是从LS的栈顶开始,复制ParamCount
个元素。这个复制操做发生在call
执行的时候。it
短调用是段内部的调用,长调用是段之间的调用。长调用才能从低特权级转移到高特权级。table
短调用和长调用示意图之间的差异,仅仅在于后者把cs
入栈了。cs
是调用者的代码段选择子。
示意图中的堆栈,是H的堆栈HS。
call执行前----> | 高地址 | |
---|---|---|
ss | ||
esp | <-----------堆栈 | |
参数二(ax) | ||
参数一(bx) | ||
call执行后----> | eip | |
call执行前----> | 高地址 | |
---|---|---|
ss | ||
esp | <-----------堆栈 | |
参数二(ax) | ||
参数一(bx) | ||
cs | ||
call执行后----> | eip | |
调用门的运行过程,涉及LS和HS两个堆栈。上面的示意图只画出了HS的状态,不足以阐述调用门的整个流程,本小节再用文字详细说明调用门的运行过程。
先介绍一个新东西,TSS,任务状态寄存器,一个寄存器。CPU在切换任务的时候,能用TSS给切换下来的任务创建一个快照,这个快照包含一个任务的全部寄存器数据。不过,大部分操做系统嫌弃这种切换方式太消耗时间,并无彻底按照CPU厂商的意图使用TSS。Linux系统也是如此。
在TSS中,包含ss0、esp0
、ss一、esp1
、ss二、esp2
三组数据,正好对应0特权级、1特权级、2特权级三个层次的特权级。
特权级不是有四个层次吗?为何没有对应3特权级的那一组数据呢?TSS的做用是为低特权级向高特权级转移时提供高特权级的堆栈。3特权级是最低特权级,没有更低的特权级向它转移。
假如,从3特权级向0特权级转移,CPU会从TSS中选择ss0、esp0
做为0特权级代码段的堆栈。
使用call SelectorGate:0
实现低特权级向高特权级转移的流程以下(不叙述CPL等特权级检查流程,假设知足这些条件):
call
语句时。ss_old、esp_old
临时保存起来。ss、esp
,将堆栈指向的新堆栈。例如,DPL的值是0,选择ss0、esp0
。ss_old、esp_old
中的入栈到新堆栈中。ParamCount
个元素。cs、eip
。从低特权级转移到高特权级的方法是,使用调用门,具体语句是call SelectorGate:0
。
不能使用jmp
。jmp
只能在实现短调用,在同一个特权级转移。由于jmp
不会将下一条指令的地址存储到新特权级的堆栈中,这意味着,jmp
是一个有去无回的指令。从低特权级转移到高特权级的场景,通常是用户进程求助操做系统完成某种功能,须要再次返回用户进程。
电脑开机后,CPU的特权级是0,这是从BIOS那里寄存下来的。这种知识彷佛无用,懒得多说。
前文讲了从低特权级切换到高特权级的方法,可CPU从开始工做的那一刻起,一直都是在0特权级。这样说来,若是要动手实现从低特权级转移到高特权级,应该先实现从高特权级转移到高特权级。
怎么实现?
前文已经埋下了伏笔,call
指令会将调用者(低特权级)的cs(选择子)
和eip(偏移量)
入栈到被调用者(高特权级)的堆栈中。从堆栈中获取调用者(低特权级)的cs(选择子)
和eip(偏移量)
,就能从高特权级转移到低特权级。完成这项工做,只需一个指令而已,iretf
。
;特权级是3 push ax push bx call SelectorGate:0 mov ax, 5
call SelectorGate:0
调用下面的代码。
;特权级是0 ;调用门选择子指向的目标代码段,高特权级代码段 ;一些操做,示范,没必要理会具体功能 mov al, 'A' mov ah, 0Fh mov [gs:(80*20+20)*2], ax iretf
iretf
执行后,CPU会继续执行mov ax, 5
。mov ax, 5
就是被调用者堆栈中的cs:eip
指向的指令。
仍是画两个和上面call
指令相似的堆栈图吧。
ret执行后 | 高地址 |
---|---|
调用者ss | |
调用者esp | |
参数一 | |
参数二 | |
ret执行前 | 调用者eip |
retf执行后 | 高地址 |
---|---|
调用者ss | |
调用者esp | |
参数一 | |
参数二 | |
调用者cs | |
retf执行前 | 调用者eip |
长调用返回使用iretf
指令将上面的堆栈S中的元素出栈。具体流程以下:
iretf
含有参数,esp增长参数个数跳过这些参数。esp
、ss
中。此时会切换到调用者堆栈。iretf
含有参数,增长esp的值以跳过参数(在call
前,参数也压入了调用者堆栈中)。cs:eip
代码。没有找到iretf
的权威资料,上面的资料也没有验证。先搁置吧。
不使用调用门能不能转移特权级?
使用jmp
只能在同特权级跳转。
一致代码段,call
只能转移到比当前特权级高或相等的特权级。
非一致代码段,call
只能在相同特权级跳转。
没有弄明白。好像不重要。