基本概念
数据总线
数据总线是计算机中各组成部件之间进行数据传输时的公共通道。“内数据总线宽度”是指CPU芯片内部数据传送的宽度;“外数据总线宽度”是指CPU与外部数据交换时的数据宽度。
地址总线
地址总线是載对存储器或I/O端口进行访问时,传送由CPU提供的要访问的存储单元或I/O端口的地址信息的总线,其宽度决定了处理器能直接访问的主存容量的大小。
现在的微型计算机系统采用下图的三级存储器组织结构,即缓冲存储器Cache、主存、和外存。高速缓冲存储器Cache的使用,大大减少了CPU读取指令和操作数所需的时间,使CPU的执行速度显著提高。
在硬件工程师和普通用户看来,内存就是固化在主板上的内存条,但在应用程序员眼中,并不关心主板上内存条的容量,而是他们可以使用的内存空间,而对于OS开发者来说,则是介于二者之间,既需要知道物理内存的细节,也要为应用程序员提供一个内存空间。逻辑地址、线性地址和物理地址是计算机原理中很重要的三个概念。这三者密切联系又有本质不同。从逻辑地址到物理地址经过了这样的变换:逻辑地址->分段->线性地址->分页->物理地址。
逻辑地址
逻辑地址(Logic Address)是指由程序产生的与段相关的偏移地址部分。比如,在C程序中,可以使用&操作读取指针变量本身的值,实际上这个值就是逻辑地址。它是相对于你当前进程数据段的地址,和绝对物理地址不相干。只有在intel实模式下,逻辑地址才等同于物理地址。应用程序员仅需要和逻辑地址打交道,分段和分页机制对他们来说是透明的,仅由系统编程人员涉及。编译好的程序的入口地址可以看作是首地址,逻辑地址通常可以认为是编译器为我们分配好的相对于这个首地址的偏移。一个逻辑地址由段标识符和段内偏移量组成。总之,逻辑地址是相对于应用程序而言的。 逻辑地址有时也称虚拟地址。
线性地址
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。 需要注意的是,线性地址是抽象的,对于同一平台是大小固定的,比如32位地址总线,线性地址空间为4GB,对于每个进程都是这样。也就是说线性地址空间不是进程共享的,而是进程隔离的,每个进程都有相同大小的线性空间,一个进程对某一线性地址的访问与其他进程对同一线性地址的访问不冲突,得到的值也不尽相同。对于CPU来说,某一时刻只有一个进程在运行,这个进程就认为自己独占4GB的线性空间。当进程切换的时候,线性空间也随之切换。尽管线性空间的大小和内存大小之间没有关系,但是任何线性地址最后还是要转换为物理地址才能被CPU使用去访问物理内存。基于分页机制,4GB的线性地址一部分被映射到物理内存,一部分被映射到磁盘上的交换文件,一部分什么也没有映射。 逻辑地址到线性地址的转换如下图:
物理地址
物理地址(Physical Address)是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
分段机制
分段提供了隔绝各个代码段/数据段和堆栈段的机制,因此多个任务可以运行在同一处理器上而不互相干扰。从逻辑地址(虚拟地址)到线性地址经过了分段处理。分断机制来源于早期的intel 8086处理器,早期的处理器工艺无法在一个处理器上面封装40引脚,8086处理器地址总线宽度为20位,而寄存器的宽度只有16位。20位可寻址空间为1MB,而16位的寄存器无法一次读取20位的地址,这就产生了矛盾。为了解决这一矛盾,使CPU能够寻址1MB空间,这就引入了分段机制。将段寄存器的地址左移4位,加上16位的偏移就形成了一个20位的地址。 在Linux中没有分段管理,逻辑地址就是线性地址。
分页机制
准确的说分页是CPU提供的一种机制,Linux只是根据这种机制的规则,利用它实现了内存管理。分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表。
为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。
32位的线性地址被分成3个部分:
最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。
页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位)叫做页目录项,页目录项里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。
页表的大小也是4k,同样包含1024项,每个项4字节叫做页表项,页表项的内容为最终物理页的物理内存起始地址。
最低的12位表示物理页内的偏移。
对于一个要转换成物理地址的虚拟地址,CPU首先根据CR3中的值,找到页目录所在的物理页。然后根据虚拟地址的第22位到第31位这10位(最高的10bit)的值作为索引,找到相应的页目录项(PDE,page directory entry),页目录项中有这个虚拟地址所对应页表的物理地址。有了页表的物理地址,根据虚拟地址的第12位到第21位这10位的值作为索引,找到该页表中相应的页表项(PTE,page table entry),页表项中就有这个虚拟地址所对应物理页的物理地址。最后用虚拟地址的最低12位,也就是页内偏移,加上这个物理页的物理地址,就得到了该虚拟地址所对应的物理地址。
下面是一个例子: