技术开发 频道

虚拟内存机制浅析

  下面看看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 $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 */

0
相关文章