您好,欢迎访问三七文档
实验三虚拟内存综合实验实验所属系列:《计算机操作系统》实验对象:本科相关课程及专业:计算机操作系统,C,数据结构实验时数(学分):4学时实验类别:课外上机实验开发教师:秦科【实验目的】通过实验,掌握分页机制的实验原理,了解页表的内容和机制【实验内容】通过手工查看系统内存,并修改特定物理内存的值,实现控制程序运行的目的【实验环境】Linux+Bochs【实验参考步骤】1.编写实验使用的示例程序:#includestdio.hintj=0x123456;intmain(){printf(theaddressofjis0x%x\n,&j);while(j);printf(programterminatednormally!\n);return0;}2.根据分页机制,查看段寄存器值3.计算LDT实际地址4.根据表项的结构,获取线性地址为0x10000000。5.使用creg查看寄存器信息如下:6.计算线性地址,加上页内偏移,即为物理地址。【实验预备知识】关于计算机中的地址(一)物理地址,线性地址,逻辑地址,虚拟地址。物理地址:物理地址最好理解,我们可以简单的把内存比作一个大的数组(为了分析方便),每个数组都有其下标,这个下标标识了内存中的地址,这个实实在在的在内存中的地址,我们称之为物理地址。但是在用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应,相信并不是一个所谓的数组,但是做出这样的比拟,有利于更好的理解。还依稀记得这张图:逻辑地址:与物理地址比较相对的是逻辑地址,实际上这个概念,我觉得可以这样理解,这个地址就是在程序中我们把它放到的位置;而这个位置通常是由编译器给出的。另外的一种理解是:逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。Intel段式管理中:,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为[段标识符:段内偏移量]。”比如我们在程序中定义一个变量intg=3;相应的汇编代码应该是mov[g],3;那么这个g应该放在哪儿呢?实际上我们可以看到,这个g的地址总在在编译,链接之后就会一个确定的地址;而这个确定的地址我们叫做逻辑地址。虚拟地址:百度百科曰:VirtualAddress,简称VA,由于Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址。实际上因为我们现代程序中地址都是虚拟的,所以这里的虚拟地址和线性地址是等价了的。线性地址:线性地址(LinearAddress)也叫虚拟地址(virtualaddress)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。问题的产生:当我们写一个程序,定义了一个变量(全局或者局部),如果这个地址就是之前说的物理地址,那么很显然会出很大问题:为了避免进程之间有地址的冲突必须为每个进程分配空间,做出一系列保护措施,可想而知,为了运行一个进程操作系统要做的工作是巨大的,而且很多的程度需要硬件的配合;那么很显然PC不是这么处理的,既然不能给程序真的物理地址,那么就给它“假”的,或者说一种内存管理映像,但是毕竟程序是要放在内存中才能运行的,所以在假和真之间必须经过某种映射处理才行。而这个假的地址就是:虚拟内存,它对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000-1那个地址元素;进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间,而且只要我们控制好转换的过程就不会出现地址冲突,这样多个进程可以使用相同的地址,因为转换后的物理地址并非相同的。#includestdio.hinta=0;voidfunc(void){intb=0x11223344;}intmain(){intg=3;g=a;func();return0;}在windows下使用gcc编译,链接之后我们得到一个a.exe文件,这是一个可执行程序,我们这里使用工具ollydbg打开看下这个exe文件究竟是什么样的。这里我们可以看到地址是从0x00400000开始的,里面还有这样的信息:E870050000这显然是机器码,而在右边,ollydbg给出的翻译是call00401500后面给出的是一个确定的地址信息。这里只是一个例子,事实上,如果我们在vs中做好一定的配置,那么会得到:但在使用全局变量a的语句中(g=a),汇编代码为:moveax,ds:[00417140h];一定程序上符合我们之前说的:段:偏移量的做法;一个比较有意思的问题能够说明我们所看到的exe中这些地址究竟是实的还是虚的:#includestdio.h#includestdlib.hintmain(){char*p=(char*)malloc(sizeof(char));printf(address=%x\n,p);getchar();return0;}这个程序运行之后,你会发现每次运行的结果都是不同的,这能说明什么呢?其实这什么都说明不了。首先可以断定不是物理内存,因为如果是物理内存是不会直接给应用程序的。那么究竟是逻辑地址还是还是线性地址呢?随便一搜网上都说是线性地址,但是如果验证它是线性地址而不是逻辑地址呢,我想从两个方面入手:1.malloc分配的内存是在堆上,定义一个赋值的数组很容易看到这段区域,实际上跟栈区紧挨着,而且跟函数是有关系的。2.malloc之后总是要由我们自己来进行free操作,是不是和线性地址物理地址之间映射有关系的。这是有趣的问题,其实使用oolydbg或者vc修改机器码运行可以很快的得出结论的。(二)复杂的地址转换现在回到问题的本质,现在一个程序要运行,必须加载到事实在在的内存中去,不管你做多少次变化,程序需要的是那个确确实实存在的物理地址,也就是说其他的地址都是浮云,这些地址只不过是为了完成某种工作而出现的中间产物罢了,事实上这也是我的个人理解我:这几个地址的存在也是因为为了解决RAM容量小,而又想运行多任务而出现的。CPU需要将一个虚拟内存空间中的地址转换为物理地址,也就是:将一个逻辑地址转换为物理地址。需要进行两步:首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。这样的两次转换真真切切在我们的CPU中,当然也在很多的教辅材料中,不想花太多的时间描述,因为里面涉及到的内容太复杂了,很多问题我都没有清晰的答案:从开机启动的内存,cache初始化到bootloader,GDT/LDT等等,而且这还没有考虑到操作系统之间的差异,因为linux,windows对待地址转换问题是有些不同的。接下给出几幅我觉得很不错的图片,形象思维一哈:这个图很清晰的描述了逻辑地址到物理地址的转换过程:逻辑地址=====》线性地址====》物理地址1.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和windows的做法貌似是不同的。2.CPU的页式内存管理CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页,例一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。看图:描述:1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。万里长征就从此长始了。2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)转换步骤:1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的物理地址;
本文标题:地址映射实验指导书
链接地址:https://www.777doc.com/doc-5283308 .html