分享

二进制 | 翱翔的旅行日志!

 astrotycoon 2015-11-14

这篇文章是对《深入理解计算机系统》第九章的一个学习总结,可能一篇文章写不下,我会分两部分写。

《深入理解计算机系统》这本书是工作室一位前辈推荐的。嗯,是本很好的底层入门的书籍。书的原名叫《Computer Systems:A Programmer’s Perspective》,感觉中文的翻译不是很贴切。不过无所谓了。sa date开始咯!(卖萌~)

我们知道,在编写一个程序的时候,就好像这个程序在完全的单独的使用内存,虽然,事实不是这样的,这是靠虚拟储存器实现的。虚拟储存器是现代操作系统对主存概念的抽象,是硬件异常、主存、磁盘文件和内核软件的完美交互。

虚拟储存主要有三点主要的作用:

  1. 它将主存看成了一个储存在磁盘的地址空间的高速缓存,主存中只保留活动区域,并根据需要在主存和磁盘间来回交换数据
  2. 它为每个进程提供了一致的地址空间,从而简化了储存器的管理。
  3. 它保护了每个进程空间不受其他进程破化

我之所以直接写虚拟储存器有2个原因。第一点,虚拟储存器在我看来是最重要的一个概念,对于二进制安全,我也认为这个概念及其重要(也可能我搞错了,我还只是个菜鸡^^)。第二点,我昨天刚刚看完这个。

物理和虚拟寻址

物理地址(Physical Address,PA)这个概念是不需要过多的解释,就是指物理内存的地址。虚拟地址(Virtual Address,VA)直观的讲,c语言里的指针所用的就是虚拟地址。虚拟地址就是一种储存器的抽象。使用物理地址的寻址方式是物理寻址,使用虚拟地址的寻址方式的成为虚拟寻址。

地址空间

地址空间就是一个地址的数组,有虚拟地址空间和物理地址空间

虚拟储存器作为缓存的工具

概念上来说,虚拟储存器被组织为一个由存放在磁盘上N个连续的字节大小的单元组成的数组。每个字节都有唯一的虚拟地址。和储存器的层次结构的中的其他的缓存一样,磁盘(较低层次)的数据被分割成块,这些块作为磁盘和主存(较高层次)之间的传输单元。VM系统将虚拟储存器分割为称为虚拟页(Virtual Page,VP)的大小固定的块。每个块大小为P字节。物理储存器也被分割为物理页(Physical Page,PP)大小也是P字节。物理页也被称为页帧(page frame)。

任意时刻,虚拟页面都被分为三个不相交的子集:

未分配:VM系统还未分配(或者创建)的页,不占用任何磁盘空间。

缓存的:当前已缓存在物理储存器中的已分配的页

未缓存的:没有缓存在物理储存器中的已分配的页。

我们用DRAM缓存来表示主存中对虚拟储存器的缓存。我们知道DRAM比磁盘块了1000000多倍(关于SRAM和DRAM的介绍,自行谷歌,不过我最近也会写一个关于这个的简单介绍),而SRAM比DRAM快了将尽10倍。所以,任何对DRAM的不命中都会招致很大的性能惩罚,是一巨大的开销。因此,虚拟页往往很大,典型是在4KB~2MB之间。由于大的不命中惩罚,DRAM也是全相连的。同时操作系统也对DRAM使用了精密的替换策略。

每个进程都有一个由页表项(PTE)组成的页表。先简化一下概念。我把页表项认为成一个有效位和其他的地址位组成的。页表1当有效位是0的时候表示这一页还不在主存中,他可能还没创建或者没有在内存中,后面的地址位表示页在磁盘的位置。当有效位是1的时候,表示这一页在内存中,地址位表示这一页的起始位置。当cpu引用一个不在内存中的页的时候,会引发一个缺页异常。内核的缺页处理程序将会进行处理。在磁盘和储存器间的页传送活动叫交换(swapping)或者页面调度(paging)。页从磁盘换入(页面调入)DRAM和从DRAM换出(页面调出)磁盘。

此时,程序的局部性的作用就再次体现出来了。一个局部性差的程序,可能导致其工作集超过内存的大小,导致页面不断换入换出,此时程序慢的像爬一样,这种状况叫做程序的颠簸(thrashing)

再谈虚拟储存器的作用

作为储存器的管理工具

系统通过页表把每月页映射到内存的页上,简化了链接、加载、共享以及储存器的分配

作为储存器的保护工具

任何现代计算机系统都必须为操作系统提供手段来控制对储存器的访问。每次地址的翻译都需要PTE ,PTE上有各种控制位,如SUP位:为1时,只能内核模式访问。用户态程序只能访问SUP为0的页。READ位和WRITE位控制对页的读和写权限。如果一条指令违反了许可条件,cpu就触发一个一般保护错误,将控制移交给内核异常处理程序。Unix外壳一般讲这种异常报告为段错误(segmentation fault)。

地址翻译

先弄一个表

地址翻译符号表

CPU芯片上有一个叫做储存器管理单元(memory management unit,MMU)的硬件实时的翻译虚拟地址为物理地址。一个虚拟地址由2部分组成,p位的虚拟页面偏移(virtual page offset,VPO)和一个(n-p)位的虚拟页号(virtual page number,VPN)组成。MMU根据VPN来选择PTE。例如VPN0选择PTE0.物理地址也是2部分组成的,物理页面偏移(physical page offset,PPO)和VPO的位数是一样的,物理页号(physical page number,PPN)。CPU中还有一个控制寄存器,叫作页表基址寄存器(page table base register,PTBR),这个寄存器储存着页表的地址,是一个进程的上下文。

地址翻译

