几乎每一本将操作系统原理的书籍都会谈及内存管理方法的段页式存储。以前看书都是看的云里雾里!原因就是还没有懂INTEL指令,不知道页式存储有什么作用。国内教材有个最大的弊病就是,作者通常先将概念,然后再将如何运用,很少去提及概念是如何被提出来!为什么非要提出这样一个概念。
除非你已经被一个问题困扰到茶饭不思、辗转难眠,否则你绝对不会明白一个新的概念对你有多大的作用
分段还是分页
分段还是分页其实不是两个不同的概念!分段,其中心思想是将程序分成若干的逻辑段,每个段实现什么功能,提供什么资源。这个其实和后来面向对象的类的思想没有任何区别,仅仅是表现不一样罢了。在过去编程时,将程序分为若干逻辑段(数据段,32位代码段,16位代码段等),然后通过设置CS来实现段之间的跳转。这种方式使得程序在设计期间逻辑清晰。但是这种方式,程序是一个线性的指令集合,某些时候,人们需要同时执行两个任务,采用分段需要频繁的切换CS,这样造成很大的性能损耗。INTEL为了满足这种能够新增加一组寄存器 CR0、1、2、3来实现任务跳转。这个使用同样一个逻辑地址就能定位为不同的物理地址,如下图
对于同样一个逻辑地址10000,可以使用不同的分页转换,将逻辑地址转化为不同的物理地址!
注:分段和分页并不是对立的两个概念,分页其实建立在分段的基础之上!
如何分页
请思考如下一个数学题
y=f(x) {x的定义域为[0,π/2]} ,找到一个函数使得f(x)的值域为[0,1]?
如果你学过三角函数马上就会想到 f(x)=sin(x)
用在分页机制里,该数学题变为了
y=f(x) {x的定义域为[0,4.4M] 用4.4M的虚拟地址范围 },找到一个函数使得f(x)的值域为[0,4G]---32位CPU内存寻址范围?
解析:
这里当然可以使用一个函数来实现这种扩展,但是在计算机里面,没有使用函数,而是使用索引的方式来表示一个更大的范围,所谓索引,就是新华字典里面的查询页!一本字典那么厚,但是可以使用几页查询页就能找到字典里面的任何一个字!
在INTEL x86CPU保护模式里,一页通常值为4K(4K=2的12次方,需要12位来表示页内偏移地址),所以4G就是1M(1M=2的20次方)个4K,1M个页索引。这样一个物理地址就可以转化为
20位页索引+12位页内:需要用1M个索引项去记录每一页的属性!INTEL为了减小1M这个值,又将1M索引再次来个索引,将1M个索引按照1K个索引为1段,总共就是1K段!这样一个屋里地址就转化为:
10位页索引的索引+10位页索引+12位页内偏移地址,使用两级索引来表示一个地址,地址转换过程如下:
10位页索引的所有集合(1M个索引)有个专门的名字:页表,表中的每一项也就是每一个索引叫做页表项PTE(page table entry)
10位页索引的索引的所有集合(1K个)有个专门的名字:页目录表,表中的每一项也就是每一个索引的索引叫做页目录表项PDE(page directory entry)
有一点值得强调,页表项和页目录表项不仅仅只有索引,只是上面为了理解简单而省略一些信息,他们都有各种的属性!
每个项需要4个字节,总共就需要 4*1K(1K个页目录表项)+4*(1M个页表项) = 4.4M!
实际上启动分页的过程,也就是将在内存中初始化f(x)的一个过程!具体而言就是在内存中为每一个物理页做索引,加属性的过程!
当这些信息都被正确的在内存中初始化完毕之后,最重要的一步,就可以将信息的首地址复制给CR3,然后将CR0最高位设置为1就表示启动了分页机制!
分页还是不分页,分段还是不分段,保护模式还是实模式追索到源头其实就是对CPU指令集的一个应用,CPU提供了某些功能,然后由此编写的操作系统就启用这些功能!所以不是操作系统多么牛逼,而是处理器多么厉害
分页有多厉害
详细解读一下这句话:将信息的首地址复制给CR3。
CPU在将一个给定的虚拟地址进行地址转换的时候,首先是寻找CR3给定的页目录地址,找到之后,然后按照那个目录地址开始一级级的转换,这里你可能就会问,那是不是我随便给定一个CR3地址,那不是同一个地址会被转换为随便一个位置哦?是啊,就有这么牛逼!不同的页目录,就有不同的地址,操作系统实现的多任务,虚拟地址空间就是通过设置不同的CR3来实现!现在是否能够明白windows操作系统的每个进程寻址空间呢?在虚拟地址框架下,不管你如何寻址,你肯定找不到别人进程的代码,除非你改变CR3的值!
此时你看看下面这张图:
是不是觉得浅显易懂了呢?
最后附上一段代码,摘抄自《一个操作系统的实现》
; 启动分页机制 -------------------------------------------------------------- SetupPaging: ; 为简化处理, 所有线性地址对应相等的物理地址. ; 首先初始化页目录 mov ax, SelectorPageDir ; 此段首地址为 PageDirBase mov es, ax mov ecx, 1024 ; 共 1K 个表项 xor edi, edi xor eax, eax mov eax, PageTblBase | PG_P | PG_USU | PG_RWW .1: stosd add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. loop .1 ; 再初始化所有页表 (1K 个, 4M 内存空间) mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase mov es, ax mov ecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页 xor edi, edi xor eax, eax mov eax, PG_P | PG_USU | PG_RWW .2: stosd add eax, 4096 ; 每一页指向 4K 的空间 loop .2 mov eax, PageDirBase mov cr3, eax mov eax, cr0 or eax, 80000000h mov cr0, eax jmp short .3 .3: nop ret ; 分页机制启动完毕 ----------------------------------------------------------