深入理解计算机系统(原书第3版)读书笔记,其实就是嚼碎了原文然后把一部分挑了出来摘要,免得读着读着忘了
文章目录
- 前言
- 一、物理和虚拟寻址
- 二、地址空间
- 三、虚拟内存作为缓存的工具
- 1、DRAM缓存的组织结构
- 2、页表
- 3、页命中
- 4、缺页
- 5、分配页面
- 6、局部性
- 四、虚拟内存作为内存管理的工具
- 五、虚拟内存作为内存保护的工具
- 六、地址翻译
- 1、结合高速缓存和虚拟内存
- 2、利用TLB加速地址翻译
- 3、多级页表
- 4、端到端的地址翻译
- 七、 案例研究:Intel Core i7/Linux 内存系统
- 1、Core i7 地址翻译
- 2、Linux 虚拟内存系统
- 八、内存映射
- 1、再看共享对象
- 2、再看fork函数
- 3、再看execve函数
- 4、使用mmap函数的用户级内存映射
- 九、动态内存
- 总结
前言
- 虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
- 虚拟内存遍及计算机系统的所有层面,在硬件异常、汇编器、链接器、加载器、共享对象、文件和进程的设计中扮演着重要角色。理解虚拟内存将帮助你更好地理解系统通常是如何工作的。
这一章从两个角度来看虚拟内存。本章的前一部分描述虚拟内存是如何工作的。后一部分描述的是应用程序如何使用和管理虚拟内存。好消息就是如果你掌握这些细节,你就能够手工模拟一个小系统的虚拟内存机制,而且虚拟内存的概念将永远不再神秘。第二部分是建立在这种理解之上的,向你展示了如何在程序中使用和管理虚拟内存。你将学会如何通过显式的内存映射和对象 malloc 程序这样的动态内存分配器的调用来管理虚拟内存。你还将了解到 C 程序中的大多数常见的与内存有关的错误,并学会如何避免它们的出现。(饼画的我很喜欢)
一、物理和虚拟寻址
计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组成的数组。都有一个唯一的物理地址(Physical Address)。
将一个虚拟地址转换为物理地址的任务叫做地址翻译(address translation)。CPU 芯片上叫做内存管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。
二、地址空间
- 地址空间(address space)是一个非负整数地址的有序集合。
- 如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间(linear address space)。
- 一个地址空间的大小是由表示最大地址所需要的位数来描述的。例如,一个包含 N = 2 n N=2^n N=2n个地址的虚拟地址空间就叫做一个n位地址空间(二进制需要n个位数才能表示完)。现代系统通常支持32位或者64位虚拟地址空间。
- 一个系统还有物理地址空间(physical address space),对应于系统中物理内存的M个字节(M不是求2的幂)。
地址空间清楚地区分了数据对象(字节)和它们的属性(地址)。 一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。(咋从一对多变成一对一了?因为数据对象包含很多个字节是吧)
三、虚拟内存作为缓存的工具
虚拟内存被组织为一个由存放在磁盘上的M个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。(应该说的是磁盘上这个虚拟内存的数组的内容被缓存在主存中)
和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。每个虚拟页的大小为字节。类似地,物理内存被分割为物理页(Physical Page, PP)大小也为P字节(物理页也被称为页帧(page frame))。
1、DRAM缓存的组织结构
直写式(WT,Write Through)与回写式(WB,Write Back)指的是缓冲内存的工作方式。直写式缓存方式是当CPU要将数据写入内存时除了更新缓冲内存上的数据外也将数据写在 DRAM 中以维持主存与缓冲内存的一致性。当要写入内存的数据一多速度自然就慢了下来。回写式的缓存方式是每当 CPU 要将数据写入内存时只会先更新缓冲内存上的数据随后再让缓冲内存在总线不塞车的时候才把数据写回DRAM所以速度自然快得多。(高速缓存将充当缓冲区)
2、页表
虚拟内存系统必须有某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页。
由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫做页表(page table)的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。
页表就是一个页表条目(Page Table Entry, PTE)的数组。
图的解释:有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。因为 DRAM 缓存是全相联的,所以任意物理页都可以包含任意虚拟页。
3、页命中
当CPU想要读包含在VP2中的虚拟内存的一个字时会发生什么?地址翻译硬件将虚拟地址作为一个索引来定位PTE2, 并从内存中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在内存中的了。所以它使用PTE中的物理内存地址(该地址指向 PP1中缓存页的起始位置), 构造出这个字的物理地址。
4、缺页
DRAM缓存不命中称为缺页(page fault)。
- CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3, 从有效位推断出VP3未被缓存,并且触发一个缺页异常。
- 缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。
- 内核从磁盘复制VP3到内存中的PP3, 更新PTE3,随后返回。
- 当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。图 9-7 展示了在缺页之后我们的示例页表的状态。
虚拟内存系统使用了和 SRAM 缓存不同的术语,即使它们的许多概念是相似的。在虚拟内存的习惯说法中,块被称为页。在磁盘和内存之间传送页的活动叫做交换(swapping)或者页面调度(paging)。页从磁盘换入(或者页面调入)DRAM和从DRAM 换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是当有不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。也可以采用其他的方法,例如尝试着预测不命中,在页面实际被引用之前就换人页面。然而,所有现代系统都使用的是按需页面调度的方式。
5、分配页面
分配一个新的虚拟内存页时对我们示例页表的影响,例如,调用 malloc 的结果。在这个示例中,VP5的分配过程是在磁盘上创建空间,并更新PTE5使它指向磁盘上这个新创建的页面。(page table 是在DRAM里的,这个很有意思,MMU有一个TLB,MMU在CPU里)
6、局部性
程序将趋向于在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集合(resident set)。只要我们的程序有好的时间局部性,虚拟内存系统就能工作得相当好。但是,当然不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理内存的大小,那么程序将产生一种不幸的状态,叫做抖动(thrashing), 这时页面将不断地换进换出。虽然虚拟内存通常是有效的,但是如果一个程序性能慢得像爬一样,那么聪明的程序员会考虑是不是发生了抖动。
四、虚拟内存作为内存管理的工具
到目前为止,我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。注意,多个虚拟页面可以映射到同一个共享物理页面上。
、
- 简化链接
- 简化加载
- 简化共享
- 简化内存分配:当一个运行在用户进程中的程序要求额外的堆空间时(如调用 malloc 的结果), 操作系统分配一个适当数字(例如k个)连续的虚拟内存页面,并且将它们映射到物理内存中任意位置的k个物理页面。由于页表工作的方式,操作系统没有必要分配k个连续的物理内存页面。页面可以随机地分散在物理内存中。
五、虚拟内存作为内存保护的工具
在这个示例中,每个PTE中已经添加了三个许可位。SUP 位表示进程是否必须运行在内核(超级用户)模式下才能访问该页。运行在内核模式中的进程可以访问任何页面,但是运行在用户模式中的进程只允许访问那些SUP为0的页面。READ位和WRITE位控制对页面的读和写访问。例如,如果进程i运行在用户模式下,那么它有读VP0和读写VP1的权限。然而,不允许它访问 VP2。如果一条指令违反了这些许可条件,那么 CPU 就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。Linux shell —般将这种异常报告为“段错误(segmentation fault)” 。
六、地址翻译
了解硬件在支持虚拟内存中的角色,省略了大量的细节,尤其是和时序相关的细节。图 9-11 概括了我们在这节里将要使用的所有符号。
图9-12展示了MMU如何利用页表来实现虚拟地址空间到物理地址空间映射。CPU中的一个控制寄存器,页表基址寄存器(Page Table Base Register, PTBR) 指向当前页表。n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(Virtual Page Offset, VPO)和一个(n-p)位的虚拟页号(Virtual Page Number, VPN)。MMU利用VPN来选择适当的PTE。例如,VPN0选择PTE0,VPN1选择PTE1,以此类推。将页表条目中物理页号(Physical Page Number, PPN)和虚拟地址中的VPO串联起来,就得到相应的物理地址。 注意,因为物理和虚拟页面都是P字节的,所以物理页面偏移(Physical Page Offset, PPO)和VPO是相同的。
(第7步其实就是a)的一次循环了)。
1、结合高速缓存和虚拟内存
既使用虚拟内存又使用SRAM髙速缓存的系统中,都有应该使用虚拟地址还是使用物理地址来访问SRAM高速缓存的问题。大多数系统是选择物理寻址的。
2、利用TLB加速地址翻译
每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE, 以便将虚拟地址翻译为物理地址。在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)。TLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。TLB 通常有高度的相联度。当 TLB 命中时(通常情况)所有的地址翻译步骤都是在芯片上的 MMU 中执行的,因此非常快。
3、多级页表
这种方法从两个方面减少了内存要求。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表着一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会是未分配的。第二,只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页表,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。
4、端到端的地址翻译
一个具体的端到端的地址翻译示例。书P609/775
七、 案例研究:Intel Core i7/Linux 内存系统
书P612/775。
1、Core i7 地址翻译
当MMU翻译每一个虚拟地址时,它还会更新另外两个内核缺页处理程序会用到的位。每次访问一个页时,MMU都会设置A位,称为引用位(reference bit)。内核可以用这个引用位来实现它的页替换算法。每次对一个页进行了写之后,MMU都会设置D位,又称修改位或脏位(dirty bit)。修改位告诉内核在复制替换页之前是否必须写回牺牲页。内核可以通过调用一条特殊的内核模式指令来清除引用位或修改位。
2、Linux 虚拟内存系统
任务结构中的一个条目指向 mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的两个字段是 pgd 和 mmap。其中pgd指向第一级页表(页全局目录)的基址,而 mmap 指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。
为了我们的目的,一个具体区域的区域结构包含下面的字段:
- vm_start: 指向这个区域的起始处。
- vm_end: 指向这个区域的结束处。
- vm_port:描述这个区域内包含的所有页的读写许可权限。
- vm_flags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的(还描述了其他一些信息)。
- vm_next: 指向链表中下一个区域结构。
八、内存映射
将一组连续的虚拟页映射到任意一个文件中的任意位置的表示法称作内存映射(memory mapping)Linux 提供一个称为 mmap的系统调用,允许应用程序自己做内存映射。
Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)虚拟内存区域可以映射到两种类型的对象中的一种:
1) Linux文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。 如果区域比文件区要大,那么就用零来填充这个区域的余下部分(?)。
2) 匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理内存中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在内存中的。注意在磁盘和内存之间并没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页(demand-zero page)
无论在哪种情况中,一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件(swap file)之间换来换去。交换文件也叫做交换空间(swap space)或者交换区域(swap area)需要意识到的很重要的一点是,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。
1、再看共享对象
一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。
2、再看fork函数
3、再看execve函数
4、使用mmap函数的用户级内存映射
mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新的区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为 NULL。为了我们的目的,我们总是假设起始地址为NULL。
九、动态内存
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。