逻辑地址、线性地址、物理地址

一、基本概念

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位相加,得到最终的物理地址;

原文地址:https://www.cnblogs.com/X-W-LIU/p/3455734.html