初期的显存分配机制
在初期的计算机中,要运行一个程序,会把这种程序全都放入显存,程序都是直接运行在显存上的,也就是说程序中访问的显存地址都是实际的化学显存地址。当计算机同时运行多个程序时,必须保证这种程序用到的显存总额要大于计算机实际化学显存的大小。
那当程序同时运行多个程序时,操作系统是怎样为这种程序分配显存的呢?下边通过实例来说明当时的显存分配方式:
某台计算机总的显存大小是128M,如今同时运行两个程序A和B,A需占用显存10M,B需占用显存110。计算机在给程序分配显存时会采取这样的方式:先将显存中的前10M分配给程序A,接着再从显存中剩余的118M中界定出110M分配给程序B。这些分配方式可以保证程序A和程序B都能运行,并且这些简单的显存分配策略问题好多。
初期的显存分配方式
问题1:进程地址空间不隔离。因为程序都是直接访问数学显存,所以恶意程序可以随便更改别的进程的显存数据,以达到破坏的目的。有些非恶意的,并且有bug的程序也可能不留神更改了其它程序的显存数据,都会造成其它程序的运行出现异常。这些情况对用户来说是难以容忍的,由于用户希望使用计算机的时侯,其中一个任务失败了,起码不能影响其它的任务。
问题2:显存使用效率低。在A和B都运行的情况下,假如用户又运行了程序C,而程序C须要20M大小的显存能够运行,而此时系统只剩下8M的空间可供使用,所以此时系统必须在已运行的程序中选择一个将该程序的数据暂时拷贝到硬碟上,释放出部份空间来供程序C使用,之后再将程序C的数据全部倒入显存中运行。可以想像得到,在这个过程中,有大量的数据在倒入装出,造成效率非常低下。
问题3:程序运行的地址不确定。当显存中的剩余空间可以满足程序C的要求后,操作系统会在剩余空间中随机分配一段连续的20M大小的空间给程序C使用,由于是随机分配的,所以程序运行的地址是不确定的。
分段
为了解决上述问题,人们想到了一种变通的方式,就是降低一个中间层,借助一种间接的地址访问方式访问数学显存。根据这些技巧,程序中访问的显存地址不再是实际的化学显存地址,而是一个虚拟地址,之后由操作系统将这个虚拟地址映射到适当的数学显存地址上。这样,只要操作系统处理好虚拟地址到化学显存地址的映射,就可以保证不同的程序最终访问的显存地址坐落不同的区域,彼此没有重叠,就可以达到显存地址空间隔离的疗效。
当创建一个进程时,操作系统会为该进程分配一个4GB大小的虚拟进程地址空间。之所以是4GB,是由于在32位的操作系统中,一个表针宽度是4字节,而4字节表针的轮询能力是从~,最大值表示的即为4GB大小的容量。与虚拟地址空间相对的什么是物理内存和虚拟内存,还有一个数学地址空间,这个地址空间对应的是真实的数学显存。假如你的计算机上安装了512M大小的显存,这么这个数学地址空间表示的范围是~。当操作系统做虚拟地址到化学地址映射时,只能映射到这一范围,操作系统也只会映射到这一范围。当进程创建时,每位进程就会有一个自己的4GB虚拟地址空间。要注意的是这个4GB的地址空间是“虚拟”的,并不是真实存在的,但是每位进程只能访问自己虚拟地址空间中的数据,难以访问别的进程中的数据,通过这些方式实现了进程间的地址隔离。那是不是这4GB的虚拟地址空间应用程序可以随便使用呢?很遗憾,在系统下,这个虚拟地址空间被分成了4部份:NULL表针区、用户区、64KB禁入区、内核区。
1)NULL表针区(~):假如进程中的一个线程企图操作这个分区中的数据,CPU才会引起非法访问。他的作用是,调用等显存分配函数时,假若难以找到足够的显存空间,它将返回NULL。而不进行安全性检测。它只是假定地址分配成功,并开始访问显存地址(NULL)。因为严禁访问显存的这个分区,因而会发生非法访问现象,并中止这个进程的运行。
2)用户模式分区(~):这个分区中储存进程的私有地址空间。一个进程未能以任何方式访问另外一个进程留驻在这个分区中的数据(相同exe,通过copy-on-write来完成地址隔离)。(在中,所有.exe和动态链接库都载入到这一区域。系统同时会把该进程可以访问的所有显存映射文件映射到这一分区)。
2)隔离区(~):这个分区严禁步入。任何企图访问这个显存分区的操作都是违法的。谷歌保留这块分区的目的是为了简化操作系统的现实。
3)内核区(~):这个分区储存操作系统留驻的代码。线程调度、内存管理、文件系统支持、网络支持和所有设备驱动程序代码都在这个分区加载。这个分区被所有进程共享。
应用程序能使用的只是用户区而已,大概2GB左右(最大可以调整到3GB)。内核区为2GB,内核区保存的是系统线程调度、内存管理、设备驱动等数据,这部份数据供所有的进程共享,但应用程序是不能直接访问的。
人们之所以要创建一个虚拟地址空间,目的是为了解决进程地址空间隔离的问题。但程序要想执行,必须运行在真实的显存上,所以,必须在虚拟地址与数学地址间构建一种映射关系。这样,通过映射机制,当程序访问虚拟地址空间上的某个地址值时,就相当于访问了化学地址空间中的另一个值。人们想到了一种分段()的方式,它的思想是在虚拟地址空间和化学地址空间之间做一一映射。例如说虚拟地址空间中某个10M大小的空间映射到化学地址空间中某个10M大小的空间。这些思想理解上去并不难,操作系统保证不同进程的地址空间被映射到化学地址空间中不同的区域上什么是物理内存和虚拟内存,这样每位进程最终访问到的。
化学地址空间都是彼此分开的。通过这些方法,就实现了进程间的地址隔离。还是以实例说明,假定有两个进程A和B,进程A所需显存大小为10M,其虚拟地址空间分布在到,进程B所需显存为100M,其虚拟地址空间分布为到。这么根据分段的映射方式,进程A在数学显存上映射区域为到,,进程B在数学显存上映射区域为到。于是进程A和进程B分别被映射到了不同的显存区间,彼此互不重叠,实现了地址隔离。从应用程序的角度看来,进程A的地址空间就是分布在到,在做开发时,开发人员只需访问这段区间上的地址即可。应用程序并不关心进程A到底被映射到化学显存的那块区域上了,所以程序的运行地址也就是相当于说是确定的了。右图显示的是分段方法的显存映射方式:
分段方法的显存映射方式
这些分段的映射方式似乎解决了上述中的问题一和问题三,但并没能解决问题二,即显存的使用效率问题。在分段的映射方式中,每次换入换出显存的都是整个程序,这样会导致大量的c盘访问操作,致使效率低下。所以这些映射方式还是稍显粗糙,细度比较大。实际上,程序的运行有局部性特征,在某个时间段内,程序只是访问程序的一小部分数据,也就是说,程序的大部份数据在一个时间段内都不会被用到。基于这些情况,人们想到了细度更小的显存分割和映射方式,这些方式就是分页()。
分页
分页的基本方式是,将地址空间分成许多的页。每页的大小由CPU决定,之后由操作系统选择页的大小。目前Inter系列的CPU支持4KB或4MB的页大小,而PC上目前都选择使用4KB。按这些选择,4GB虚拟地址空间共可以分成页,512M的数学显存可以分为个页。其实虚拟空间的页数要比数学空间的页数多得多。
在分段的方式中,每次程序运行时总是把程序全部倒入显存,而分页的方式则有所不同。分页的思想是程序运行时用到哪页就为哪页分配显存,没用到的页暂时保留在硬碟上。当用到这种页时再在化学地址空间中为那些页分配显存,之后构建虚拟地址空间中的页和刚分配的数学显存页间的映射。
下边通过介绍一个可执行文件的装载过程来说明分页机制的实现方式。一个可执行文件(PE文件)似乎就是一些编译链接好的数据和指令的集合,它也会被分成好多页,在PE文件执行的过程中,它往显存中装载的单位就是页。当一个PE文件被执行时,操作系统会先为该程序创建一个4GB的进程虚拟地址空间。上面介绍过,虚拟地址空间只是一个中间层而已,它的功能是借助一种映射机制将虚拟地址空间映射到化学地址空间,所以,创建4GB虚拟地址空间虽然并不是要真的创建空间,只是要创建那个映射机制所须要的数据结构而已,这些数据结构就是页目和页表。
当创建完虚拟地址空间所须要的数据结构后,进程开始读取PE文件的第一页。在PE文件的第一页包含了PE文件头和段表等信息,进程依照文件头和段表等信息,将PE文件中所有的段一一映射到虚拟地址空间中相应的页(PE文件中的段的宽度都是页长的整数倍)。这时PE文件的真正指令和数据还没有被放入显存中,操作系统只是据PE文件的背部等信息构建了PE文件和进程虚拟地址空间中页的映射关系而已。当CPU要访问程序中用到的某个虚拟地址时,当CPU发觉该地址并没有相相关联的数学地址时,CPU觉得该虚拟地址所在的页面是个空页面,CPU会觉得这是个页错误(PageFault),CPU也就晓得了操作系统还未给该PE页面分配显存,CPU会将控制权交还给操作系统。操作系统于是为该PE页面在数学空间中分配一个页面,之后再将这个数学页面与虚拟空间中的虚拟页面映射上去,之后将控制权再还给进程,进程从刚刚发生页错误的位置重新开始执行。因为此时已为PE文件的那种页面分配了显存,所以就不会发生页错误了。随着程序的执行,页错误会不断地形成,操作系统也会为进程分配相应的数学页面来满足进程执行的需求。
分页方式的核心思想就是当可执行文件执行到第x页时,就为第x页分配一个显存页y,之后再将这个显存页添加到进程虚拟地址空间的映射表中,这个映射表就相当于一个y=f(x)函数。应用程序通过这个映射表就可以访问到x页关联的y页了。
逻辑地址、线性地址、物理地址和虚拟地址的区别
逻辑地址()是指由程式形成的和段相关的偏斜地址部份。比如,你在进行C语言表针编程中,能读取表针变量本身值(&操作),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对化学地址相干。只有在Intel实模式下,逻辑地址才和化学地址相等(由于实模式没有分段或分页机制,cpu不进行手动地址转换);逻辑也就是在Intel保护模式下程式执行代码段限长内的偏斜地址(假设代码段、数据段假如完全相同)。应用程式员仅需和逻辑地址打交道,而分段和分页机制对你来说是完全透明的,仅由系统编程人员涉及。应用程式员其实自己能直接操作显存,那也只能在操作系统给你分配的显存段操作。
线性地址()是逻辑地址到化学地址变换之间的中间层。程式代码会形成逻辑地址,或说是段中的偏斜地址,加上相应段的基地址就生成了一个线性地址。假如启用了分页机制,这么线性地址能再经变换以形成一个数学地址。若没有启用分页机制,这么线性地址直接就是数学地址。Intel80386的线性地址空间容量为4G(2的32次方即32根地址总线轮询)。
化学地址()是强调目前CPU外部地址总线上的轮询化学显存的地址讯号,是地址变换的最终结果地址。假如启用了分页机制,这么线性地址会使用页目录和页表中的项变换成化学地址。假如没有启用分页机制,这么线性地址就直接成为化学地址了。
虚拟显存()是指计算机呈现出要比实际拥有的显存大得多的显存量。因而他准许程式员编制并运行比实际系统拥有的显存大得多的程式。这促使许多小型项目也就能在具有有限显存资源的系统上实现。一个特别恰当的比喻是:你何必特别长的轨道才能让一列列车从北京开到天津。你只须要足够长的铁轨(例如说3公里)才能完成这个任务。采取的方式是把前面的铁轨即时铺到列车的后面,只要你的操作足够快并能满足需求,火车能够象在一条完整的轨道上运行。这也就是虚拟显存管理须要完成的任务。在.11内核中,给每位程式(进程)都界定了总容量为64MB的虚拟显存空间。为此程式的逻辑地址范围是到。有时我们也把逻辑地址称为虚拟地址。由于和虚拟显存空间的概念类似,逻辑地址也是和实际化学显存容量无关的。逻辑地址和化学地址的“差距”是,是因为虚拟地址->线性地址->化学地址映射刚好差这个值。这个值是由操作系统指定的。机理逻辑地址(或称为虚拟地址)到线性地址是由CPU的段机制手动转换的。假如没有开启分页管理,则线性地址就是数学地址。假如开启了分页管理,这么系统程式须要参和线性地址到化学地址的转换过程。具体是通过设置页目录表和页表项进行的。