使用80x86微处理器时需要区分一下三种不同的地址:
逻辑地址:包含在机器语言指令中用来指定一个操作数或一条指令的地址,每一个逻辑地址都由一个段标识符或叫段选择符(16位)和偏移量(32位)组成
线性地址:又称作虚拟地址,是一个u32的整数,可以用来表示高达4GB的地址
物理地址:是一个u32或u64的整数,与从微处理器的地址引脚发送到内存总线上的电信号相对应
MMU(内存管理单元)通过分段单元将逻辑地址转换为线性地址,之后分页单元将线性地址转换为物理地址。
注:分段单元与分页单元都是MMU的硬件电路。 分段单元 分页单元 逻辑地址 线性地址 物理地址 二、硬件中的分段 2.1 段选择符一个段选择符(Segment Selector)有16位,如下表所示:
字段名位域描述RPL0:2请求者特权级,0表示内核态,3表示用户态TI2:3指明段描述符存放的位置,为0在GDT中,1在LDT中index3:15指定存放在GDT/LDT中的段描述符(64位)入口2.2 段寄存器为了方便找到段选择符,cpu提供了段寄存器,段寄存器的唯一目的就是存放段选择符。段寄存器包括CS、SS、DS、ES、FS、GS。虽然只有6个段寄存器,但是程序可以把同一个段寄存器用作不同的目的(先将其值保存在内存中,用完之后再恢复)。
其中三个段寄存器有特殊用途:
段寄存器描述CS代码段寄存器,指向包含程序指令的段SS栈段寄存器,指向包含当前程序栈的段、DS数据段寄存器,指向包含静态数据或者全局数据段其他三个段寄存器用作一般用途,可以指向任意数据段。
CS寄存器包含一个两位字段,用来表示CPU当前特权级(Current Privilege Level,CPL)。0表示内核态,3表示用户态。
2.3 段描述符每个段由一个64位(8byte)的段描述符(Segment Descriptor)表示,其描述了段的特征。段描述符存放在GDT(Global Descriptor Table,全局描述表)或LDT(Local Descriptor Table,局部描述表)中,是其中的一个表项数据结构。
段描述符中各个字段的含义如下所示:
字段名描述Base32位段线性基地址G粒度标志,0段大小以字节为单位,否则以4KB倍数计Limit20位段界限偏移量,决定段的长度,若G=0,段大小在1B1MB,G=1,段大小在4KB4GBS系统标志,0表示系统段,1表示普通代码段或数据段Type描述段类型特征及存取权限DPL描述符特权级(Descriptor Privilege Level),0表示内核态可访问,3表示内核态与用户态都可访问P0表示段当前不在主存中,Linux总设置为1,表示不把段交换到磁盘D/B取决于代码段还是数据段,如果段偏移地址32位设置为1,若为16位置0AVLLinux忽略2.4 快速访问段描述符为了加速逻辑地址到线性地址(虚拟地址)的转换,80x86提供非可编程寄存器,供6个段寄存器使用,每个非可编程寄存器中包含一个64位段描述符,由相应段寄存器的16位段选择符指定,每当一个段选择符装入段寄存器中时,相应的64位段描述符由内存装入CPU非可编程寄存器,之后,只要段寄存器中的内容不变,则线性地址转换中不再访问GDT或LDT中的64位段描述符。
2.5 分段单元逻辑地址到线性地址的转换过程中分段单元执行以下操作:
先检查段选择符中TI字段决定从GDT还是LDT中获取段描述符。后获得从gdtr或ldtr中获得GDT或LDT的线性基地址。
将段选择符index字段*8,表示段描述符在GDT/LDT中的线性偏移,加上GDT/LDT线性基址,获得段描述符。
从段描述符中的Base获得段线性基址,加上偏移量得到线性地址。
三、Linux中的分段运行在用户态的所有Linux进程都使用一对相同的用户代码段和用户数据段来对指令和数据寻址。同理,运行在内核态的进程都使用相同的内核代码段与内核数据段对指令和数据进行寻址。
四个主要的Linux段描述符字段值:
段BaseGLimitSTypeDPLD/BP用户代码段0x0000000010xffffff110311用户数据段0x0000000010xffffff12311内核代码段0x0000000010xffffff110011内核数据段0x0000000010xffffff12011相应的段选择勤劳的大神__USER_CS、__USER_DS、__KERNEL_CS和__KERNEL_DS分别定义。如,对内核代码段寻址,内核只需将__KERNEL_CS宏产生的值装入cs段寄存器中即可。
注意,上述四个段的线性基址都为0,且G=1,代表段大小以4K(即 2 12 2^{12} 212)为单位,Limit字段为0xfffff(即 2 20 2^{20} 220),因此寻址限长为( 2 20 2^{20} 220 * 2 12 2^{12} 212 - 1 = 2 32 − 1 = 4 G B − 1 ) 2^{32} - 1 = 4GB - 1) 232−1=4GB−1),在用户态或内核态的所有进程使用相同的逻辑地址。
由于所有段都从0x00000000开始,可以得出一个重要结论,Linux下逻辑地址与线性地址一致。
3.1 Linux GDT单处理器中只有一个GDT,多处理器中每个CPU有一个GDT。所有的GDT都存放在cpu_gdt_table数组中,所有的GDT地址及大小存放在cpu_gdt_descr数组中,这些符号都在arch/i386/kernel/head.S中定义。每个GDT包含18个段描述符及14个预留项。其中的18个段描述符指向下列段:
用户及内核代码与数据段(4个)
任务状态段(TSS),每个处理器有一个
3个局部线程存储段(Thread-Local Storage,TLS)
与高级电源管理(AMP)相关的3个段
与支持即插即用(PnP)功能的BIOS服务程序相关的5个段
被内核用来处理“双重错误”异常的特殊TSS段
一个包括缺省局部描述符表的段
文章仅是自己看书后对内存的理解与记录,有问题欢迎指出。