---- 整理自狄泰软件唐佐林老师课程
文章目录
- 1. 初识任务状态段(TSS,Task State Segment)
- 1.1 TSS中不同特权级的栈信息
- 1.2 特权级转移时的栈变化
- 1.3 问题一
- 1.4 目标实验(操作系统雏形)
- 1.5 编程实验(特权级转移综合实验)
- 1.6 问题二
- 1.6.1 RPL的意义
- 1.6.2 引出问题
- 1.7 小结
- 2. 数据段的访问规则(数据段没有可执行属性)
- 2.1 访问示例
- 2.2 编程实验
- 2.3 实验结论
- 3. 代码段的规则
- 3.1 代码段的分类
- 3.2 代码段之间的跳转规则(不借助门描述符)
- 3.3 编程实验:代码段的直接跳转规则
- 3.3.1 非一致性代码段跳转
- 3.3.2 一致性代码段的跳转
- 3.4 实验结论
- 4. 深入理解调用门
- 4.1 关于调用门的注意事项
- 4.2 编程实验:深入理解调用门
- 4.3 小结
1. 初识任务状态段(TSS,Task State Segment)
- 处理器所提供的 硬件数据结构,用于实现多任务解决方案
- TSS 中保存了关键寄存器的值以及不同特权级使用的栈
- 段寄存器
- 通用寄存器
- 不同特权级的栈信息
1.1 TSS中不同特权级的栈信息
- 在TSS中只保存了3个栈信息
- 特权级0:ss0、esp0
- 特权级1:ss1、esp1
- 特权级2:ss2、esp2
1.2 特权级转移时的栈变化
- 特权级在变化时,需要用到不同特权级下的栈,当处理器进入不同的特权级时,它自动在TSS中找同特权级的栈。
- 低特权级 ==> 高特权级(调用门)
- 从 TSS 获取高特权级目标栈段
- 将低特权级栈信息压入高特权级栈中(ss和esp)
- 高特权级 ==> 低特权级(retf)
- 将低特权级栈信息从高特权级栈中取出并恢复到ss和esp
引用自《操作系统真象还原》:
对于第1种——特权级由低变高的情况,由于不知道目标特权级对应的栈地址在哪里,所以要提前将目标栈的地址记录在某个地方,当处理器向高特权级转移时再从中取出来加载到SS和esp中以更新栈,这个保存的地方就是TSS,处理器会自动地从TSS中找到对应的高特权级栈地址。
也就是说,除了调用返回外,处理器只能由低特权级向高特权级转移,TSS中所记录的栈是转移后的高特权级目标栈,所以它一定比当前使用的栈特权级要高,只用于向更高特权级转移时提供相应特权的栈地址。进一步说,TSS中不需要记录3特权级的栈,因为3特权级是最低的,没有更低的特权级会向它转移。
不是每个任务都有4个栈,一个任务可拥有的栈的数量取决于当前特权级是否还有进一步提高的可能,即取决于它最低的特权级别。比如3特权级的程序,它是最低的特权级,还能提升三级,所以可额外拥有2、1、0特权级栈,用于将特权分别转移到2、1、0级时使用。2特权级的程序,还能提升两级,所以可额外拥有1、0特权级栈,用于将特权分别转移到1、0级时使用。以此类推,1特权级的程序,它可以额外拥有0特权级栈,0特权级已经是至高无上了,只有这一个0级栈。以上所说的低特权级转向高特权级的过程称为“向内层转移”,想想4个特权级划分的同心圆就知道了,高特权级位于里面。
对于第2种——由高特权级返回到低特权级的情况,处理器是不需要在TSS中去寻找低特权级目标栈的。其中一个原因我猜您已经猜到了:TSS中只记录2、1、0特权级的栈,假如是从2特权级返回到3特权级,上哪去找3特权级的栈?另一方面的原因是低特权级栈的地址其实已经存在了,这是由处理器的向高特权级转移指令(call等)实现的机制决定的,换句话说,处理器知道从哪里找低特权级的目标栈。
由于特权级向低转移后,处理器特权级有了变化,同样也需要将当前栈更新为低特权级的栈,它如何找到对应的低特权级栈呢?正常情况下,特权级由低向高转移在先,由高向低返回在后,即只有先向更高特权级转移,才能谈得上再从高特权级回到低特权级,否则没有“去”就谈不上“回”(宁可被骂啰嗦,我也要说清楚)。当处理器由低向高特权级转移时,它自动地把当时低特权级的栈地址(SS和ESP)压入了转移后的高特权级所在的栈中(随着以后深入学习大家会明白这一点),所以,当用返回指令如retf或iret从高特权级向低特权级返回时,处理器可以从当前使用的高特权级的栈中获取低特权级的栈段选择子及偏移量。由高特权级返回低特权级的过程称为“向外层转移”。
1.3 问题一
- TSS中为什么 只保存3个特权级(0、1、2) 的栈信息?
- 低特权级到高特权级会用,没有会升高到3的特权级。
1.4 目标实验(操作系统雏形)
低特权级 - 高特权级( 特权级转移 实验)
- 定义32位核心代码段和数据段(Privilege = 0):模拟内核态
- 定义32位任务代码段和数据段(Privilege = 3):模拟用户态
- 由核心代码段跳转到任务代码段执行(高 ==> 低,即 0 ==> 3,retf):模拟内核到应用程序
- 在任务代码段中调用高特权级代码段打印字符串(Call Gate):模拟应用程序陷入内核态
- 注意事项
- 特权级转移时会发生栈的变化(如何变化?)
- 栈的变化需要在TSS结构体中预先定义
- TSS结构体作为一个独立段定义(描述符,选择子)
- 在核心代码段中加载具体的TSS结构体(
ltr TSSSelector
)
1.5 编程实验(特权级转移综合实验)
【参看链接】:18-19-20 - 深入特权级转移 / 18 / 00
- 从 0 到 3:
- 模拟应用程序调用系统函数
放到显存段的时候,内核数据有可能会被破坏。
- 修改gs显存段的特权级为3,正确代码如下:
【参看链接】:18-19-20 - 深入特权级转移 / 18 / 01
1.6 问题二
- RPL究竟是什么?
- 有什么用?
1.6.1 RPL的意义
- 位置意义
- 选择子或段寄存器的最低2位
- 请求意义
- 资源请求的特权级 (不同于当前特权级CPL)
- 当需要请求获取某种资源时,处理器通过 CPL、RPL和DPL构成的一定的规则 来共同确定请求是否合法!
1.6.2 引出问题
- 处理器通过什么规则判断 资源请求 或 代码跳转 是否合法?
1.7 小结
- TSS是通过调用门转移到高特权级执行的关键
- TSS是处理器的硬件数据结构,用于实现多任务
- TSS结构遵循保护模式下内存使用的规则
- RPL在请求资源时是合法性判断的依据之一
- 处理器使用CPL、RPL和DPL共同确定合法性
2. 数据段的访问规则(数据段没有可执行属性)
- 访问者权限(CPL)高于等于 数据段权限(DPL)(数值上:CPL <= DPL)
- 请求特权级(RPL)高于等于 数据段权限(DPL)(数值上:RPL <= DPL)
2.1 访问示例
- CPL = 2,RPL = 1,DPL = 3,访问是否合法?
- 合法
- CPL = 0,RPL = 3,DPL = 2,访问是否合法?
- 不合法
- CPL = 0,RPL = 1,DPL = 2,访问是否合法?
- 合法
2.2 编程实验
【参看链接】:18-19-20 - 深入特权级转移 / 19 / 00
2.3 实验结论
- 选择子被段寄存器加载时,会进行保护模式的检查
- 检查选择子的下标是否合法(段描述符的合法性)
- 检查特权级是否合法(CPL和RPL的特权级要要高于数据段的特权级DPL)
- 检查特权级时CPL和RPL之间是不比较的
3. 代码段的规则
3.1 代码段的分类
- 系统段(S = 0)
- LDT,TSS,各种门结构
- 非系统段(S = 1)
- 一致性代码段:X = 1,C = 1
- 非一致性代码段:X = 1,C = 0
3.2 代码段之间的跳转规则(不借助门描述符)
- 非一致性代码段
- 代码段之间只能平级转移(数值上:CPL == DPL,RPL <= DPL)
- 一致性代码段
- 支持低特权级代码段向高特权级代码段的转移(数值上:CPL >= DPL)
- 虽然可以成功转移高特权级代码段,但是当前特权级CPL不变
注意:
- 数据段只有一种,没有一致性和非一致性的区分
- 并且,数据段不允许被低特权级的代码段访问
3.3 编程实验:代码段的直接跳转规则
3.3.1 非一致性代码段跳转
- 代码段之间只能平级转移(数值上:CPL == DPL,RPL <= DPL)
【参看链接】:18-19-20 - 深入特权级转移 / 19 / 01
注:自己的理解:从code32跳到function执行,那么资源请求的特权级应该要高于等于之前的code32特权级。所以数值上function的RPL应该要小于等于code32的DPL。
3.3.2 一致性代码段的跳转
- 支持低特权级代码段向高特权级代码段的转移(数值上:CPL >= DPL)
【参看链接】:18-19-20 - 深入特权级转移 / 19 / 02
- 虽然可以成功转移高特权级代码段,但是当前特权级不变
【参看链接】:18-19-20 - 深入特权级转移 / 19 / 03
注:CPL没有发生改变,正是由于当前的特权级没有发生改变,栈没有发生改变,所以可以在NEW_SEGMENT中无缝调用PrintFunc。
3.4 实验结论
- 特权级降低转移时,retf 指令会触发栈段的特权级检查
- 一致性代码段可直接跳转到其它同级非一致性代码段执行:
- 一致性代码段和非一致性代码段中的代码没有本质区别,这两种代码段仅仅是跳转时使用的合法性判断规则不同,因此,一致性代码段到非一致性代码段的直接同级跳转是合法的。
- 小技巧
- 大多数情况下,选择子中的RPL和对应段描述符中的DPL可设置为相同值。
4. 深入理解调用门
- 调用门用于向高特权级的代码段转移(数值上:CPL >= DPL_object)
- 调用门描述符的特权级低于当前特权级(数值上:CPL <= DPL_gate)
4.1 关于调用门的注意事项
- 一:
- 调用门支持特权级同级转移
- 调用门同级转移被处理为普通函数调用或直接跳转
- call通过调用门能提升特权级,jmp通过调用门只能同级转移
- 二:
- 通过调用门降特权级返回(retf)时:
- 对目标代码段以及栈段进行特权级检查
- 对相关段寄存器强制清零(指向高特权级数据的段寄存器)
- 通过调用门降特权级返回(retf)时:
4.2 编程实验:深入理解调用门
【参看链接】:18-19-20 - 深入特权级转移 / 20
思考:
- 特权级由高至低返回后,为什么要把指向高特权级数据的段寄存器清零?
- 因为内核安全。
4.3 小结
- 调用门的使用(数值上):DPL_object <= CPL <= DPL_gate
- 调用门支持同级跳转(jmp指令只能同级转移)
- retf对栈的检查:CS.RPL == SS.RPL == SS.DPL
- 特权级降低转移时,相关段寄存器的值将被清零