一、基本概念
1)物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
2)逻辑地址(logical address) Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指
定一个操作数或者是一条指令的地址。
3)线性地址(linear address)
总的来说,CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:
首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线性地
址, 再利用其页式内存管理单元,转换为最终物理地址。
二、逻辑地址—>线性地址—段式内存管理
一个逻辑地址由两部份组成:段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。
索引号,即段描述符表索引。段选择符的前13位,通过索引可以在段描述符表中找到一个具体的段描述符,这个描述符描述了某个段的相关信息:
Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的:
(1)为0,表示用GDT
(2)为1,表示用LDT
GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。
给定一个完整的逻辑地址[段选择符:段内偏移地址]
1>看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
2>拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
3>把Base + offset,就是要转换的线性地址了。
三、Linux段式管理
include/asm-i386/segment.h
#define GDT_ENTRY_DEFAULT_USER_CS 14
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
把其中的宏替换成数值,则为:
1 #define __USER_CS 115 [00000000 1110 0 11]
2
3 #define __USER_DS 123 [00000000 1111 0 11]
4
5 #define __KERNEL_CS 96 [00000000 1100 0 00]
6
7 #define __KERNEL_DS 104 [00000000 1101 0 00]
方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了
__USER_CS index= 14 T1=0
__USER_DS index= 15 T1=0
__KERNEL_CS index= 12 T1=0
__KERNEL_DS index= 13 T1=0
T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15项(arch/i386/head.S):
.quad 0x00cf9a000000ffff /* 0x60 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x68 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x73 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x7b user 4GB data at 0x00000000 */
根据段描述符各字段展开,发现16-31位全为0,即四个段的基地址全为0。
于是,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移==>为线性地址,可以得出重要的结论:
在Linux下,逻辑地址与线性地址总是一致的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。
四、CPU的页式内存管理
CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。
从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。
另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元
1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。
2、每一个活动的进程,因为都有其独立的对应的虚似内存,那么它也对应了一个独立的页目录地址。
3、每一个32位的线性地址被划分为三部分,页目录索引(10位):页表索引(10位):偏移(12位)
依据以下步骤进行转换:
1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。真正
的页的地址被放到页表中去了。
3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
4、将页的起始地址与线性地址中最后12位相加,得到最终的物理地址;