保护模式补充
保护
前面讲过,保护模式的保护,是指CPU在进行内存跳转、访问时,增加了很多检查,确保内存访问安全。在分段和分页机制中,我们也看到了,段选择子,段描述符等都有很多的权限相关的标记位,CPU就是根据这些标记位及一系列访问规则判断是否有权限访问的,即进行内存保护。
数据段&代码段
段描述符中的S位(44位),表明段描述符的类型:
- S=0, 系统段/门描述符
- S=1, 数据段/代码段
段描述符中的TYPE位(40~43位),表明段描述符的类型。一个段可能为:
TYPE值 | 数据段/代码段 | 系统段/门描述符 |
---|---|---|
0 | 只读 | 未定义 |
1 | 只读,已访问 | 可用286 TSS |
2 | 读/写 | LDT |
3 | 读/写,已访问 | 忙的286 TSS |
4 | 只读,向下扩展 | 286 调用门 |
5 | 只读,向下扩展,已访问 | 任务门 |
6 | 读/写,向下扩展 | 286 中断门 |
7 | 读/写,向下扩展,已访问 | 286 陷阱门 |
8 | 只执行 | 未定义 |
9 | 只执行、已访问 | 可用386 TSS |
A | 执行/读 | 未定义 |
B | 执行/读、已访问 | 忙的386 TSS |
C | 只执行、一致代码 | 386 调用门 |
D | 只执行、一致代码、已访问 | 未定义 |
E | 执行/读、一致代码 | 286 中断门 |
F | 执行/读、一致代码、已访问 | 386 陷阱门 |
从这么多种类型定义中,可以预期整个检查规则很复杂(@_@)。其中门描述符(gate)用于中断发生时的检查,CPU确认特权级等设定符合约束后,才能进入相应的处理程序。
特权级
x86 CPU定义了4种特权级,越靠近中心,级别越高。
实际中,linux只使用了ring0和ring3:ring0对应内核态,ring3对应用户态。
特权检查
- CPL: 处理器当前特权级
- DPL: 段或门的特权级,段描述符或门描述符的(45~46位)DPL字段
- RPL: 请求特权级,选择子的(0~1位)RPL字段
特权检查时对这3个特权级,依据规则检查。检查通过则访问成功。
数据段检查规则
(CPL <= 目标数据段DPL) && (RPL <= 目标数据段DPL)
即: 高特权级指令访问低特权级数据
代码段检查规则
- 无门结构且目标为非一致代码段:CPL = RPL = 目标代码段DPL
- 无门结构且目标为一致代码段:(CPL >= 目标数据段DPL) && (RPL >= 目标数据段DPL)
- 有门结构:(DPL_GATE >= CPL >= DPL_CODE) && (RPL <= DPL_GATE)(从低特权级跳到高特权级需要通过门)
总结:数据只能高访问低;代码只能低跳转到高;从高访问低只有返回指令可以;
整个保护模式的规则很复杂,又会涉及到很多概念和细节,放弃吧~
地址转换
逻辑地址->线性地址->物理地址的转换,通过CPU约定的分段规则及分页规则完成。可以想象,所有的操作都会涉及内存寻址,这些转换必然非常频繁的进行。CPU为了高效处理,设计了专门的硬件模块(MMU,内存管理单元)来完成内存地址转换。
linux的分段
根据Intel CPU的设计,理论上我们可以将内存划分出很多个段。但Linux基于可移植性等因素考虑(一些RISC CPU不支持分段),并没有充分利用x86 CPU提供的分段机制,而是将整个4GB的线性地址空间都对应为同一个段。这样,事实上逻辑地址(段内偏移量)与线性地址相同。
由于x86 CPU段机制规定,代码段与数据段不同;而且linux内核态对应ring0,用户态对应ring3。所有linux需要分别创建内核数据段,内核代码段;用户数据段,用户代码段。
linux的分页
现在的linux内核中,为了同时支持32位与64位系统,linux分页也不是采用2级页表,而是4级页表。