内存地址
- 逻辑地址:段+偏移量
- 线性地址:也称为虚拟地址,32位
- 物理地址:对真正存在的内存条进行寻址的地址
- 内存管理单元MMU:
- 分段单元:将逻辑地址转换成线性地址
- 分页单元:将线性地址转换成物理地址
硬件中的分段
段选择符和段寄存器
- 段选择符,16位字段
- 索引号
- 表指示器(GDT还是LDT)
- 请求者特权级RPL
- 段寄存器
- 目的:存放段选择符
- 常用段寄存器
- CS,codeSegment:代码段寄存器,指向程序指令的段
- SS,stackSegment:栈段寄存器,指向包含当前程序栈的段
- DS,dataStack:数据段寄存器,指向包含静态数据或全局数据的段
- PS:CS低二位指明CPU当前特权级CPL
段描述符
描述段的特征,8字节,放在全局描述符表GDT或局部描述符表LDT
- GDT地址放在gdtr寄存器,LDT地址放在ldtr寄存器
- 字段
- Base:段首字节线性地址
- Limit:最大偏移量
- DPL:描述符特权级
- S:系统标志,表明是系统段(GDT、LDT)还是普通段(数据段、代码段)
- Type:描述段的类型特征和存取权限
- P:表示是否在内存中,Linux不用
- D或B:D还是B取决于是代码段还是数据段
- AVL:Linux不用
分段单元
- 检查段选择符TI字段,确定段选择符在GDT还是LDT
- 从段选择符的index字段计算段描述符在表中的偏移
- 从ldtr寄存器或gdtr寄存器获取段描述符表的基址
- 根据2、3找到对应段描述符,Base字段+逻辑地址的偏移量就能得到线性地址
Liunx中的分段
四个重要的Linux段
- 用户代码段:段选择符宏__USER_CS
- 用户数据段:段选择符宏__USER_DS
- 内核代码段:段选择符宏__KERNEL_CS
- 内核数据段:段选择符宏__KERNEL_DS
- 四个段描述符的Base和Limit都一样,由CS判断CPL
GDT
每个CPU对应一个GDT,副本基本都一样
- 内容
- 用户态和内核态下的代码段和数据段
- TSS
- LDT
LDT
大多数用户态下的Linux程序不使用LDT,因此GDT中的LDT是默认的
硬件中的分页
- 线性地址被分成以固定长度为单位的组,称为页
- 分页单元把所有物理内存分程固定长的的页框
- 把线性地址映射到物理地址的数据结构称为页表
常规分页
线性地址被分为3个域,10位目录,10位页表,12位偏移量,4KB一页
- 二级页表作用:不需要全部页表都放入内存,根据页目录把实际需要的页表放入内存
- 页目录放在cr3页目录基址寄存器
- 缺页时把线性地址放在cr2页故障地址寄存器
- 页目录项和页表项的几个标志
- present:是否在内存中
- dirty:是否被写操作
- read/write:含有页或页表的存取权限
- pageSize:只应用于页目录项,和下一节扩展分页有关
扩展分页
允许页框大小为4M而不是4KB
- 这种情况下,不需要中间10位页表进行地址转换
- 作用:一页太小不好用,避免需要使用大段连续地址时需要过多页表项,占用内存
TLB
用于加快线性地址的转换。每个CPU都有自己的TLB
- 当cr3寄存器被修改时(进程切换),硬件会自动使TLB的所有项都无效
- 更新时机:当一个线性地址被第一次使用时,就加入TLB表项
Linux中的分页
高地址128MB的线性地址总是留作它用,因为内核使用这些线性地址实现非连续内存分配和固定映射的线性地址