注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

lazydba

hello

 
 
 

日志

 
 

虚拟内存机制浅析  

2009-08-29 08:31:05|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
现在的操作系统基本上都实现了虚拟内存的功能。本文就简单的记录一下我对虚拟内存的理解。
1. 虚拟内存的作用
1.1 便于写程序,特别是多进程环境下的程序。
想象一下,如果每个程序都要在固定的物理地址执行,读取指定物理地址处的数据,那么多个程序如何共存,数据如何保护?有了虚拟内存这一层,每个程序都运行在自己相对独立的虚拟空间中,不需要考虑别的程序。

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

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

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

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

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

下面看看32位X86下的Linux是怎么做的,先上图,是不是看起来很熟悉?



这个图是从Intel的手册里rob来的(Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A)。
虚拟地址到物理地址的转换,是CPU的核心功能之一,操作系统利用了CPU的功能。

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

页目录和每个页表的大小都是4k,而每一页内存也是4k,不知道这是否是一种巧合?

页表可以动态分配,只有需要访问的虚拟地址的映射关系才会存到页表里,这样如果我只需要访问一小部分空间时,并不需要分配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 $0x1000,%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 */


3. 其它考虑
3.1
虚拟内存到物理内存的映射表,可以有其它的实现方法。
上面提到的32位的地址空间用了2级的映射表,如果地址空间扩展到64位,要如何实现?映射表要占用多少空间?

其实有一种数据结构叫做Hash表,Oracle的buffer cache,library cache都用到了这个,是否有CPU用这种方式的映射表?

3.2
地址转换消耗其实是比较大的,CPU有一个专门用来Cache映射表的TLB,用硬件实现,加速地址转换。Cache无所不在。

3.3
x86的cpu即支持分页,也支持分段,分页是分段寄出上的分页,为了简化,上面没有提。

3.4
物理内存如何管理?最简单的可以对物理内存的每一页做个位图映射。

3.5
每个进程都有自己的页表,进程越多,页表占用的总的空间越多。
如果每个进程需要访问相同的物理内存,那么它们是否可以公用页表呢?

比如Oracle的SGA,需要访问SGA的进程可能成百上千(假设1k),而SGA可能是相当大的(假设4G),导致页表相当大(4M),如果每个进程都要自己的页表,那么这些进程的页表占用的空间就要4G(1k*4M),而其实它们的页表的内容应当是一样的,操作系统是否会对这种情况做一个优化呢?




  评论这张
 
阅读(99)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017