虚拟内存机制


问题引入:现在的操作系统基本上都实现了虚拟内存的功能。本文就简单的记录一下我对虚拟内存的理解。

虚拟存储器的思想是程序、数据和堆栈的大小都有可能超过物理内存大小,由操作系统把当前使用的放在内存,而不需要的放在磁盘。在编写程序是可以放在磁盘存储器上,但在执行时,必须把程序加载到物理存储器中。而存储器管理就是要将46位虚拟地址变换成32位物理地址.

而绝大部分操作系统使用的虚拟存储器技术就是分页技术。 

1. 虚拟内存的作用

      1.1 便于写程序,特别是多进程环境下的程序。
想象一下,如果每个程序都要在固定的物理地址执行,读取指定物理地址处的数据,那么多个程序如何共存,数据如何保护?有了虚拟内存这一层,每个程序都运行在自己相对独立的虚拟空间中,不需要考虑别的程序。

     1.2 可以访问比物理内存多的虚拟空间
理论上程序可以访问虚拟空间中的每一个地址,比如32位机器上一个程序可以访问4G的空间,即使实际的物理内存没有那么多。因为一个虚拟地址可以映射到物理内存,也可以映射到硬盘,或是其它设备,比如显卡的内存。从这个角度看,物理内存是当作了cache。为了达到这个效果,操作系统做了很多工作,比如将数据从硬盘空间取到内存,或是将内存放到硬盘里,一般这个操作叫做page in, page out,硬盘上存放虚拟空间内容的那个地方叫swap空间。

2. 虚拟地址和物理地址的映射
有了虚拟内存这一层,CPU上的指令访问的地址都是虚拟地址,而这些地址是需要在物理内存中真实存在的,这里就需要在虚拟地址和物理地址直接建立一个映射关系(应当是多对一的关系),说白了就是一个整数集合到另一个整数集合的映射。

       在虚拟存储器中,程序所产生的地址为虚拟地址,虚拟地址构成了虚拟地址空间。这些虚拟地址通过MMU(内存管理单元)映射为物理地址。
采用分页机制的系统,虚拟地址空间以页面为单位进行划分,虚拟地址空间会被划分成多个等大小的页面。物理地址空间也按页面为单位进行划分每一块成为页帧,或者页框。每一虚拟页面可以随意对应到物理页框,也可以对应到磁盘的页面文件的上。
例如一条mov指令:mov   eax,[0]; 
        此时虚拟地址0将被发给MMU,MMU发现0属于页面0的范围内,如果页面0对应的页框号为1,那么物理地址在物理地址4096-8191范围,此时就会将4096发送到地址总线上。因为虚拟地址0的页内偏移也是0(页内偏移:在页面里的位置,比如1,的页面偏移是1,4097的页面偏移也是1,这是因为一个页面大小为4K,用虚拟地址   mod   4k就得到了页内偏移)。
虚拟地址空间是连续的,而物理空间是可以不连续的。也就是说一个程序只要保证他的虚拟地址空间是连续的,它就可以正常运行。
上面说的是虚拟地址到物理地址的映射的简单情况。可是如何记录这些页面到页框的映射关系呢?(当然也有些处理器系统是页框到页面的转化)。在IA处理器上使用的是页表,就是在物理内存里有一块连续的空间,来记录这些页面到页框的映射关系。每一个页表项里都有一部分去指向页框的起始地址,还有部分记录了这个页面的属性。可以通过页面号来做索引。页面号就是虚拟地址   /   4K,得到的整数部分。 
当然如果只是单一的页表,也是有问题的,如果虚拟地址空间过大,那么页表所占的空间也会很大,这时候可以采用多级页表。IA32在采用4K页面的时候就使用了2级页表,IA64使用了4级。

从虚拟地址到物理地址要经过两步,第一步从虚拟地址到线性地址,第二步从线性地址到物理地址。 
第一步从段描述符表描述的段基址加上段偏移生成线性地址。 
IA32中线性地址高10位为页目录索引,通过此找到页表,线性地址中间10位为页表项索引,通过前面找到的页表加上这个索引,找到页表项。页表项指示着页框号,页框号加上线性地址低12位(页内偏移)就生成了物理地址。
通过把每个任务放置在不同的虚拟地址空间的方法来实现任务与任务的隔离,达到应用程序之间保护的目的。虚拟地址到物理地址的映射函数在每个任务中进行定义,随着任务切换,映射函数也切换。任务A的虚拟地址空间映射到物理地址空间的某个区域,而任务B的虚拟地址空间映射到物理地址空间的另外区域,彼此独立,互不相干。因此,两个不同的任务,尽管虚拟存储单元地址相同,但实际的物理存储单元地址可以不同。  
每个任务各有一组独立的映射表,即具有不同的地址转换函数。在80386上,每个任务都有自己的段表及页表。当处理器进行切换并执行新的任务时,这种任务切换的一个重要部分,就是为新任务切换任务的转换表。为了使操作系统与所有的应用程序相隔离,可以把操作系统存储在一个单一的任务中。

