逻辑地址线性地址物理地址和虚拟地址理解.docx

上传人:b****5 文档编号:6691261 上传时间:2023-01-09 格式:DOCX 页数:8 大小:116.19KB
下载 相关 举报
逻辑地址线性地址物理地址和虚拟地址理解.docx_第1页
第1页 / 共8页
逻辑地址线性地址物理地址和虚拟地址理解.docx_第2页
第2页 / 共8页
逻辑地址线性地址物理地址和虚拟地址理解.docx_第3页
第3页 / 共8页
逻辑地址线性地址物理地址和虚拟地址理解.docx_第4页
第4页 / 共8页
逻辑地址线性地址物理地址和虚拟地址理解.docx_第5页
第5页 / 共8页
点击查看更多>>
下载资源
资源描述

逻辑地址线性地址物理地址和虚拟地址理解.docx

《逻辑地址线性地址物理地址和虚拟地址理解.docx》由会员分享,可在线阅读,更多相关《逻辑地址线性地址物理地址和虚拟地址理解.docx(8页珍藏版)》请在冰豆网上搜索。

逻辑地址线性地址物理地址和虚拟地址理解.docx

逻辑地址线性地址物理地址和虚拟地址理解

逻辑地址(LogicalAddress)是指由程式产生的和段相关的偏移地址部分。

例如,你在进行C语言指针编程中,能读取指针变量本身值(&操作),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对物理地址相干。

只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel保护模式下程式执行代码段限长内的偏移地址(假定代码段、数据段如果完全相同)。

应用程式员仅需和逻辑地址打交道,而分段和分页机制对你来说是完全透明的,仅由系统编程人员涉及。

应用程式员虽然自己能直接操作内存,那也只能在操作系统给你分配的内存段操作。

线性地址(LinearAddress)是逻辑地址到物理地址变换之间的中间层。

程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。

如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。

若没有启用分页机制,那么线性地址直接就是物理地址。

Intel80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。

物理地址(PhysicalAddress)是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。

如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。

如果没有启用分页机制,那么线性地址就直接成为物理地址了。

虚拟内存(VirtualMemory)是指计算机呈现出要比实际拥有的内存大得多的内存量。

因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。

这使得许多大型项目也能够在具有有限内存资源的系统上实现。

一个非常恰当的比喻是:

你不必非常长的轨道就能让一列火车从上海开到北京。

你只需要足够长的铁轨(比如说3公里)就能完成这个任务。

采取的方法是把后面的铁轨即时铺到火车的前面,只要你的操作足够快并能满足需求,列车就能象在一条完整的轨道上运行。

这也就是虚拟内存管理需要完成的任务。

在Linux0.11内核中,给每个程式(进程)都划分了总容量为64MB的虚拟内存空间。

因此程式的逻辑地址范围是0x0000000到0x4000000。

有时我们也把逻辑地址称为虚拟地址。

因为和虚拟内存空间的概念类似,逻辑地址也是和实际物理内存容量无关的。

逻辑地址和物理地址的“差距”是0xC0000000,是由于虚拟地址->线性地址->物理地址映射正好差这个值。

这个值是由操作系统指定的。

机理逻辑地址(或称为虚拟地址)到线性地址是由CPU的段机制自动转换的。

如果没有开启分页管理,则线性地址就是物理地址。

如果开启了分页管理,那么系统程式需要参和线性地址到物理地址的转换过程。

具体是通过设置页目录表和页表项进行的。

 

一、概念物理地址(physicaladdress)

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

——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。

所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。

也许错误的理解更利于形而上的抽像。

虚拟内存(virtualmemory)这是对整个内存(不要与机器上插那条对上号)的抽像描述。

它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000-1那个地址元素;之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtualmemory)。

进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。

这个“转换”,是所有问题讨论的关键。

有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。

