保护

前面讲过,保护模式的保护,是指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级页表。