引用:Live and Learn html
以Intel的中央处理器为例,Linux 32位的系统中,物理内存的基本单位是字节(Byte),1个字节有8个二进制位。每一个内存地址指向一个字节,内存地址加1后获得下一个字节的地址。这里用以表示物理内存实际位置的地址,就是一般所说的物理地址(Physical Address)。CPU正在执行的进程代码、进程数据和栈区数据等,都临时保存在物理内存中。程序员
线性地址(Linear Address,亦即虚拟地址 Virtual Address)是出于如下考虑算法
(1) 隔离不一样进程使用的内存地址空间;(4) 扩展内存,即运行所需内层大于物理内存的程序编程
而在物理地址和程序之间增长的中间层。虚拟地址范围对应CPU的寻址能力,32位的CPU的虚拟地址范围为 0x00000000 ~ 0xFFFFFFFF,即最大虚拟内存为2^32 Bytes = 4GB;相应的64位CPU最大虚拟内存为 2^64 Bytes,然而实际上目前大部分操做系统和应用程序都不须要这样大的虚拟地址空间,而且64位长的地址会增长系统的复杂性和地址转换成本,所以目前的 x86-64架构只使用虚拟地址低位48位(0 ~ 47)做为虚拟地址,并用第47位的值填充48 ~ 63高位,所以64位CPU的最大虚拟内存为2^48 = 256TB。通常地,物理地址空间只是虚拟地址空间的一个子集。架构
为了提升内存管理效率,发挥虚拟空间的做用,可设定CPU的 CR0 寄存器的最高位(PG,分页标志位),启用分页机制将虚拟空间等分红若干页,而后按页帧管理和使用虚拟空间。物理内存规定的页大小有4096 Bytes,8192 Bytes,2MB, 4MB等,由于虚拟空间页中存储的内容其实是要放到物理内存上的,因此虚拟空间也采用上述大小进行分页。普通的分页大小采用4KB的标准。操作系统
现代计算机系统中,通常不须要程序员直接操做物理地址,而是由操做系统按页帧为进程分配执行用的虚拟地址。每一个页帧能够被映射到任何可用的物理内存 页。CPU 在执行程序进程时,CPU发出对相应的虚拟地址进行读或写操做,硬件设备(MMU,内存管理单元,通常都集成在CPU芯片上)分析虚拟地址后查询页表并计 算,将该虚拟地址映射为物理地址,而后经过北桥芯片(北桥芯片主要功能就是负责CPU和物理内存之间的通讯)链接内存总线,从而CPU可以访问到物理内存 中进程代码和数据。指针
逻辑地址(Linear Address)指的是程序内部的地址偏移量。该地址以操做系统为程序分配的程序入口地址为基准,指定程序中操做数或指令的地址。逻辑地址是程序员直接操纵的地址,例如在 C 语言编程中,定义一个 int 变量,而后使用取地址运算符(&var)获得的地址就是逻辑地址。htm
逻辑地址由两部分构成,分别是段选择符(Segment Selector)和段内偏移量(Offset),段选择符是一个16-bit (2字节)无符号数,段偏移量则是一个 32-bit 的无符号数。段选择符的内容以下图所示。blog
Figure 1 Segment Selector Fields<喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KCjxoMT4yLiDE2rTmudzA7daut9a2zrv61sY8L2gxPgoKPHA+ts6jqFNlZ21lbnRhdGlvbqOpysfU2rbOyr3E2rTmudzA7bXEuMXE7s/C0M6zybXEyvXT76Gjts7KvbncwO21xLv5sb7LvM/rysew0bPM0PLE2sjdu/K5/bPMudjPtbfWs8m2zqOswP3I57T6wuu2zqGiyv2+3bbOtcijrLLZ1/fPtc2zsLS2zs6qvfizzLfWxeTQ6cTitdjWt7/VvOSjrNXi0fmyu82svfizzLbOv9W85LK7zayjrMq1z9bBy734s8y49MDrus3E2rTmsaO7pKGjw7+49rbOtrzT0NfUvLq1xMPoyva3+6OoU2VnbWVudCBEZXNjcmlwdG9yLCA4LWJ5dGUgbG9uZ6Opo6zV4tCpw+jK9rf7sbuxo7Tm1NrIq77Ww+jK9rf7se2jqEdsb2JhbCBEZXNjcmlwdG9yIFRhYmxlLCBHRFSjqbvyvtayv8Poyva3+7Hto6hMb2NhbCBEZXNjcmlwdG9yIFRhYmxlLCBMRFSjqdbQoaO2zsPoyva3+7XExNrI3cjnzbwyy/nKvqGjPC9wPgogPGltZyBzcmM9"http://www.2cto.com/uploadfile/Collfiles/20141113/2014111309173162.png" alt="\"> 索引
Figure 2 Segment Descriptor
Base Address一共 32 bits,它指向当前段第一个字节的线性地址。Limit部分一共 20 bits,它指明本段虚拟空间最后一个字节相对第一个字节的偏移量,所以它也能表示段的长度。与页不一样(长度固定为4KB等),段的长度根据程序相应内容 变化。另外,若是标志位 G设定为0,那么偏移量每增长 1,地址值增长 1 byte,那么这时段的最大长度为 1 byte * 2^20 = 1MB;若是标志位 G设定为1,那么偏移量加1,地址值增长 4 KB,相应的这时段的最大长度为 4KB * 2^20 = 4GB。
在进程的执行过程当中,当遇到须要访问内存的指令时,首先根据逻辑地址获得相应的线性地址,而后再根据线性地址获得物理地址。根据逻辑地址获得线性地址的过程如图3所示。
Figure 3 Translating a Logical Address
CPU提供CS 寄存器临时保存正在执行的进程代码段的段选择符,DS 寄存器临时保存进程数据段的段选择符,以及SS寄存器临时保存栈区分段的段选择符。这样,在转换逻辑地址的时候,CPU根据当前保存在CS中的段选择器 (参见图1),其中 TI 标志肯定段描述符位于GDT仍是LDT,Index部分肯定段描述符在表(GDT或LDT)中的位置,从而能够找到逻辑地址对应的段描述符;根据段描述符 中的Base Address 找到段的起始线性地址,使用起始地址加上指令逻辑地址中的偏移量,就能获得指令所指向的实际线性地址。
因为分段机制和Intel处理器相关联,在其它的硬件系统上,可能并不支持分段式内存管理,所以在 Linux 中,操做系统倾向与使用分页的方式管理内存。在用户模式(User Mode)下,全部的进程共用用户代码段和用户数据段。用户模式下,全部进程使用代码段的段描述符的Base Address部分都指向线性地址0x00000000,同时数据段的段描述符的 Base Address部分也指向线性地址0x00000000;在内核模式(Kernel Mode)下,全部的进程共用内核代码段和内核数据段。内核全部进程使用代码段的段描述符的Base Address部分都指向线性地址0x00000000,同时数据段的段描述符的 Base Address部分也指向线性地址0x00000000。上述的段描述符的G位都设定为1,段对应的虚拟空间从0到2^32,对应整个32位CPU的最大 虚拟空间。
上述办法解决了其它硬件平台不支持段式管理的状况,大大简化了地址转换操做,可是因为理论上每一个进程的可用线性空间范围都是4G,即进程共用段表, 使用段界限隔离进程内存的目的就不能实现了。所以,在Linux中,为每一个进程分配独立的页表,纯粹依靠分页机制提供内存保护和进程隔离。接下来,针对分 页机制进行详细的说明。
分页机制将整个线性地址空间及整个物理内存当作由许多大小相同的存储块组成的,并把这些块做为页(虚拟空间分页后每一个单位称为页)或页帧(物理内存 分页后每一个单位称为页帧)进行管理。不考虑内存访问权限时,线性地址空间的任何一页,理论上能够映射为物理地址空间中的任何一个页帧。最多见的分页方式是 以 4KB 单位划分页,而且保证页地址边界对齐,即每一页的起始地址都应被4K整除。在4KB的页单位下,32位机的整个虚拟空间就被划分红了 2^20 个页。由于虚拟地址是按页所有被映射到相同大小的页帧,而且页面边界对齐,所以虚拟地址的后12位能够直接做为物理地址的低12位使用。
为了节省储存页表所需的内存空间(2^20 * 4B = 4M),32位操做系统常使用两级页表结构记载虚拟地址空间分页现状。所以每一个虚拟地址就由三部分组成,高10位是页目录(Page Directory)中内容的索引,中间10位是页表索引,低12位则做为对应物理地址在页帧中的偏移量。
Figure 4 Paging Mechanism
页目录保存在CR3寄存器中,能够直接访问。访问时以线性地址高10位做为索引,直接检索并获得对应索引的 32 位页目录项。32位页目录项的结构如图4中Page Directory部分所示,目录项的高20位用以给出该目录项对应的页表在内存中的物理地址的高 20 位,1024个目录项恰好能给出1024个页表的入口地址。目录项的低12位是一些标志位,其中P标志指明当前目录项对应的页表是否在内存中;U标志指明 当前目录项对应的页的访问权限;S标志指明页的大小是4KB或4MB,等等。另外,因为每一个页目录项的长度为32位,即4个字节,页目录中共有1024个 页目录项,因此页目录的总大小为 4KB。
页表保存在内存中。页表项的长度是32位,每一个页表中有1024个页表项,可得出每一个页表的大小是 4 KB。页表在内存中存放时,与物理分页的大小(4KB)对齐,因此每一个页表所在的物理内存的起始物理地址的后12位都是0。而该物理地址的高20位又由页 表对应的页目录项中的高20位指定,这样就能够获得找到物理内存中的页表了。找到页表后,以线性地址的中间10位为索引,检索到该索引对应的32位页表 项。和页目录项相似,页表项的高20用以给出其对应页帧的起始物理地址的高20位。页表项的低12位是关于页的标志位。
页帧对应物理内存。根据前面的两步找获得页帧的起始物理地址的高20位后,因为物理内存按4KB大小划分红页帧,因此页帧的起始物理地址的低12位 都是0。这样高20位加低12位,获得页帧的起始物理地址。找到页帧后,使用线性地址的低10位做为偏移量,加上页帧的起始物理地址后能找到线性地址对应 的物理地址了。须要注意的是,页帧和页表项的对应关系并非肯定的,页表项指向的页首先是虚拟页,而后该虚拟页的内容被储存在任何合适的页帧中。
操做系统按页为每一个进程分配虚拟地址范围,理论上根据程序须要最大可以使用4G的虚拟内存。但因为操做系统须要保护内核进程内存,因此将内核进程虚拟 内存和用户进程虚拟内存分离,前者可用空间为1G虚拟内存,后者为3G虚拟内存。进程执行时,操做系统为其分配的页的页目录会被加载到CR3寄存器,页表 会被加载到物理内存。分页单元将线性地址转换为物理地址的过程当中,会检查当前进程是否有访问该分页的权限,以及线性地址对应的页数据是否在物理内存中,如 果上述检查条件未被经过,分页单元将会生成页错误异常,进而停止进程或将相应分页数据加载到物理内存。
物理地址扩展(Physical Address Extension)是Intel 32位CPU上独有的一种虚拟地址分页方式。理论上,32位CPU有32条内存寻址线,最多能访问4G的物理内存;实际上在Linux系统中,用户模式程 序须要线性地址空间,所以内核最多只能直接访问的物理内存为1G。可是,随着计算机软件的发展,一台32位计算机上可能同时运行许多进程,而这些同时运行 的进程所需内存量会大于4G,所以Intel为其32位CPU增长了4条内存寻址线,共36条,这样CPU支持的物理内存增大到2^36,即64GB。扩 展物理内存的同时,保持虚拟地址空间范围为4G不变。从而使32位的应用程序继续使用32位的地址,每一个进程可以使用的最大虚拟内存还是4GB。
64GB的物理内存在4KB分页下,被分红2^24个页帧,每一个页帧的起始物理地址后12位仍然为0,但前24位则须要页表提供。而咱们知道,常规 分页中,页表项为32位,其中只能提供20位做为其指向页帧的高20位物理地址,不能知足36位系统的寻址须要。能够同经过增长页表项的总长度来解决这个 问题,为了保证以4KB的边界对齐,咱们将页表项的长度增长为64位,8字节(而不能是恰好知足须要的36位),页表大小保持4KB,那么一个页表中只有 512个页表项(2^12 / 8)。
相应地,页目录也要适应36位的物理内存寻址能力,每条页目录项长度也变成64位,页目录大小保持4KB,一个页目录中只有512个页目录项。这样一个页目录总共可检索 512 × 512=2^18个页,而虚拟地址空间共有 2^20个页,因此总共须要4个页目录。
一个新的分层被加入到CR3控制器和页目录之间,这个新的分层是页目录指针表(Page Directory Pointer Table)。页目录指针表中有四个长度为64位的指针,分别指向前述的4个页目录。页目录指针表被加载到64GB内存的第一个4GB上(物理地址 0x00000000 ~ 0xFFFFFFFF),CR3中则保存的是该页目录指针表的起始物理地址。
开启物理扩展寻址方式后,将线性地址转换为物理地址的方式和以前有较大不一样,具体过程如图5所示。Figure 5 Linear Address Translation with PAE
首先由CR3获得页面指针表的物理地址,而后以线性地址的30 ~ 31位做为索引获得页目录。接下来的21 ~39位(共9 bits,刚好提供所有512个页目录项的索引)能够帮助找到线性地址对应的页表,12 ~ 20(共9 bits,刚好提供所有512个页表项的物理地址)能够帮助找到线性地址对应的页帧的物理地址。
64位机的寻址能力为 2^64 Bytes,但实际中用不到这么多的虚拟内存,使用64位寻址方式还会形成寻址时间增长、内存空间浪费等不利因素,所以在实际应用中,对64位机使用48 位的寻址方式(最大支持256TB物理内存)。一样的,将物理内存分为4KB大小的页帧,那么就须要 48-12=36位物理地址高位来肯定页帧位置。为了减少储存页表所需的物理内存,实现内存权限访问,能够经过增长两个页目录层来分散页表。在Linux 中,采用4层分页的方式来实现该目的。
(1) 为进程建立一个独立的虚拟地址空间(范围)
例如在32位系统常规分页状态下,操做系统发现待执行程序的指令和数据总和为32KB,那么操做系统会为进程分配8个页的虚拟内存空间,并分配页目录和页表,把页目录装入CR3,把进程用到的页表加载到内存。但并不把指令和数据加载到内存。
(2) 读取程序可执行文件文件头,而且创建虚拟空间与可执行文件中的代码段、数据段的逻辑地址的映射关系这一步将程序指令和数据映射到虚拟内存空间中。
(3) 将 CPU 的指令寄存器设置成可执行文件的入口地址,启动运行
执行程序过程时,若是当前指令或数据之在虚拟地址空间中,而实际上并不在物理内存中(前两步都没有将指令或数据加载到物理内存),将发生页错误,这 时操做系统再从物理内存分配一个空闲的物理页帧,并将虚拟地址页对应的数据从磁盘拷贝加载到物理页帧中,并创建页表项和页帧的映射关系。随着进程的执行, 页错误也会不断产生,操做系统也会响应每一个页错误并为进程分配物理内存页帧。但物理内存是有限的,为一个进程可分配的物理内存也有限。所有可用物理内存都 分配给进程后,若是进程继续抛出页错误请求更多物理内存,这时候操做系统根据自身的页置换操做算法,在保证进程正常运行的前提下,将先前为进程分配的物理 内存页帧收回,从新分给该进程。