(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。

不奇怪,因为转换后的物理地址并非相同的。

——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是callA,而是call0x0811111111,也就是说,函数A的地址已经被定下来了。

没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。

打住了,这个问题再说下去,就收不住了。

逻辑地址(logicaladdress)Intel为了兼容,将远古时代的段式内存管理方式保留了下来。

逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。

以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。

——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为[段标识符:

段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符:

0x08111111],这样,才完整一些”线性地址(linearaddress)或也叫虚拟地址(virtualaddress)跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

CPU将一个虚拟内存空间中的地址(逻辑地址)转换为物理地址,需要进行两步:

首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。

这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。

之所以这样冗余,Intel完全是为了兼容而已。

 

二、CPU段式内存管理

逻辑地址如何转换为线性地址一个逻辑地址由两部份组成,段标识符:

段内偏移量。

段标识符是由一个16位长的字段组成,称为段选择符。

其中前13位是一个索引号。

后面3位包含一些硬件细节,如图:

 

最后两位涉及权限检查,本贴中不包含。

索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么索引呢?

这是“段描述符(segmentdescriptor)”,段描述符具体地址描述了一个段。

这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,由8个字节组成,如下图:

 

图示比较复杂,可以利用一个数据结构来定义它,不过,在此只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。

Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。

那究竟什么时候该用GDT,什么时候该用LDT呢?

这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。

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

再看这张图比起来要直观些:

 

首先,给定一个完整的逻辑地址[段选择符:

段内偏移地址],

1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。

我们就有了一个数组了。

2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。

3、把Base+offset,就是要转换的线性地址了。

还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。

 

三、Linux的段式管理

Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,硬件要求这样做了,软件就只能照办,形式主义。

另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。

按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过Linux则对所有的进程都使用了相同的段来对指令和数据寻址。

即用户数据段,用户代码段,对应的,内核中的是内核数据段和内核代码段。

include/asm-i386/segment.h

 

CODE:

#defineGDT_ENTRY_DEFAULT_USER_CS

#define__USER_CS(GDT_ENTRY_DEFAULT_USER_CS*8+3)

#defineGDT_ENTRY_DEFAULT_USER_DS

#define__USER_DS(GDT_ENTRY_DEFAULT_USER_DS*8+3)

#defineGDT_ENTRY_KERNEL_BASE

#defineGDT_ENTRY_KERNEL_CS(GDT_ENTRY_KERNEL_BASE+0)

#define__KERNEL_CS(GDT_ENTRY_KERNEL_CS*8)

#defineGDT_ENTRY_KERNEL_DS(GDT_ENTRY_KERNEL_BASE+1)

#define__KERNEL_DS(GDT_ENTRY_KERNEL_DS*8)

把其中的宏替换成数值,则为:

 

CODE:

#define__USER_CS115[000000001110011]

#define__USER_DS123[000000001111011]

#define__KERNEL_CS96[000000001100000]

#define__KERNEL_DS104[000000001101000]

方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了

CODE:

__USER_CSindex=14T1=0

__USER_DSindex=15T1=0

__KERNEL_Cindex=12T1=0

__KERNEL_DSindex=13T1=0

T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15项(arch/i386/head.S):

 

CODE:

.quad0x00cf9a000000ffff/*0x60kernel4GBcodeat0x00000000*/

.quad0x00cf92000000ffff/*0x68kernel4GBdataat0x00000000*/

.quad0x00cffa000000ffff/*0x73user4GBcodeat0x00000000*/

.quad0x00cff2000000ffff/*0x7buser4GBdataat0x00000000*/

按照前面段描述符表中的描述,可以把它们展开,发现其16-31位全为0,即四个段的基地址全为0。

这样,给定一个段内偏移地址,按照前面转换公式,0+段内偏移,转换为线性地址,可以得出重要的结论,“在Linux下,逻辑地址与线性地址总是一致的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。

”Linux中,绝大部份进程并不例用LDT,除非使用Wine,仿真Windows程序的时候。

 

四、CPU的页式内存管理

CPU的页式内存管理单元,负责把一个线性地址,转换为物理地址。

从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。

这个大数组我们称之为页目录。

目录中的每一个目录项,就是一个地址——对应的页的地址。

另一类“页”,我们称之为物理页,或者是页框(frame)、页桢的。

是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。

为了节省空间,引入了一个二级管理模式的机器来组织分页单元。

文字描述太累,看图直观一些:

 

如上图,

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

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

——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。

     每一个32位的线性地址被划分为三部份,面目录索引(10位):

页表索引(10位):

偏移(12位)依据以下步骤进行转换:

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

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

(又引入了一个数组),页的地址被放到页表中去了。

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

(4)    将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;这个转换过程,应该说还是非常简单地。

 

全部由硬件完成,虽然多了一道手续,但是节约了大量的内存,还是值得的。

那么再简单地验证一下:

1、这样的二级模式是否仍能够表示4G的地址;页目录共有:

2^10项,也就是说有这么多个页表每个目表对应了:

2^10页;每个页中可寻址:

2^12个字节。

还是2^32=4GB

2、这样的二级模式是否真的节约了空间;也就是算一下页目录项和页表项共占空间(2^10*4+2^10*4)=8KB。

值得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加。

计算起来就方便多了。

但是,为什么同时也要把页目录低12位屏蔽掉呢?

因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。

 

 

五、Linux的页式内存管理

原理上来讲,Linux只需要为每个进程分配好所需数据结构,放到内存中,然后在调度进程的时候,切换寄存器cr3,剩下的就交给硬件来完成了(事实上要复杂得多,在此只分析最基本的流程)。

前面说了i386的二级页管理架构,不过有些CPU,还有三级,甚至四级架构,Linux为了在更高层次提供抽像,为每个CPU提供统一的界面。

提供了一个四层页管理架构,来兼容这些二级、三级、四级管理架构的CPU。

这四级分别为:

页全局目录PGD、页上级目录PUD、页中间目录PMD、页表PT。

整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:

 

 

 

那么,对于使用二级管理架构32位的硬件,四级转换怎么能够协调地工作呢?

嗯,来看这种情况下,怎么来划分线性地址吧!

从硬件的角度,32位地址被分成了三部份;从软件的角度,由于多引入了两部份,也就是说,共有五部份。

——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。

这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也就共建了和谐计算机系统。

这样,虽说是多此一举,但是考虑到64位地址,使用四层转换架构的CPU,此时不再把中间两个设为0了,这样,软件与硬件再次共建了和谐计算机系统——抽像,强大呀!

例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进是:

00001000000101000111001001011000内核对这个地址进行划分PGD=0000100000PUD=0PMD=0PT=0101000111offset=001001011000

现在来理解Linux高招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。

而不是再到PUD和PMD中去查数组(虽然它们两个在线性地址中,长度为0,2^0=1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?

从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。

那么所谓先到PUD,到到PMD中做映射转换,就变成了保持原值不变,一一转手就可以了。

这样,就实现了“逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知道有PUD、PMD这个东西”。

然后交给硬件,硬件对这个地址进行划分,看到的是:

页目录=0000100000PT=0101000111offset=001001011000嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1