启用分页机制

启用分页机制

系统开启的时候默认是实地址模式,那么如何从实地址模式到内存分页机制呢?这篇文章将详细描绘这个转换的过程,并且具体的分析内存布局。

我们以时间的先后顺序,来阐明整个过程:

1:页表(pg0,pg1映射8M)的初步初始化,具体干嘛稍后再说

/*  * The page tables are initialized to only 8MB here -the final page

  * tables are set up later depending on memorysize */

.org 0x2000

ENTRY(pg0)

 .org 0x3000

ENTRY(pg1)

 /* * empty_zero_page must immediately followthe page tables ! (The

  * initialization loop counts untilempty_zero_page)  */

 .org 0x4000

ENTRY(empty_zero_page)

  /** Initialize page tables*/

         movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables */

         movl $007,%eax          /*"007" doesn't mean with right to kill, but

                                   PRESENT+RW+USER*/

 2:      stosl

         add $0x1000,%eax

         cmp $empty_zero_page-__PAGE_OFFSET,%edi

         jne 2b

内核的这段代码执行时,因为页机制还没有启用,还没有进入保护模式,因此指令寄存器EIP中的地址还是物理地址,但因为pg0中存放的是虚拟地址(想想gcc编译内核以后形成的符号地址都是虚拟地址),因此,“$pg0-__PAGE_OFFSET ”获得pg0的物理地址,可见pg0存放在相对于内核代码起点为0x2000的地方,即物理地址为0x00102000,而pg1的物理地址则为0x00103000。

Pg0和pg1这个两个页表中的表项则依次被设置为0x007、0x1007、0x2007等。

其中最低的三位均为1,表示这两个页为用户页,可写,且页的内容在内存中。所映射的物理页的基地址则为0x0、0x1000、0x2000等,也就是物理内存中的页面0、1、2、3等等,共映射2K个页面,即8MB的存储空间。由此可以看出,Linux内核对物理内存的最低要求为8MB。紧接着存放的是empty_zero_page页(即零页),零页存放的是系统启动参数和命令行参数。

 

2 启用分页机制

/*  * This is initialized to create an identity-mapping at0-8M (for bootup

  * purposes) and another mapping of the 0-8M area at virtual address

  * PAGE_OFFSET.*/

.org 0x1000

ENTRY(swapper_pg_dir)//设置swapper_pg_dir

         .long0x00102007

         .long0x00103007

         .fillBOOT_USER_PGD_PTRS-2,4,0

         /* default:766 entries */

         .long0x00102007

         .long0x00103007

         /* default:254 entries */

        .fillBOOT_KERNEL_PGD_PTRS-2,4,0

/*  * Enable paging */

3:

       movl $swapper_pg_dir-__PAGE_OFFSET,%eax

       movl %eax,%cr3          /*set the page table pointer.. */

       movl %cr0,%eax

       orl $0x80000000,%eax

       movl %eax,%cr0          /*..and set paging (PG) bit */

       jmp 1f                  /*flush the prefetch-queue */

1:

       movl $1f,%eax

       jmp *%eax            /*make sure eip is relocated */

1:

我们先来看这段代码的功能。这段代码就是把页目录swapper_pg_dir的物理地址装入控制寄存器cr3,并把cr0中的最高位置成1,这就开启了分页机制。

但是,启用了分页机制,并不说明Linux内核真正进入了保护模式。