虚拟内存就是进程本身拥有的不对应具体物理内存的空间,很显然一个Hello World的可执行文件不会占用4G的物理内存,因为事实上根本没那么多物理内存。再比如malloc函数分配的内存,在分配结束之后,只要不对他访问,就不会去映射具体物理内存,那么malloc这段内存就永远是虚拟的,当要对该内存进行写访问的时候,MMU 发现该内存还没有被映射,于是就会进入中断程序,在linux里面中断最终会执行do_page_fault函数,在这个函数里面,操作系统会分配一页真正的物理内存,然后映射到你进程的这段地址空间里面,这样你才能访问真正的物理内存。

     以下一些内容是在一篇博客上看到的。。。。。特贴在这里

有了虚拟内存这一层,CPU上的指令访问的地址都是虚拟地址,而这些地址是需要在物理内存中真实存在的,这里就需要在虚拟地址和物理地址直接建立一个映射关系(应当是多对一的关系),说白了就是一个整数集合到另一个整数集合的映射。

最简单的可以用一张表格来记录这个关系:T[vi] = pi, 其中vi, pi都是32位的整数.

如果对每一个可能的地址都要记录一下的话,这个表格占用的空间需要16G的空间。为了减小管理开销,将空间进行切分,一段连续的空间作为一个映射的单元,一般称为page。假设每页内存的大小为4k, 那么4G的空间就可以看作是1M个4K大小的page组成,那么映射表的大小需要4M,是不是好多了?

这个4M是对一个进程来说的,如果我有1k个进程,那么是不是需要4G的空间了呢?
4M可以映射整个空间,但如果我只需要访问其中很小的空间(比如32M),是不是也一定要分配4M空间给映射表呢?

借用一个寄存器,记录了页目录的地址,页目录可以存放1024个指针,指向1024张页表。虚拟地址的高10位决定了一个地址的映射是存放在哪个页表中的 (1024种可能). 页表中存了转换信息,每个页表存1024项,虚拟地址的bit[21..12]决定了它在页表中的地址,这样就可以定位到物理内存中的一页了,剩下的12 位则是页内偏移,这样就实现了虚拟地址到物理地址的转换。


页表可以动态分配,只有需要访问的虚拟地址的映射关系才会存到页表里,这样如果我只需要访问一小部分空间时,并不需要分配1024张页表,只分配真正需要的页表就可以了。

由于每个物理内存页都是在4k的边界上开始的,页表和页目录里存的32位地址的低12位其实是不需要的,可以另做它用,比如做一些标记位。

so far so good。

但是,页目录和页表又是存在哪里的呢?里面的内容是怎么维护的呢?
页表和页目录显然也是存在物理内存中的,而且一个页表或页目录刚好对应一个物理的内存页,是不是很巧?
里面的内容是由操作系统维护的,对页表和页目录的操作也只不过是对内存的操作而已。

但是程序访问页表和页目录时,引用的是它们的虚拟地址,而这个虚拟地址是不是也要做一个到物理地址的转换呢?当初就是在个地方,我想破脑袋也想不明白到底是怎么回事。

其实,Intel CPU的分页机制,是需要手工开启的,在系统启动的时候,分页机制还没开启,这时程序访问的地址是物理地址,就是在这个时候,操作系统将页目录和页表初始化了一下,将从0开始的一段地址做了一个恒等映射。0 -> 0, 1 -> 1, …,然后才开启CPU的分页机制,将CR3指向页目录的起始地址。

实现页表初始化的几行代码在head.S文件里,大家一起欣赏一下吧:

//页表初始化
page_pde_offset = (__PAGE_OFFSET >> 20);

movl $pa(__brk_base), %edi          //第一张页表的物理地址
movl $pa(swapper_pg_dir), %edx      //页目录的物理地址
movl $PTE_IDENT_ATTR, %eax          //页目录中项的标识位
10:
leal PDE_IDENT_ATTR(%edi),%ecx        /* Create PDE entry */
movl %ecx,(%edx)                      /* Store identity PDE entry */
movl %ecx,page_pde_offset(%edx)       /* Store kernel PDE entry */
addl $4,%edx                          //下一个页表项的地址
movl $1024, %ecx                    //每个页表有1024项需要初始化
11:
stosl                               //存到页表里,edi指向的地方
addl $0×1000,%eax
loop 11b                             //这个循环对每张页表都会循环1024次, edi会自增。
/*
* End condition: we must map up to the end + MAPPING_BEYOND_END.
*/
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
cmpl %ebp,%eax
jb 10b
//开启分页机制
/*
* Enable paging
*/
movl $pa(swapper_pg_dir),%eax //页目录的地址
movl %eax,%cr3        /* set the page table pointer.. */
movl %cr0,%eax
orl  $X86_CR0_PG,%eax  //设置分页标识位。
movl %eax,%cr0        /* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f    /* Clear prefetch and normalize %eip */

这一部分源于 http://www.taobaodba.com/html/329_virtual_memory.html




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值