`

地址(转载)

阅读更多

一、概念

物理地址(physical address)
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。

虚拟内存(virtual memory)
这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个 0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是 call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。
打住了,这个问题再说下去,就收不住了。

逻辑地址(logical address)
Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些”

线性地址(linear address)或也叫虚拟地址(virtual address)
跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

-------------------------------------------------------------
CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。

这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已。

2、CPU段式内存管理,逻辑地址如何转换为线性地址
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:
[attach]179411[/attach]
最后两位涉及权限检查,本贴中不包含。

索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么东东的索引呢?这个东东就是“段描述符 (segment descriptor)”,呵呵,段描述符具体地址描述了一个段(对于“段”这个字眼的理解,我是把它想像成,拿了一把刀,把虚拟内存,砍成若干的截——段)。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,我刚才对段的抽像不太准确,因为看看描述符里面究竟有什么东东——也就是它究竟是如何描述的,就理解段究竟有什么东东了,每一个段描述符由8个字节组成,如下图:
[attach]179412[/attach]
这些东东很复杂,虽然可以利用一个数据结构来定义它,不过,我这里只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。

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

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

好多概念,像绕口令一样。这张图看起来要直观些:
[attach]179413[/attach]
首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
3、把Base + offset,就是要转换的线性地址了。

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

3、Linux的段式管理
Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,呵呵,没办法,硬件要求这样做了,软件就只能照办,怎么着也得形式主义一样。
另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。所以,Linux的段式管理,事实上只是“哄骗”了一下硬件而已。

按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过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 *



#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)

#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS *


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

#define __USER_CS 115        [00000000 1110  0  11]

#define __USER_DS 123        [00000000 1111  0  11]

#define __KERNEL_CS 96      [00000000 1100  0  00]

#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下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!”

忽略了太多的细节,例如段的权限检查。呵呵。

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

4.CPU的页式内存管理

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页 (page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个 tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。

另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:
[attach]179414[/attach]
如上图,
1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。万里长征就从此长始了。
2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
3、每一个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屏蔽为0,是很好理解的,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加。计算起来就方便多了。但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。

本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍。

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

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

页全局目录PGD(对应刚才的页目录)
页上级目录PUD(新引进的)
页中间目录PMD(也就新引进的)
页表PT(对应刚才的页表)。

整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:
[attach]179415[/attach]
那么,对于使用二级管理架构32位的硬件,现在又是四级转换了,它们怎么能够协调地工作起来呢?嗯,来看这种情况下,怎么来划分线性地址吧!
从硬件的角度,32位地址被分成了三部份——也就是说,不管理软件怎么做,最终落实到硬件,也只认识这三位老大。
从软件的角度,由于多引入了两部份,,也就是说,共有五部份。——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。
这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也不会出错,也就是说大家共建了和谐计算机系统。

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

例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
0000100000 0101000111 001001011000
内核对这个地址进行划分
PGD = 0000100000
PUD = 0
PMD = 0
PT = 0101000111
offset = 001001011000

现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再到 PUD和PMD中去查数组(虽然它们两个在线性地址中,长度为0,2^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换,就变成了保持原值不变,一一转手就可以了。这样,就实现了“逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知道有PUD、PMD这个东西”。

然后交给硬件,硬件对这个地址进行划分,看到的是:
页目录 = 0000100000
PT = 0101000111
offset = 001001011000
嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。
http://www.chinaunix.net/jh/4/919019.html























分享到:
评论

相关推荐

    [转载]已破解的FusionCharts图表SWF文件地址清单

    NULL 博文链接:https://skzr-org.iteye.com/blog/800380

    scratch2源码转载)

    scratch2源码转载】。。。。。本资源系百度网盘分享地址

    转载40种转载40种网页常用小技巧(javascript)--备不时之需(javascript)--备不时之需

    通过设置 `Shortcut Icon`,可以自定义浏览器地址栏左侧显示的图标。 ### 6. 自定义收藏夹图标 ```html ``` 当用户将网页添加到收藏夹时,此图标将会出现在收藏夹列表中。 ### 7. 关闭输入法 ```html ``` 此属性...

    多线程MAC地址扫描

    易语言编写的多线程MAC地址扫描源码。(转载)

    Labview_LabSQL

    软件安装说明: ...欢迎把我们的软件地址转载到其他网站,给我带来访问量,这就是我们最大的动力。 如果觉得网站好请告诉你的朋友哦! 我们的网站是3322软件站(http://www.3322.cc)。 解压密码:www.3322.cc

    switchHosts的下载地址.txt

    host的作用是将一些常用的...版权声明:本文为CSDN博主「追根溯源」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liulei21/article/details/84852941

    想搞嵌入式视频开发的可以看看,是转载的资料,附带下载地址

    本篇文章将深入解析嵌入式视频开发的核心技术——FFmpeg和x264,通过解读标题“想搞嵌入式视频开发的可以看看,是转载的资料,附带下载地址”以及描述“专门的ffmpeg和x264开发教程,有具体的下载地址”,为读者呈现...

    迅雷快车专用地址转换(VC源码)

    下面文字为转载: 迅雷下载地址是在普通地址的前面加上“AA”,在后面加上“ZZ”,再将这个地址进行BASE64编码处理,然后再在前面加上“thunder://”,就成了迅雷专用下载地址了。比如要将...

    115网盘地址解析工具V2.0 2011-10-29

    115网盘地址解析工具V2.0 2011-10-29 C# 115网盘地址解析工具(附上一版本源码以及原理分析) 2011-10-29 如有转载,请注明出处:http://www.cnblogs.com/flydoos/archive/2011/10/29/2228311.html

    CCIE面试题 转载于互联网

    - **NAT Source List与Pool**:通过`ip nat inside source list 10 pool cisco`和`ip nat outside source list 20 pool hzh`,定义了内部地址列表与外部地址池的映射关系。 综上所述,CCIE面试题主要考察了PIM-SM...

    转载VLAN学习手册

    2. **MAC地址划分**:基于设备的MAC地址来划分VLAN,更灵活,但配置复杂。 3. **协议类型划分**:根据数据帧的协议类型划分VLAN,如区分IPX、AppleTalk等不同网络协议的流量。 4. **策略划分**:基于用户策略,如...

    VC socket传送文件的例子(转载)

    3. 绑定地址:使用`bind`函数将Socket与特定的IP地址和端口号绑定。 4. 监听连接:调用`listen`函数开始监听来自客户端的连接请求。 5. 接受连接:使用`accept`函数接受客户端的连接,并返回一个新的Socket用于数据...

    vulhub靶场个人writeup,转载引用请注明地址,坑点补充联系作者。- -.zip

    靶场,是指为信息安全人员提供实战演练、渗透测试和攻防对抗等训练环境的虚拟或实体场地。在不同的领域中,靶场扮演着重要的角色,尤其是在网络安全领域,靶场成为培养和提高安全专业人员技能的重要平台。...

    Socket 传输文件代码转载

    2. **定义变量**:`SendFileName`存储待发送文件的路径,`RemoteHostName`是远程主机的名称或IP地址,`RemotePort`是远程主机的端口号,`ipInfo`和`ipAddr`用于解析远程主机的IP地址,`ip`是最终使用的IP地址,`...

    allegro 约束(转载)

    - **DDR地址、片选及其他控制线**: 线宽5mil,内部间距15mil,外部间距20mil,需采用菊花链拓扑结构,其线长可比DDR时钟线长1000至2500mil。 - **DDR数据线、DDRDQS、DDRDQM线**: 线宽5mil,内部间距15mil,外部间距...

    dos局域网命令转载.doc

    - `ping [IP地址]`:向指定的IP地址发送默认大小为32字节的数据包。 - 参数说明: - `-l [数据包大小]`:设置数据包的大小。 - `-n [发送次数]`:设置发送数据包的次数。 - `-t`:持续发送数据包直到中断。 3....

    C指针全面总结(转载)

    指针是一种特殊的数据类型,其存储的是内存地址。为了全面理解指针,我们需要掌握以下几个关键概念: 1. **指针的类型**:指针本身的类型,即指针变量能够存储的地址类型。可以通过去除指针变量名和指针声明符`*`来...

    快速搜索整理IP地址器

    ┃ 如有转载请勿删除本说明文件,谢谢合作 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ ┃ 【使用前请您先阅读以下条款,否则请勿使用本站提供的文件】 ┃ ┃ ┃ ┃ 推荐使用:...

    二级c 语言试题不可转载

    指针是C语言的一大特色,它使得可以直接操作内存地址。"C_961.doc"可能包含了指针的使用,如指向变量、动态内存分配、指针运算和指针数组等题目,这部分内容往往对初学者来说较为挑战。 5. 文件操作 C语言提供了...

    zigbee组网分析 转载

    在这个过程中,设备会设置自己的网络地址,并进行相关配置。 5. **网络建立与加入** - 协调器在`ZDApp_Init()`中创建网络,包括设置网络ID、信道、安全参数等。终端设备则会搜索网络,通过协调器发送的网络信息来...

Global site tag (gtag.js) - Google Analytics