文章目录
- key words:
- 介绍
- **MMU**
- **物理地址**
- 线性地址
- 虚拟内存
- 逻辑地址
- 内存管理
- 内存及寻址
- 地址变换
- 地址变换
- 分段机制
- 分页机制
- 启用
- 分页机制实现线性地址到物理地址转换的过程
- 分页机制和分段机制的不同
- 页表结构
- 两级页表结构
- 不存在的页表
- 页表项格式
- 虚拟存储
- 任务之间的保护 和 特权级保护
key words:
物理地址: 内存单元所看到的地址,机器内主存的地址,包括RAM和ROM
逻辑地址: cpu生成的地址,程序运行在内存中,使用的地址,又称虚拟地址
线性地址:
逻辑地址:0~Max, 物理地址:R~R+Max
运行是从虚拟地址到逻辑地址的映射,由内存单元管理MMU设备来完成,
用户空间
内核空间
介绍
内存分配策略,连续内存分配是最为简单的方法之一,将内存分为多个固定大小的分区,每个分区只能容纳一个进程。分许中选择满足进程要求的内存块,常用的三个策略:1. 首次适应(第一块满足要求) 2.最佳适应(最小的足够大) 3. 最差适应(最大的)
首次适应和最佳适应都会有外部碎片问题,解决方法:紧缩,移动内存内容,一边所有空闲空间合并成一整块。
允许物理地址空间非连续,常见的有分页和分段
MMU
现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要处理器中的MMU(Memory Management Unit,内存管理单元)提供支持
首先引入两个概念,虚拟地址和物理地址。如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA)
物理地址
CPU通过地址来访问内存中的单元,地址有虚拟地址和物理地址之分,如果CPU没有MMU(Memory Management Unit,内存管理单元),或者有MMU但没有启用,CPU核在取指令或访问内存时发出的地址将直接传到CPU芯片的外部地址引脚上,直接被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA)
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
线性地址
如果CPU启用了MMU,CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址
MMU将虚拟地址映射到物理地址是以页(Page)为单位的,对于32位CPU通常一页为4K。例如,虚拟地址0xb700 1000 ~ 0xb700 1fff是一个页,可能被MMU映射到物理地址0x2000~0x2fff,物理内存中的一个物理页面也称为一个页框(Page Frame)
内核也不能直接访问物理地址.但因为内核的虚拟地址和物理地址之间只是一个差值0xc0000000的区别,所以从物理地址求虚拟地址或从虚拟地址求物理地址很容易,±这个差就行了
虚拟内存
这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不就是物理地址上那个大数组中0x08000000 - 1那个地址元素;
之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的
逻辑地址
Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些”
CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已
相对程序员而言使用的地址,或说程序无需知道具体的实际地址管理数,而只要在系统(操作)允许范围内使用就行了(这时使用的是一种算法控制下的地址,实际上它只是借用地址概念产生的程序运行模式),它所要说明的是方便,也就是一个线性的(最好)的程序(指f令)排列方式。它的大小一般由操作系统决定
内存管理
内存及寻址
内存是一组有序字节组成的数组, 每个字节有唯一的内存地址
内存寻址是指对储存在内存中的某个指定数据对象的地址进行定位, 数据对象是指储存在内存中的一个指定数据类型的数值或字符串
- 通常内存地址从0开始编址, 对于80X86来说, 地址总线宽度为32位, 因此一共有2^32个不同的物理地址, 即内存物理地址空间有4G
- 对于80X86, 一条指令主要由Opcode操作码 和 Oprand操作对象即操作数构成, 操作数可以位于一个寄存器中, 也可以在内存中. 若要定位内存中的操作数, 就要进行内存寻址, 针对寻址对象数据类型不同, 也有很多不同的寻址方案, 为了进行内存寻址,
X86 为段部分提供了6个存放段选择符的寄存器: CS,DS,ES,SS,FS. 其中CS用于寻址 代码段, 而对战段专门使用SS寄存器
- X86 使用了一种 **Segment 段** 的寻址技术, 它把内存空间分成一个或多个段的线性区域, 从而对内存中一个数据对象的寻址就需要使用一个段的**起始地址(段地址)**和一个**段内偏移地址**两部分构成.
- 段地址部分使用16位的段选择符指定, 其中14位可以选择2^14 (16384)个段
- 段内偏移地址可以是0~4G, 即一个段最大可达 4G
- 逻辑地址(虚拟地址), 它唯一确定了一个数据对象的段地址和段内偏移地址, 而仅由32位偏移地址或指针 指定的地址是基于当前段的对象地址
地址变换
任何完整的内存管理系统都包含两个关键部分: 保护和地址变换
- 提供保护措施是可以防止一个任务访问另一个任务或操作的内存区域
- 地址变换 能让操作系统在给任务分配内存时具有灵活性, 并且因为我们可以让某些物理地址不被任何逻辑地址所映射, 所以地址变换过程中同时也提供了内存保护功能
计算机中物理内存是字节的线性数组, 每个字节具有唯一的物理地址: 程序中的地址是由两部分组成的逻辑地址, 这种逻辑地址并不能直接访问物理地址, 而需要使用地址变换机制将他们变换或映射到物理内存地址上, 内存管理机制即用于将这种逻辑地址转换成物理内存地址
为了减少确定地址变换所需要的信息, 变换或映射同通常以内存块作为操作单位. 分段机制和分页机制是两种广泛使用的地址变换技术. 他们的不同之处在于逻辑地址是如何组织成被映射的内存块, 变换信息如果指定以及程序员如何操作. 分段和分页都使用驻留在内存中的表来指定他们各自的变换信息, 这些表只能由操作系统访问
地址变换
分段机制
把逻辑地址转换为线性地址
上图的分段机制,把处理器可寻址的地址空间划分成一些较小的称为段的受保护的地址空间区域。 用能够用来存放 程序代码、数据和堆栈,或者是系统的数据结构。运行多个程序的时候,给每个程序分配各自的段,确保各个程序不会互相干扰。 还可以对断进行分类,对不同类型的段分配不同的权限。
系统中所有使用的段都包含在处理器的线性地址空间中。 需要定位段中的某一个字节(这个就是寻址)的时候,程序必须要提供一个逻辑地址。 逻辑地址包含 一个段选择符 和一个偏移量 。
段选择符是在系统中是唯一的,在段选择符中提供了 段描述符的偏移量,段描述符是段描述符表中的一个数据结构。 每一个段都有一个段描述符,说明了 段的大小 、访问权限、 段的特权机、 段类型 和 段的基地址(段的第一个字节在星星地址空间中的位置,也叫段地址)
逻辑地址的偏移量(即偏移地址)加上段地址就可以定位到段中某一个字节的位置。 因此 段地址 + 偏移地址 就是处理器线性地址空间中的地址(线性地址)。
线性地址和物理地址具有相同的数据结构,对于两维的逻辑地址空间来说(为什么是两维? 段地址和偏移地址各算一维吗?), 它们都是一维的。
虚拟地址(逻辑地址)空间最多只能有 16k个段(2^14= 16348个段)分为两部分,前一半为全局地址空间,后一半为局部地址空间,每个段最长为 4GB ,这样虚拟地址空间最大就可以达到 64TB(2^14 ∗ 2^32 = 2^46)。 线性地址空间和物理地址空间都是 4GB 。 如果禁用了分页机制,那么线性地址空间就是物理地址空间。
分页机制
把线性地址转换为物理地址, 处理器分页机制会把线性地址空间划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上
多任务系统通常定义的线性地址空间比实际的物理内存大很多, 所以需要使用某种“虚拟化”线性地址空间的方法,即虚拟存储技术。 虚拟存储是一种内存管理技术,使用这种技术可以让编程人员产生内存空间要比实际物理内存大很多的错觉。
分页机制支持虚拟存储技术。使用小块的物理内存(RAM 或 ROM)和某些外部存储空间(如硬盘)来模拟大容量的线性地址。 把每个段划分成相同大小(通常每页为 4KB)的页,操作系统维护着一个页目录和一些页表,当程序要访问线性地址空间中的某一个位置时,处理器就会根据页目录和页表把线性地址转换成一个物理地址,然后就可以在该内存位置上进行读写操作了。 如果当前被访问的页面不在物理内存中,处理器就会产生一个页错误异常来中断程序的执行,然后操作系统就可以从硬盘上把该页面读入物理内存中,并继续执行被中断的程序。如果操作系统严格地实现了分页机制,那么对于正确 执行的程序来说页面在物理内存和硬盘之间的交换就是透明的。
分页机制使用了大小固定的内存块,而分段机制使用了大小可变的内存,不管在物理内存上还是在硬盘上,分页使用固定大小的块更为适合物理内存,分段机制使用大小可变的块更为适合处理复杂系统的逻辑分区,可以定义与逻辑块大小适合内存单元而不用受到固定大小的页面的限制,每个段都可作为一个单元来处理,简化了段的保护和共享。
启用
通过设置控制寄存器 CR0 的 PG 为可以启用分页机制。
如果 PG=1, 则启用分页操作,处理器会使用分页机制将线性地址转换成物理地址。
如果 PG=0, 则禁用分页机制,分段机制产生的线性地址直接用作物理地址。
分页机制实现线性地址到物理地址转换的过程
80x86使用 4K 字节固定大小的页面,每个页面均是 4KB,并且对其于 4K 地址边界处。
这表示分页机制把 2^32字节(4GB)的线性地址空间划分成** 2^20**(1M = 1048576)个页面。分页机制通过把线性地址空间中的页面重新定位到物理地址空间中进行操作。
由于 4K 大小的页面作为一个单元进行映射,并且对其于 4K 边界,因此线性地址的低 12 位可做为页内偏移量直接作为物理地址的低 12 位。分页机制执行的重定向功能可以看作是把线性地址的高 20 位转换到对应物理地址的高 20 位。
线性到物理地址转换功能,被扩展成允许一个线性地址被标注为无效的,而非要让其产生一个物理地址。以下两种情况一个页面可以被标注为无效的:
- 操作系统不支持的线性地址
- 对应的虚拟内存系统中的页面在磁盘上而非在物理内存中 在第一中情况下,产生无效地址的程序必须被终止 在第二种情况下,该无效地址实际上是请求 操作系统虚拟内存管理器
把对应的页面从磁盘加载到物理内存中,以供程序访问。因为无效页面通常与虚拟存储系统相关,因此它们被称为不存在页面,由页表中称为存在的属性来确定
当使用分页时,处理器会把线性地址空间划分成固定大小的页面(4KB),这些页面可以映射到物理内存中或磁盘存储空间中,当一个程序引用内存中的逻辑地址时,处理器会把该逻辑地址转换成一个线性地址,然后使用分页机制把该线性地址转换成对应的物理地址。
如果包含线性地址的页面不在当前物理内存中,处理器就会产生一个页错误异常。页错误异常处理程序就会让操作系统从磁盘中把相应页面加载到物理内存中(操作过程中可能会把物理内存中不同的页面写到磁盘上)。当页面加载到物理内存之后,从异常处理过程的返回操作会使异常的指令被重新执行。 处理器把用于线性地址转换成物理地址和用于产生页错误的信息包含在存储与内存中的页目录与页表中。
分页机制和分段机制的不同
分页与分段的最大的不同之处在于分页使用了固定长度的页面。
段的长度通常与存放在其中的代码或数据结构有相同的长度。与段不同,页面有固定的长度。
如果仅使用分段地址转换,那么存储在物理内存中的一个数据结构将包含其所有的部分。如果使用了分页,那么一个数据结构就可以一部分存储与物理内存中,而另一部分保存在磁盘中。
为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的一个叫做转换查找缓冲区(TLB)的缓冲器件中。 TLB 可以满足大多数读页目录和页表的请求而无需使用总线周期。 只有当 TLB 中不包含所要求的页表项是才会出现使用额外的总线周期从内存读取页表项。通常在一个页表项很长时间没有访问过时才会出现这种情况。
页表结构
分页转换功能由驻留在内存中的表来描述,该表称为页表,存放在物理地址空间中。
页表可以看作是简单的 2^20 物理地址数组。 线性到物理地址的映射功能可以简单地看作进行数组查找。 线性地址的高 20 位构成这个数组的索引值,用于选择对应页面的物理(基)地址。
线性地址的低 12 位给出了页面中的偏移量,加上页面的基地址最终形成对应的物理地址。由于页面基地址对齐在 4K 边界上,因此页面基地址的低 12 为肯定是 0 ,这意味着高 20 位的页面基地址 和 12 位偏移地址连接组合在一起就能得到对应的物理地址。
页表中每个页表项 大小为 32 位,由于只需其中的 20 位来存放页面的物理基地址,因此剩下的 12 位可用于存放诸如页面是否存在等的属性信息。如果线性地址索引的页表被标注为存在,则表示该项有效, 我们可以从中取得页面的物理地址。 如果项中表明不存在,那么当当访问对应物理界面时就会产生一个异常。
两级页表结构
页表含有 2^20 个表项,而每项占用 4 个字节。如果作为一个表来存放,最多将占用 4M 的内存。 为了减少内存占用量,80x86 使用了两级页表。 高 20 位线性地址到物理地址的转换被分成两步来进行,每次转换其中 10 位。
- 第一级表称为页目录,存放在一页 4K 大小的页面中,具有 2^10 个 4 字节长度的表项。 这些表象指向对应的二级表。 线性地址的最高 10 位(31-22)用作以及表中的索引。
- 第二级称为页表,长度也是 4K 大小的一个页面,最多有 1K 个 4 字节的表项。 每个 4 字节的表项含有相关页面的 20 位物理基地址。 二级页表使用线性地址的中间 10 位(21-12)作为表项索引值,以获取含有页面 20 物理地址基地址的表项。 该20位页面物理基地址和线性地址中的低12位(页内偏移)组合在一起就得到了分页转换过程的输出值,即对应的的最终物理地址。
二级页表的查找过程。其中 CR3 寄存器指定页目录表的基地址。线性地址的高10位用于索引这个页目录表,以获得指向相关第二级页表的指针。线性地址空间中间10位用于索引二级页表,以获得物理地址的高20位。线性地址的低12位直接作为物理地址的低12位,从而组成一个完整的32位物理地址。
不存在的页表
二级页表结构允许页表被分散在内存各个页面中,而不需要保存在连续的 4MB 内存块中。另外,并不需要为不存在的 或 线性地址空间未使用部分分配二级页表。 虽然目录表页面必须常驻内存中,但是二级页表可以在需要时再分配。这使得页表结构的大小对应于实际使用的线性地址空间大小。
页目录表中每个表项也有一个存在属性,类似于页表中的表项。页目录表项中的存在属性指明对应的二级页表是否存在。如果目录表指定的二级页表存在,那么通过访问二级表,表查找过程第二步将正常进行。如果存在位表明对应的二级表不存在,那么处理器就会产生一个异常来通知操作系统。页目录表项中的存在属性使得操作系统可以根据实际使用的线性地址范围来分配二级页表页面。
目录表项中的存在位还可用于在虚拟内存中存放二级页表。这意味着在任何时候只有部分二级页表需要存放在物理内存中,而其余的可保存在磁盘上。处于物理内存中页表对应的页目录项将被标注为存在,以表明可用它们进行分页转换。处于磁盘上的页表对应的页目录项将被标注为不存在。由于二级页表不存在而引发的异常会通知操作系统把缺少的页表从磁盘上加载进物理内存。把页表存储在虚拟内存中减少了保存分页转换所需要的物理内存量。
页表项格式
页目录和页表的表项格式如下图所示,其中位32-12含有物理地址的高20位,用于定位物理地址空间中一个页面(也叫页帧)的物理基地址。表项的低 12 位含有页属性信息。
- P 位0是存在标志,用于指明表项对地址转换是否有效。 P=1 表示有效;P=0 表示无效。在页转换过程中,如果涉及的页目录或页表的表项无效,则会导致一个异常。 如果 P=0,那么除表示表项无效外,其余位可供程序自由使用,如图中 b 所示
- R/W 位1是读/写标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行。当处理运行在超级用户特权级(级别0、1或2)时,则R/W位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。
- U/S 位2是用户/超级用户标志。如果为1,那么运行在任何特权级的程序都可以访问该页面,如果是0,那么页面只能被允许在超级特权级上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。
- A 位5是已访问标志。当处理器访问页表项映射的页面时,页表项的这个标志就会被置为1.当处理器访问页目录表项映射的任何页面时,页表目录项的这个标志就会被置为1。处理器只负责设置该标志位,操作系统可通过定期地复位该标志来统计页面的使用情况。
- D 位6是页面已被修改标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。
- AVL 该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。
虚拟存储
页目录和页表表项中的存在标志 P 为使用分页技术的虚拟存储提供了必要的支持。若线性地址空间中的页面存在于物理内存中,则对应表项中的标志 P=1,并且该表项中含有相应的物理地址。页面不在物理内存中的表项其标志 P=0。 如果程序访问物理内存中不存在的页面,处理器就会产生一个缺页异常。此时操作系统就可以利用这个异常处理过程把缺少的页面从磁盘上调入物理内存中,并把相应物理地址存放在表项中,最后在返回程序重新执行引起异常的指令之前设置标志 P=1。
已访问标志 A 和已修改标志 D 可以用于有效地实现虚拟存储技术。通过周期性地检查和复位所有 A 标志,操作系统能够确定哪些页面最近没有访问过,这些页面可以成为移出到磁盘上的候选者。假设当一页面从磁盘上读入内存时,其脏标志 D=0,那么当页面再次被移出到磁盘上时,若 D标志还是0,则该页面就无需被写入磁盘中(因为没有被改)。若此时 D=1,则说明页面已经被修改过,于是就必须将该页面写到磁盘上。
任务之间的保护 和 特权级保护
- 任务之间的保护:给每个任务不同的逻辑地址空间来完全隔离各个任务,通过把每个任务的逻辑地址映射到不同的物理地址来实现 。
把每个任务放在不同的虚拟地址空间中,并对每个任务设置不同的 逻辑地址到物理地址变换映射的方式,一个任务的逻辑地址被映射到物理内存的一部分区域,另一个任务的逻辑地址又被映射到物理内存的另一个区域。 一个任务的逻辑地址不会映射到 其它任务的逻辑地址 所映射的那一片物理内存区域,这样所有的任务都被隔绝开了。 只需要给每个任务各自独立的映射表,每个任务就会有不同的地址变换函数。在80X86中,每个任务都有自己的段表和页表,当处理器切换去执行一个新的任务时,任务切换的关键就是切换到新任务的变换表。
在所有任务中安排相同的虚拟地址到物理地址映射部分,并且把操作系统存储在这个公共的虚拟地址空间部分,就实现了操作系统可以被所有任务共享。这个被所有任务都具有的 相同虚拟地址空间部分叫做全局地址空间。这也是现代Linux操作系统使用虚拟地址空间的部分。
每个任务唯一的虚拟地址空间叫做局部地址空间 。局部地址空间含有需要与系统中其它任务区别开的私有的代码和数据。由于每个任务具有不同的局部地址空间,因此两个不同的任务对相同虚拟地址处的引用将转换到不同的物理地址。 这就是操作系统可以给 每个任务相同的虚拟地址,但仍然能隔绝每个任务。 另一方面,所有任务在全局地址空间中对相同虚拟地址的的引用将被转换到同一个物理地址,这就给公共代码和数据(如操作系统)的共享提供了支持。
- 特权级保护
在一个任务中,定义了 4 个执行特权级,用来 依据段中含有的数据的敏感度 和 任务中不同程序部分的 受信程度,来限制对任务中各段的访问。最敏感的数据被赋予最高特权级,它们只能被任务中最受信任的部分访问。不太敏感的数据被赋予较低的特权级,它们可以被任务中较低特权级的代码访问。
特权级用数字 0 到 3 表示, 0 具有最高特权级,而 3 则是最低特权级。每个内存段都与一个特权级相关联。 这个特权级限制了只有具有足够特权级的程序才能访问这个段。 处理器从 CS 寄存器指定的段中取得和执行指令,当前特权级即CPL 就是当前活动代码段的特权级,它定义了当前所执行程序的特权级别,确定了哪些段能够被程序访问。
每当程序企图访问一个段时,当前特权级就会与段的特权级进行比较,以确定是否有访问许可。 在给定的 CPL 级别上执行的程序 能够访问同级别或低级别的数据段, 任何对高级别段的引用都是非法的,并且会引发一个异常来通知操作系统。 每个特权级都有自己的程序栈,以避免使用共享栈带来的保护问题。当程序从一个特权级切换到另一个特权级上执行时,堆栈段也随着改变到新级别的堆栈中。