既然ring 3的应用程序
无法去有效的访问ring 0的数据的话
那么我们怎么能够实现
我们前面说的一个很重要的一个因素
就是我们的操作系统
要给我们的应用程序提供服务
那既然它都不能访问了
它怎么能提供服务呢
其实我们还是需要有一种手段
来实现不同特权级的一个跳转
比如说我们应用程序选择了内核的访问
通过一种机制要让我们控制流
从应用程序跳到内核态去执行
这是怎么来实现的
在X86硬件架构里面有多种实现方式
实现不同特权级的跳转
但是在这里面我们只用其中一种
就是基于中断
通过中断来实现这个优先级
或者特权级的转换
那怎么通过中断来实现
这里面有必要对中断要做一定的讲解
我们前面其实在lab1里面
已经对中断有所了解
其实中断它会有一个中断门
那么有了中断门之后 我们其实就可以
通过我们前面讲到的中断描述符表
建立好这个中断门来实现跳转
这里面可以看到
这里面中断的特权级的改变
首先我们可以看到
我们正在执行的时候
它是在用这么一个栈在执行
然后如果说这时候产生了中断
中断有很多种方式
比如说异常 外部中断
或者是软中断等等
我们说软中断就相当于是
我们通过INT这条指令来产生中断
当我们这个中断产生之后
会出现一个变化
那么这是产生中断之前
这是产生中断之后
那么我们一旦产生了中断
在我们的内核态ring 0这个栈里面
会压入一系列的一些数据
以便于恢复被打断的程序继续执行
压东西包含了压在内核ring 0
这个栈里面的
包括什么SS ES EFLAGS CS 和EIP
那么前两个SS ES代表的是
当前被打断的程序
它的一个堆栈信息
那么EFLAGS代表
它当前执行的时候一些标志位
比如是否溢出
这些所谓的标志执行加 减这些
可以看出来是否溢出
那么这些标志实际上是
放在EFLAGS里面的
还有就是它的返回地址CS和EIP
就是打断之后 这个地址
如果执行完毕这个中断处理例程之后
还要回去继续执行
这保存了它回去的地址
也可能还会有一些是错误代码
这个异常相关
那这是产生中断之前
这是产生中断之后
那么可以看到它的堆栈会产生变化
那么这个栈我们再强调一下这个栈
属于内核态的ring 0态的这个栈
好 我们说产生中断之后你一定是说
它会用到我们ring 0的栈
那我们现在要考虑一下
我们怎么能够从一个特权级
跳到另外一个特权级里面去
那我们开始完成lab1的时候
其实已经注意到我们都一直
在内核态里面来执行代码
我们一个challenge的一个练习
想让大家做一个不同特权级的切换
那其实就是在这里面有所体现
你怎么能实现
从一个一开始处于内核态ring 0
这个特权级很高的ring 0这个态
往应用程序去转换
怎么跳到用户态去让应用程序去执行
那么这个就在于你怎么去构造一个
所谓的一个特殊的栈来完成这个工作
我们可以看看
这是我们说的内核栈
那么一旦产生一个中断之后
它其实会有一些压栈
那这些栈的信息需要注意
因为你当前正在内核里面去执行
所以在内核里面处于ring 0这个态的时候
产生的中断还是ring 0
它的特权级没有发生变化
所以它会把这个
EFLAGS CS EIP和Error Code存进去
好我们现在希望的是让它回到什么地方
回到ring 3里面
就回到应用程序里面去执行
那么首先构造一个环境
让它能回到ring 3
那我们就模仿了一个
就是ring 3产生中断的时候的一个现场
就是产生终端的这个现场
跟同特权级的比起来
如果从ring 3产生的中断
跳掉ring 0里面去之后
它会多存一些信息
我们前面讲到了会存两个SS和ESP
而且SS里面那个RPL是3需要注意
这个RPL是3也意味着
意味着什么呢
意味着这个SS其实是在用的那个数据段
用的是用户态的数据段
而不是内核态的数据段 所以它RPL是3
那同理我们前面在ring 0里面
产生的中断它这个CPL是0
代表了它当前运行在内核里面
这个时候我们希望它回去
能够跳到ring 3去执行
所以我们会把它的CS
会改成3 改成3之后
也意味着如果说把这个栈构造好之后
我们最后通过一条特殊的指令
通过这条指令叫做什么
IRET这条指令可以完成这个转换
OK 一旦IRET指令执行之后
它就会把这些信息给弹出栈
来把这个内容恢复到对应的寄存器里面
包含了EIP CS EFLAGS ESP和SS
那一旦把这些值附给了
我们那些寄存器之后
可以看到这时候它的运行环境
已经变成了用户态 为什么这么说
因为它访问的数据 在SS这个访问数据
它访问的代码CS 都是3
所以说这时候
如果我们这个跳过去的应用程序
想去访问处于一个内核态的一个数据的话
会报错 OK 这也意味着我们这个SS和
内核里面的这个SS的栈不是一个数据段的
应该是不同的数据段
这实际上是一种特权级的转换
你可以看到当这个发生转换之后
这时还是内核栈它把这信息弹出来了
同时需要注意同时新的一个栈到这儿来
新的一个栈已经形成
这个栈实际上是我们说处于用户态的一个栈
它的SS和ESP已经变到这儿来了
而且它的代码也是变到CS和EIP指向代码
如果这个CS EIP不做改变
那么它还是会直到
它产生中断的下一条指令去执行
好 那我们前面讲的是从ring 0
跳到ring 3怎么来完成
其实通过构造一个
能够返回到ring 3的一个栈
内核的一个模拟的一个中断栈
来完成一个执行过程
那么它最后通过IRET指令
就可以完成数据的更新
这一寄存器的更新
来从而实现特权级的一个转换
那另外一点就是怎么能够
再从用户态跳到内核态去执行
那这也是我们需要考虑的问题
实际上这种机制也是被我们的应用程序
无论是Windows Linux等等
通常的这些操作系统
它的应用程序都采取这种方式
通过所谓的一个软中断或者叫trap
来完成从用户态到内核态的转变
那么这里面我们看看如果是基于X86架构
怎么来完成从ring 3到ring 0的一个转变
这也是一样
这是一个处于正在执行的
一个程序的堆栈信息
那么一旦产生了一个中断
那它实际上就已经实现了跳转
那为了能够实现跳转
你首先要把这个中断门给建立好
就是IDT里面 要把中断门建立好
中断门有一个中断描述符
它会指出来产生了某一个中断之后
它应该跳到哪儿去执行 但是在那个软件
中断服务例程
操作系统里面中断服务例程执行之前
我们CPU已经一旦产生中断
一定要保存一些信息 这些信息是什么
刚才说到的就是SS ESP EFLAGS CS和EIP
当一个应用程序运行到用户态的时候
产生了中断 那我们的硬件
会在处于ring 0态的
那个栈里面会存这些信息
这些信息其实是被打断那一刻
那个应用程序它的堆栈
它的执行的地址都保存在这儿
这个信息有了 如果处理完这个
中断服务例程之后
我们再执行IRET这条指令
就是中断返回指令
它就会根据这里面的信息
再重新返回到ring 3里面去
那我们这时候不希望它返回到ring 3
我们希望它返回到ring 0怎么办
大家想想 其实和我们刚才的方法一样的
我们可以通过对这个堆栈的修改
就是内核态的修改
使得它执行完IRET之后
就是IRET这条指令之后
可以继续留在内核态里面去执行
我们做了什么修改
首先就需要注意这个SS和ESP不需要了
因为我们不需要回到用户态去 这是一个
第二个我们会把CS会做一个修改
指向内核态的那个代码段 这是一个
第二个要把CS的CPL设置为0
这里面也实际上是明确了
内核态的那个代码段的CPL就是0
通过把这个内核里面的堆栈
改成这么一个堆栈之后 OK
当然你要根据你要执行
具体要访问到哪个地方
去改变所谓的EIP
假设如果不改的话
那么这时候再去通过
执行退出IRET这条指令
中断返回指令 那么我们CPU硬件会
把这个里面的堆栈信息给取出来
然后返回到EIP和CS
指向的这个地址里面去执行
但需要注意在这时候
依然是在ring 0里面执行的
通过这种方式可以实现从ring 3
到ring 0的一个转变
那么如果大家能够把这个方法
用在我们lab1的challenge
这个练习里面的话就可以完成那个实验
那如果有兴趣的同学也可以做尝试
我们再想一想当一个应用程序
通过不同手段实现从一个特权级到
另外一个特权级的一个切换之后
那到了另外一个特权级
它怎么知道那个特权级里面的
CS SS位于什么地方 这是一个问题
CS有同学说 它的CS和EIP就表明它的地址
这个地址我们可以通过它那个
IDT描述符表里面建好了
中断描述符表建好了
当产生某一个中断
应该跳到什么地方去执行
这个地址可以搞定
但是另一方面
它的这个堆栈应该在哪儿
这个信息在哪儿
那这个信息其实是在IDT里面是没有的
那么是在另外一个
我们称之为任务状态段
Task State Segment
这么一个段保存了不同特权级的
它所用到的很多寄存器里面的信息
比如我们说的SS ESP等等
那么虽然有很多 它很全面
其实我们关注什么
关注的是不同特权级里面的那个堆栈信息
就是它的SS和ESP 那么可以看出来
对于不同特权级的SS ESP
这里面都有保存
你看ESP0 ESP1 ESP2
这是保存的 还有SS0 SS1 SS2等等
这三个代表了它保存了什么
保存了从ring 0到ring 2
这三个特权级里面的堆栈信息
所以为什么刚才说当一个应用程序
在ring 3这个应用程序产生了一个中断
或者说执行一个软中断的指令 INT
这个INT指令之后 它可以跳到内核里面去执行
但跳是跳过来了 它跳过来之后
我们的CPU会根据TSS这里面存的这个信息
来设置新的堆栈 这是一个
然后再根据我们IDT表里面的那个
中断描述符表的信息来设置新的地址
所以说我们可以看到这里面比较清楚
一旦从一个特权级跳到另外一个特权级
特别是从高特权级ring 3跳到ring 0的时候
我们的地址会发生变化
我们的堆栈会发生变化
而这个地址和堆栈都分别由
不同的硬件机构来保存
所以说这里面我们需要也要去设置好
我们操作系统需要去设置好
TSS里面的这些内容
那么TSS它其实是一个特殊的段
这个段位于内存中
那它到底怎么去访问得到
那运用到我们前面讲到的全局描述符表
我们的全局描述符表里面
有专门有一项是专门来指向所谓的TSS
就是任务状态段这么一个信息
称之为有一个特定的一个TSS的一个描述符
放在我们全局描述符表里面
那我们可以看到在这个全局描述符表里面
它保存了这个TSS的一个描述符
TSS Descriptor
在这个描述符里面很重要的一个
它保存了它的地址 就是TSS这个地址
那么根据这里面保存的地址
我们可以找到TSS里面的这个内容
这就是说我们CPU怎么能够去找到
当产生一个中断
从一个特权级跳到另一个特权级
找到另一个特权级里面的那些堆栈信息
甚至还有其它信息 但这里面对ucore而言
我们就用最简单的两个
就是它的堆栈SS和ESP
很明显那CPU我们硬件这么去找
那么这里面的内容需要我们的软件去填写
这也是跟前面介绍是一样
我们的软件会去填写我们段描述符表
就是全局描述符表
我们软件会去填写IDT 中断描述符表
同样我们的软件会把这个任务状态段
里面的内容给填好
那么一旦我们的TSS里面的内容
这个段里面的内容填好之后
我们需要我们的CPU
知道这个段位于什么地方
以便后续一旦产生切换之后
它到哪儿去找这个信息
对应这个比如SS ESP
这个内容到哪儿去找
那首先要知道这个段的基址
这个段的起始地址在哪儿保存
我们前面已经讲到了
在全局描述符表里面
你要去填写一项TSS的一个描述符
它会指向这个任务段的相应的内容
但是也需要注意这里面保存的信息
这也是内存里面的单元
所以说我们的硬件还有一个优化
它会有一个专门的Task Register
来缓存这个TSS里面的内容
从而使得我们每次
要CPU访问这个段里面的
Task State Segment这个段里面的内容的时候
它要去根据这个寄存器里面的内容
直接找到这个相应的位置
那么这个寄存器是一个特殊的寄存器
那我们会在完成对TSS的初始化之后
通过这个寄存器来加载它的基址
这两步都可以有效的找到我们这个
Task State Segment
那我们前面讲的是
大致的一个对硬件的介绍
那其实我们ucore在操作系统里面
也对此有相应的一些初始化的工作都涉及在
分配一个TSS memory实际上分配一块内存
要在这个内存里面
把它作为一个Task State Segment
来完成内容的填写
再做初始化 对内存做初始化
特别要设置对我们的SS0和ESP0
再进一步会在GDT里面去填写TSS描述符
最后还要设置TSS selector
这是我们说Task Register相应的设置工作
这里面在代码里面有对应的环节
接下来我们可以看一下代码怎么来表述的
好 那前面就把关于特权级的
给大家做了一个介绍
相关的一些进一步的信息
可以看很详细的
有关intel架构的一个技术文档
它有专门的一个软件开发者的文档
会有更详细的信息 这里面列出来了
大致的一个位置
小节一下我们这里面重点讲了特权级
intelX86 X86架构里面有几个特权级
我们怎么来完成特权级的一个切换
你怎么知道你到底处于哪个特权级
怎么去判断你的这个访问
是否违背了特权级的一个规则
以及最后还讲到了怎么去建立好
从不同特权级转换的一个相应的机制
特别是涉及到TSS这么一个机制
把TSS设置好之后才能更好的完成
从一个特权级到另一个特权级的转换
好 这是这一部分的内容