因为此时,指令寄存器EIP中的地址还是物理地址,而不是虚地址。“jmp 1f”指令从逻辑上说不起什么作用,但是,从功能上说它起到丢弃指令流水线中内容的作用(这是Intel在i386技术资料中所建议的),因为这是一个短跳转,EIP中还是物理地址。紧接着的mov和jmp指令把第二个标号为1的地址装入EAX寄存器并跳转到那儿。在这两条指令执行的过程中, EIP还是指向物理地址“1MB+某处”。因为编译程序使所有的符号地址都在虚拟内存空间中,因此,第二个标号1的地址就在虚拟内存空间的某处((PAGE_OFFSET+某处),于是,jmp指令执行以后,EIP就指向虚拟内核空间的某个地址,这就使CPU转入了内核空间,从而完成了从实模式到保护模式的平稳过渡。

然后再看页目录swapper_pg_dir中的内容。从前面的讨论我们知道pg0和pg1这两个页表的起始物理地址分别为0x00102000和0x00103000。页目录项的最低12位用来描述页表的属性。因此,在swapper_pg_dir中的第0和第1个目录项0x00102007、0x00103007,就表示pg0和pg1这两个页表是用户页表、可写且页表的内容在内存。

接着,把swapper_pg_dir中的第2~767共766个目录项全部置为0。因为一个页表的大小为4KB,每个表项占4个字节,即每个页表含有1024个表项,每个页的大小也为4KB,因此这768个目录项所映射的虚拟空间为768′1024′4K=3G,也就是swapper_pg_dir表中的前768个目录项映射的是用户空间。

最后,在第768和769个目录项中又存放pg0和pg1这两个页表的地址和属性,而把第770~1023共254个目录项置0。这256个目录项所映射的虚拟地址空间为256′1024′4K=1G,也就是swapper_pg_dir表中的后256个目录项映射的是内核空间。


由此可以看出,在初始的页目录swapper_pg_dir中,用户空间和内核空间都只映射了开头的两个目录项,即8MB的空间,而且有着相同的映射。

内核开始运行后运行在内核空间,那么,为什么把用户空间的低区(8M)也进行映射,而且与内核空间低区的映射相同?简而言之,是为了从实模式到保护模式的平稳过渡。具体地说,当CPU进入内核代码的起点startup_32后,是以物理地址来取指令的。在这种情况下,如果页目录只映射内核空间,而不映射用户空间的低区,则一旦开启页映射机制以后就不能继续执行了,这是因为,此时CPU中的指令寄存器EIP仍指向低区,仍会以物理地址取指令,直到以某个符号地址为目标作绝对转移或调用子程序为止。

在开启了页式机制,而EIP中依旧为物理地址的情况下,映射过程如下事例:

假如EIP指向0x0010 0010,即:0000 0000 0001 0000 0000 0000 0001 0000

前十位为0 ,既swapper_pg_dir[0]也就是0x00102007,中间十位既01 0000 0000,获取pg0[256],其所指向的物理页面首地址为256*4KB=1M,最后1M+1 0000就是0x0010 0010。

所以,Linux内核就采取了上述的解决办法。   

但是,在CPU转入内核空间以后,应该把用户空间低区的映射清除掉。后面读者将会看到,页目录swapper_pg_dir经扩充后就成为所有内核线程的页目录。在内核线程的正常运行中,处于内核态的CPU是不应该通过用户空间的虚拟地址访问内存的。清除了低区的映射以后,如果发生CPU在内核中通过用户空间的虚拟地址访问内存,就可以因为产生页面异常而捕获这个错误。

经过这个阶段的初始化,初始化阶段页目录及几个页表在物理空间中的位置如图所示:


其中empty_zero_page中存放的是在操作系统的引导过程中所收集的一些数据,叫做引导参数。因为这个页面开始的内容全为0,所以叫做“零页”,代码中常常通过宏定义ZERO_PAGE来引用这个页面。不过,这个页面要到初始化完成,系统转入正常运行时才会用到。

在PC中,对于最初1MB存储空间的使用是特殊的。开头640KB(0x0~0x9FFFF为RAM,从0xA0000开始的空间则用于CGA、EGA、VGA等图形卡。现在已经很少使用这些图形卡,但是不管是什么图形卡,开机时总是工作于EGA或VGA模式。从0xF0000开始到0xFFFFF,即最高的4KB,就是在EPROM或Flash存储器中的BIOS。所以,只要有BIOS存在,就至少有两个区间,如果nr_map小于2,那就一定出错了。由于BIOS的存在,本来连续的RAM空间就不连续了。当然,现在已经不存在这样的存储结构了。1MB的边界早已被突破,但因为历史的原因,把1MB以上的空间定义为“HIGH--_MEMORY”,这个称呼一直沿用到现在,于是代码中的常数HIGH--_MEMORY就定义为“1024′1024”。现在,配备了1G,2G的内存已经是很普遍了。但是,为了保持兼容,就得留出最初1MB的空间。

 

内核映象在物理内存中的分布


符号_text对应物理地址0x00100000,表示内核代码的第一个字节的地址。内核代码的结束位置用另一个类似的符号_etext表示。内核数据被分为两组:初始化过的数据和未初始化过的数据。初始化过的数据在_etext后开始,在_edata处结束,紧接着是未初始化过的数据,其结束符号为_end,这也是整个内核映像的结束符号。

图中出现的符号是由编译程序在编译内核时产生的。你可以在System.map文件中找到这些符号的线性地址(或叫虚拟地址),System.map是编译内核以后所创建的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值