黑马程序员技术交流社区

标题: 【上海校区】linux虚拟地址转物理地址 [打印本页]

作者: 梦缠绕的时候    时间: 2018-7-4 11:18
标题: 【上海校区】linux虚拟地址转物理地址
80386虚拟地址和物理地址转换CPU的发展

之前在看malloc内存分配函数的原理时,有涉及到分配虚拟内存,然后再映射到物理内存,当初也是看得一头雾水,因为对虚拟内存和物理内存不是很了解。所以这篇文章总结下我在学习虚拟内存和物理内存的一些收获。

首先给出CPU的进化表,图片来自博客wjlkoorey的博客[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... 03DA/640?wx_fmt=png[/img]

CPU发展从寻址物理地址;寻址段地址到物理地址转换;寻址逻辑地址转换为线性地址,再转换为物理地址。在8086之前的CPU,寻址都为物理地址,是并没有段的概念。当程序要访问内存时都是要给出内存的实际物理地址,这样在程序源代码中就会出现很多硬编码的物理地址。这样的程序可想而知,难重定位,可控性弱,结构丑陋,那个年代写这样的程序在我们现在看来是多么让人恼火的一件事儿。

后来8086引入一个非常重要的概念--段,这样就实现了分段机制;8086CPU地址总线为16,这样寻址范围为2^16=64k,而8086的寻址空间为1M,那么是怎么实现的了?原来这时候cpu给出的地址为段地址,需要加上段地址(由cs,ds,ss,es)之后才构成物理地址。物理地址为=段地址:段内偏移量;段地址左移4位+段内偏移量,即可构成20位的物理地址。例如ES=0x1000,DI=0xFFFF,那么物理地址为:

AD(Absolute Address)=(ES)*(0x10)+(DI)=0x1FFFF

0x10为16,段地址*16(2^4),即向左移动4位。这样就可以对20位的1M内存空间进行寻址。

而这这种方式的寻址最大地址为0xFFFF:0xFFFF=0x10FFEF,大于1M空间,这样如果访问大于1M的内存空间时,将会产生结果了?8086的做法是自动从物理内存0地址开始寻址,有人就是说[0x0000,0x10FFEF]地址是按0xFFFF取模寻址。下图说明这种情况:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... QjMg/640?wx_fmt=png[/img]

CPU发展的下一个里程碑是1985年80386的问世,从16位到32位CPU的飞跃,这中间80286就成了这次飞跃的跳板,80286地址上升到24位并且引入了保护模式。保护模式即规定进程能访问的内存,有些内存是不能访问的,例如进程不能访问内核代码。80386继承了80286的内存保护模式和分段机制,并且引入了分页虚拟机制。首先80386继承了80286的基础上添加两个段寄存器FS和GS。很显然,为了实现保护模式,段寄存器只存储段基地址是不够的,至少还需要段地址的长度还有一些诸如访问权限之类的其他信息。所以段寄存器存储的并不是真正的段基地址,而是存储每个段描述符的选择符,通过这个选择符在GDT表格中找到这个段的基地址。

现在主流的x86CPU上的主流操作系统,Linux,FreeBSD,Windows等待都是工作在保护模式下,处理器只有在上电启动,引导阶段初始化时在会进入实时模式,实时模式任务处理之后,即进入保护模式。

80386CPU逻辑地址转换为物理地址逻辑地址转化为线性地址

80386cpu当需要访问内存时,首先给出的逻辑地址,然后通过MMU内存管理单元分段机制转换为线性地址,然后线性地址再通过MMU内存管理单元分页机制转换为物理地址。

首先来看下cs,ds等段寄存器的内容,如下图所示:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... kRnw/640?wx_fmt=png[/img]

当处理器需要访问内存时,给出的逻辑地址是:选择符+偏移量,然后通过MMU的分段机制,取出的逻辑地址的INDEX,乘以8,再加上GDTR存储的全局描述符表的基地址,即可获取这个段的描述符,然后存入描述符寄存器(一种对用户隐藏的寄存器,也称为不可编程寄存器),然后取出这个64位的段描述符的段基地址+偏移量,即可获得这个逻辑地址对应的线性地址,下图展示了转换过程:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... SZ7g/640?wx_fmt=png[/img]

线性地址由目录项(DIR)+页表项(PAGE)+页内偏移(OFFSET)组成。在介绍页地址转物理地址时,先介绍下段描述符的格式,如下图所示:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... pSrw/640?wx_fmt=png[/img]

线性地址转化为物理地址

之前,已经将逻辑地址转换为线性地址,接下来看下是如何从线性地址转化为物理地址,先给出下面示意图:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... FjFw/640?wx_fmt=png[/img]

一个线性地址由10位目录表+10位页表+12位偏移量组成,当给定一个线性地址时,

这样就完成了从线性地址转为物理地址

接下来,看下页表项的格式。先来看下空闲的页表项和存储数据的页表项的格式:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... 3Gsg/640?wx_fmt=png[/img][img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... 5s8Q/640?wx_fmt=png[/img]三层页表都有着相同的格式,页帧地址为一块内存页的首地址,而内存页是4kb对齐的,所以页表项的低12位为0;而页目录项指向的是页表的首地址。

页转换缓存

为了提高系统,防止每次地址转换都需要访问页表,处理器还设置了TLB(Translation lookaside buffer)转换后备高速缓冲区,存储最近使用的线性地址到物理地址的映射;下面给出TLB的原理图:[img]https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/ ... bg7Q/640?wx_fmt=png[/img]

当CPU需要访问一个一个内存地址时,给出一个虚拟地址,先是到TLB中查找是否有对应的物理地址,如果有,即命中,直接用TLB中的对应物理地址访问缓存;如果在TLB中没有对应的物理地址,即未命中,则需要到内存页表求出物理地址,并将这个物理地址存入TLB中。访问cache时,如果Cache中有需要的数据,则直接返回需要的数据,如果Cache中没有需要访问的数据,则需要到内存获取数据返回给用户,并将获取的数据存入Cache中。

所以,如果对计算机有足够的了解,那么就会发现计算机架构里面存在着好多的缓存设计,首先应用程序从磁盘获取数据时,在内核有一块内存存储最近访问的数据缓存;当CPU从主存获取数据时,也是先从缓冲区获取数据,然后在读进CPU。还有应用程序也有缓存,例如MySQL的存储引擎innodb也有一块缓冲区,存储磁盘数据;在web网站应用程序中,当从数据库获取数据时,先用memcache或者redis获取最近访问的数据等等。


作者: 吴琼老师    时间: 2018-7-5 17:43

作者: 不二晨    时间: 2018-7-17 14:18
棒棒哒




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2