页面命中时:

  • 第一步,处理器生成一个虚拟地址,给MMU
  • 第二步,MMU生成PTE地址并从,高速缓存/主存中请求得到他。(PTBR+VPN)
  • 第三步,高速缓存/主存向MMU返回PTE
  • 第四步,MMU构造物理地址,并将他传给高速缓存/主存
  • 第五步,返回所请求的数据

页面未命中时:

  • 第一至第三步是相同的
  • 第四步,PTE有效位是0,MMU触发了一次异常,控制传递给内核异常处理程序。
  • 第五步,缺页处理程序确定出物理储存器中的牺牲页,如果这个页面被修改了,则把他换出到磁盘上。
  • 第六步,缺页处理程序把页面调入到新的页面上,并更新PTE
  • 第七步,返回到原来的进程,并重新执行刚才的指令。

结合高速缓存的虚拟储存器

现代操作系统使用了物理寻址来访问高速缓存,关于是使用虚拟地址还是物理地址来访问时存在争论的。这是一种折中的方案(关于具体的书中没有介绍)。

虚拟储存器和高速缓存

使用TLB加速地址访问

现代计算机系统中在MMU中包含了一个缓存PTE的高速缓存翻译后备缓冲器(translation lookaside buffer,TLB),以加速翻译。

tlb

多级页表

目前为止,我们只使用一级页表,我们假设一个32位的地址空间,4KB的页面和一个4字节的PTE,那么即使应用所引用的只是虚拟地址的很小一部分,也需要4MB的页表驻留在内存,这是一种严重的浪费。对于64位系统,这将变的更复杂。

我们使用层次的页表。我们使用一个二级页表作为例子来解释。

一级页表负责映射虚拟空间中的一个4MB的片(chunk),每个片都是由1024个连续的页组成的。假设4GB地址空间1024个片就可以覆盖完整个地址空间。如果片i没被分配,那么这片i就为空。

二级页表每个PTE都负责映射一个4KB的虚拟储存器页面。

这样有2个好处:

  1. 如果一级PTE为空,那么整个对应的二级页表就不存在,这是一种巨大的节约
  2. 只有一级页表是需要常驻内存的,其他的页表可以在磁盘上,在需要时再换入内存。

下图描述了一个k级页表结构

k级页表

inter core i7/linux储存器系统

Core i7是基于Nehalem微体系结构的。虽然Nehalem设计允许完全的64位虚拟地址和物理地址空间。而现在其实现支持48位(256TB)虚拟地址空间和52位(4PB)物理地址空间,还有一个兼容模式,支持32位(4GB)虚拟和物理地址空间。

下图是Core i7的重要组成部分,页的大小被配置为4KB和4MB。linux使用4KB的页

core_i7

Core i7的地址翻译

下图总结翻译过程和第一、二、三级页表结构,Core i7采用4级页表。虽然Core i7允许页表换进换出,但与已分配内存页面相关的页表都是在储存器中的。CR3控制寄存器指向一级页表的起始位置,是进程上下文的一部分。物理也是4KB对齐的。

core_i7页表

第四级页表

第四级页表

linux虚拟储存系统

下图是一个linux的虚拟储存器

linux虚拟储存器

linux将储存器组织成一些区域(也叫段)的集合。例如,代码段,数据段,用户栈等就分属不同的区域(area)。不存在不属于某个区域的虚拟页,并且不能被进程引用。区域概念很重要,因为它允许虚拟地址空间有间隙。内核不需要记录那些不存在的虚拟页,而这些不存在的虚拟页也不占用任何资源。

下图强调了一个记录一个进程中虚拟存储器的内核数据结构。内核为系统中每个进程维护一个单独的任务结构(源代码中的task_struct)。任务结构中包含或者指向内核运行该进程所需要的全部信息(PID,指向用户栈的指针、可执行目标文件的名字以及程序计数器等)。

linux_task

task_struct中有个条目指向mm_struct,它描述了储存器当前的状态。我们感兴趣的是pgd和mmap,pgd指向第一级页表的基址,而mmap指向一个vm_area_structs(区域结构)的链表,每个vm_area_structs都描述了当前虚拟地址空间的一个区域。

  • vm_start,指向这个区域的开始
  • vm_end,这个区域的结束
  • vm_prot,描述这个区域全部页的读写权限
  • vm_flags,描述这个区域是共享的还是私有的
  • vm_next

linux缺页异常处理程序

处理程序执行下面的步骤

  1. 虚拟地址是合法的吗?根据vm_start和vm_end判断
  2. 试图进行的储存器访问是否合法?根据控制位判断
  3. 进行处理

linux缺页

储存器映射

linux下虚拟储存区域可以映射到2种类型对象中的一种:

  1. Unix文件系统中的普通文件
  2. 匿名文件:匿名文件由内核创建,包含的全部都是二进制零。CPU第一次引用这样的页时,内核就找到一个牺牲页,页面被修改过就换出 ,然后用二进制零覆盖整个页。注意磁盘和储存器间没有任何数据传送。也叫请求二进制零的页(demand-zero page)。

无论何种情况,一旦一个虚拟页面被初始化,它就在一个内核维护的专门的交换文件(swap file)间换出换进。交换文件也叫交换区(swap area)。任何时候,交换区都限制着当前运行着的进程能够分配的虚拟页面总数。

共享对象

共享对象对所有相关进程都可见的,同时对其的修改也会反映到磁盘上的原始对象上。

共享对象

私有对象

私有对象是私有的,对其进行的写是不会反映到原始的磁盘对象上的,私有对象使用了写时拷贝的技术

私有对象

好了,今天就到这里。最后雪雪乱入!

雪雪(虚拟储存器是什么?能不能吃呀~~)(mukimuki)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多