linux 线性地址到物理地址的转换,逻辑地址、线性地址、物理地址

一、基本概念

1)物理地址(physical address)     用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。

2)逻辑地址(logical address)     Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指

定一个操作数或者是一条指令的地址。

3)线性地址(linear address)

总的来说,CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:

首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线性地

址,  再利用其页式内存管理单元,转换为最终物理地址。

二、逻辑地址—>线性地址—段式内存管理

一个逻辑地址由两部份组成:段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。

424898bf613f4ff48206b5525595a286.png

索引号,即段描述符表索引。段选择符的前13位,通过索引可以在段描述符表中找到一个具体的段描述符,这个描述符描述了某个段的相关信息:

350ae38bb9d06e6b7946ad326cd478bc.png

Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的:

(1)为0,表示用GDT

(2)为1,表示用LDT

GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

5aa8eea6237e3a46283e68189e837b78.png

给定一个完整的逻辑地址[段选择符:段内偏移地址]

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的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元

22b9d893a48ee64523ec8442b7fae891.png

1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。

2、每一个活动的进程,因为都有其独立的对应的虚似内存,那么它也对应了一个独立的页目录地址。

3、每一个32位的线性地址被划分为三部分,页目录索引(10位):页表索引(10位):偏移(12位)

依据以下步骤进行转换:

1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);

2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。真正

的页的地址被放到页表中去了。

3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;

4、将页的起始地址与线性地址中最后12位相加,得到最终的物理地址;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值