20145229《信息安全系统设计基础》第14周学习总结
20145229《信息安全系统设计基础》第13周学习总结
教材学习内容总结
虚拟存储器
- 一个系统中的进程是与其他进程共享CPU和主存资源的
- 为了有效的管理存储器且少出错,引入了虚拟存储器(VM)
- 虚拟存储器是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互。为每个进程提供大的、一致的和私有的地址空间
- 虚拟存储器提供的三个能力:(1)把主存看成高速缓存,只保留活动区域,根据需要在磁盘和主存至今来回传送 (2)为每个进程提供了一致的地址空间,简化了存储器管理 (3)保护了每个进程的地址空间不被其他进程破坏
- 程序员理解它的原因:(1)虚拟存储器是中心的 (2)虚拟存储器是强大的 (3)虚拟存储器是危险的
9.1物理和虚拟寻址
- 计算机系统的主存被组织成一个由M个连续字节大小的单元组成的数组。每字节都有一个唯一的物理地址。
- CPU访问存储器的最自然的方式就是使用物理地址。这种方式称为物理寻址
- CPU执行命令的时候会生成一个物理地址,后传递给主存,主存取出返回给CPU,CPU存放在一个寄存器里
- 现代存储器都使用虚拟寻址的寻址方式
- 虚拟地址在被转换为物理地址的任务叫做地址翻译,需要CPU硬件和操作系统一起完成。CPU芯片上叫存储器管理单元的硬件利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理
9.2 地址空间
- 地址空间是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那我们说它是一个线性地址空间。而且为了方便讨论,我们总是假设使用的是线性地址空间
- CPU从N=2(n次方)个地址的虚拟空间中生成虚拟地址,这个地址空间称为虚拟地址空间
- 一个地址空间的大小是由表示最大地址所需位数来描述的
- 物理地址空间是和物理存储器的M个字节对应的
- 虚拟存储器的基本思想:每个数据对象可以有多个独立的地址,每个地址都选自一个不同的地址空间。主存中的每个字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址
9.3 虚拟存储器作为缓存的工具
- 虚拟存储器上的被组织的N个字节,每个字节独有唯一的虚拟地址,这个虚拟地址是作为到数组的索引。
- 磁盘上的数组的内容被缓存在主页中,和存储器结构层次中其他缓存一样。磁盘中较低层的数据被分割成块,作为磁盘和主存的传输单元
- 虚拟页面的集合分为3个不相交的子集:(1)未分配的 (2)缓存的 (3)未缓存的
9.3.1 DRAM缓存中的组织结构
- DRAM缓存的位置对它的组织结构有很大的影响,DRAM的缓存不命中是由磁盘服务的,SRAM的缓存不命中是基于DRAM主存服务的,DRAM缓存的组织结构完全是由巨大的不名字开销驱动的
- 由于大的不命中开销和访问第一字节,虚拟页往往很大,典型的是4kb~2MB,DRAM缓存是全相连的,任何虚拟页都可以放在任何物理页中。
- 与硬件对SRAM缓存说,操作系统对DRAM使用了更精密的替换算法。最后,因为对磁盘的访问时间很长,DRAM缓存总是使用写回,而不是直写。
9.3.2 页表
- 页表将虚拟页反映到物理页,地址翻译硬件转换为物理地址的时候都要读取页表,操作系统负责维护页表中的内容,以及在磁盘和DRAM中的传送页
- 页表就是一个也表条目,虚拟地址空间的每个页在页表中都有一个固定偏移量处有一个PTE,每个PTE都有一个有效位和一个n位地址字段组成
- 如果设置了有效位,地址字段代表了DRAM中相应物理页的起始位置,这个物理页中缓存了虚拟页;如果没有设置有效位,空地址代表虚拟页没有被分配
9.3.3 页命中
- 地址翻译硬件根据虚拟地址定位,再从存储器中读取,根据是否设置有效位知道是否缓存,,后使用存储器地址构造出物理地址
9.3.4 缺页
- DRAM缓存不命中称为缺页
- 缺页异常会触发缺页异常处理程序,选择一个牺牲页,后修改也表条目
- 当缺页处理程序更新完且返回时,会重新启动导致缺页的指令,然后正常处理
- 虚拟存储器中,块称为页,在磁盘和存储器中传送页的活动为交换或者页面调度,页从磁盘换入DRAM或从DRAM换出,一直等待不命中才换入页面,这种方式称为按需页面调度。
9.3.6 又是局部性救了我们
- 虚拟存储器工作得很好归功于局部性
- 局部性原则保证了在任何时刻,程序一般在较小的活动页面集合上工作,这个集合称为工作集或者驻集
- 只要程序有好的局部性,虚拟存储器就能工作得很好
- 如果工作集的大小超过了物理存储器大小,程序将产生颠簸,页面将不断换进换出
9.4 虚拟存储器作为存储器管理的工具
- EDC PDP-11/70,支持的是比物理存储器更小的虚拟地址空间
- 按需页面调度和独立的虚拟地址空间的结合,对存储器的使用和管理造成了深远的影响
- VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配
- (1)简化链接
- (2)简化加载
- (3)简化共享
- (4)简化存储器分配
9.5 虚拟存储器作为存储器保护的工具
- 提供独立的地址空间使得分离不同进程的私有存储器变得容易,地址翻译机制可以扩展到更好的访问控制。每次CPU生成一个地址,翻译硬件会读一个PTE,通过添加额外的许可位来控制对一个虚拟页面内容的访问十分简单
- (1)SUP位:运行在内核模式可以访问任何页面,在用户模式下只能访问SUP为0的页面
- (2)READ位和WRITE位控制对页面的读和写访问
- 如果一条指令违反了这些条例,那么CPU就触发了一个一般保护屏障,将控制传递给一个内核中的异常处理程序,Linux外壳将这种异常报告为“段错误”
9.6 地址翻译
- 地址翻译是一个N元素的虚拟地址空间(VSA)中的元素和一个M元素的物理地址空间(PSA)之间元素的映射
- CPU中一个控制寄存器,页表机址寄存器指向当前页表
- n位的虚拟地址包含两个部分:1个p位的虚拟页面偏移(VPO)和一个(n-p)的虚拟页号(VPN)
- MMU利用VPN选择适当的PTE。
- 将页表条目的物理页号和虚拟地址的VPO连接起来就可以得到物理地址
- 由于物理和虚拟页面都是P字节的,所以物理页面偏移和VPO是相同的
- 产生命中时,CPU硬件执行的步骤
(1)处理器生成虚拟地址,传给MMU
(2)MMU生成PTE地址,并从高速缓存/主存请求得到他
(3)高速缓存/主存向MMU返回PTE
(4)MMU构造物理地址,并把它传给高速缓存/主存
(5)高速缓存/主存返回所请求的数据给处理器 - 页面命中完全由硬件来处理,处理缺页要求硬件和操作系统内核协助完成:
(1)处理器生成虚拟地址,传给MMU
(2)MMU生成PTE地址,并从高速缓存/主存请求得到他
(3)高速缓存/主存向MMU返回PTE
(4)PTE中有效位为0,触发缺页异常
(5)确定牺牲页
(6)调入新页面,更新PTE
(7)返回原来的进程,再次执行导致缺页的指令,会命中
9.6.1 结合高速缓存和虚拟存储器
- 在使用物理地址和虚拟地址来访问SRAM高速缓存中,大多数系统选择物理寻址
- 高速缓存无需处理保护问题,因为访问权限的检查时地址翻译过程的一部分
- 地址翻译发生在高速缓存查找之前,页表条目也可以缓存
利用TLB加速地址翻译
- 为了消除开销,系统在MMU中包括了一个关于PTE的小缓存,称为翻译后备缓冲器(TLB)
- TLB是一个小的、虚拟寻址的缓存,TLB有高度的相联性,用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中剩余的位组成的。
- TLB标记(TLBT)是由VPN中剩余的位组成的
- TLB命中时,所有的地址翻译步骤都是在芯片上的MMU中执行的,因此非常快
(1)CPU产生一个虚拟地址
(2)MMU从TLB中取出相应的PTE
(3)MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存
(4)高速缓存/主存将所请求的数据字返回给CPU - 当TLB不命中时,MMU必须从L1缓存中取出相应的PTE。新取出的PTE存放在TLB中,可能会覆盖一个已经存在的条目
9.6.3 多级页表
- 用来压缩页表的常用方法是使用层次结构的页表
- 一级页表中的每一个PTE负责映射虚拟地址空间中一个4MB的片,每一片都是由1024个连续的页面组成的。
- 二级页表中的每个PTE都负责映射一个4kb的虚拟存储器页面,就像我们查看的只有一级的页表一样
- 使用4字节的PTE,每个一级和二级页表都是4kb字节,这和一个页面的大小是一样的
- 从两方面减少了存储器要求:(1)如果一级页表的一个PTE是空的,那么相应的二级页表不会存在,代表一种节约 (2)只有一级页表才需要总是在主存中;虚拟存储器系统可以在需要时创建、页面调入或调出二级页表,减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中
案例研究:Intel Core i7/Linux存储器系统
- PTE的三个权限位:
(1)R/W位:确定内容是读写还是只读 (2)U/S位:确定是否能在用户模式访问该页 (3)XD位:禁止执行位,64位系统中引入,可以用来禁止从某些存储器页取指令 - 页处理程序涉及到的位: (1)A位:引用位,实现页替换算法 (2)D位:脏位,告诉是否必须写回牺牲页
Linux虚拟存储器系统
- linux为每个进程维持了一个单独的虚拟地址空间,其中,内核虚拟存储器位于用户栈之上
- linux将虚拟存储器组织成一些区域(也叫做段)的集合。一个区域就是已经存在的(已分配的)虚拟存储器的连续片
- 允许虚拟地址空间有间隙;内核不用记录那些不存在的页,这样的页也不用占用存储器
- 一个具体区域的区域结构:
(1)vm _start:指向这个区域的起始处;
(2)vm _end:指向这个区域的结束处;
(3)vm _prot:描述这个区域内所包含的所有页的读写许可权限;
(4)vm _fags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的,等等;
(5)vm _next:指向链表的下一个结构。
存储器映射
-
存储器映射:Linux通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容的过程
-
映射对象:(1)Unix文件系统中的普通文件 (2)匿名文件(全都是二进制0)
-
一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫交换空间,或交换区域
-
共享对象:对于所有把它映射到自己的虚拟存储器进程来说都是可见的;即使映射到多个共享区域,物理存储器中也只需要存放共享对象的一个拷贝
-
私有对象:运用写时拷贝的技术,在物理存储器中只保存有私有对象的一份拷贝
再看folk函数
- fork函数:应用了写时拷贝技术。为每个进程保持了私有地址空间的抽象概念
再看execve函数
- execve函数:将程序加载到存储器
- 步骤:
(1)删除已存在的用户区域。 (2)映射私有区域。 (3)映射共享区域。 (4)设置程序计数器。
使用mmap函数的用户级存储器映射
- 创建新的虚拟存储器区域:
include <unistd.h>
include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
成功返回指向映射区域的指针,若出错则为-1
- 删除虚拟存储器
include <sys/mman.h>、
int munmap(void *start, size_t length);
成功返回0,失败返回-1
动态存储器分配
- 当运行时需要额外虚拟存储器时,使用动态存储器分配器维护一个进程的虚拟存储器区域。更方便,也有更好的移植性
- 分配器有两种风格:
(1)显示分配器:要求应用显式地释放任何已经分配的块。
(2)隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,就释放这个块。也叫做垃圾收集器。
malloc和free函数
-
系统调用malloc函数,从堆中分配块:
include <stdlib.h>
void *malloc(size_t size); -
成功返回指针,指向大小至少为size字节的存储器块,失败返回NULL
-
系统调用free函数来释放已分配的堆块:
include <stdlib.h>
void free(void *ptr);
无返回值 -
ptr参数必须指向一个从malloc、calloc或者reallov获得的已分配块的起始位置。
-
使用动态存储器分配原因:经常直到程序实际运行时,才知道某些数据结构的大小。
分配器的要求和目标
-
显示分配器的要求:
(1)处理任意请求序列 (2)立即响应请求 (3)只使用堆(4)对齐块(5)不修改已分配的块 -
目标:
(1)最大化吞吐率 (2)最大化存储器利用率——峰值利用率最大化 -
吞吐率:每个单位时间里完成的请求数
碎片
- 碎片:虽然有未使用的存储器,但是不能用来满足分配请求。
- 内部碎片:发生在一个已分配块比有效载荷大的时候,易于量化。
- 外部碎片:发生在当空闲存储器合计起来足够满足一个分配请求,但是没有一个单独的空间块足以处理这个请求时发生。难以量化,不可预测。
隐式空闲链表
-
堆块的格式:由一个字的头部,有效荷载,和可能的额外填充组成。
-
需要:特殊标记的结束块。
-
系统对齐要求和分配器对块格式的选择会对分配器上的最小块大小有强制的要求。
-
优点:简单 缺点:任何操作的开销
置已分配的块
- 分配方式:
(1)首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块
(2)下一次适配:从上一次搜索的结束位置开始搜索
(3)最佳适配:检索每个空闲块,选择适合所需请求大小的最小空闲块
合并空闲块
- 合并是针对于假碎片问题的,任何实际的分配器都必须合并相邻的空闲块。
- 两种策略: (1)立即合并 (2)推迟合并
垃圾收集
- 垃圾收集器是一种动态存储分配器,自动释放程序已经不再需要的已分配块(垃圾)。
- 垃圾收集器将存储器视为一张有向可达图,图的节点被分配为一组根节点和一组堆节点。
- Mark&Sweep垃圾收集器:由标记阶段和清除阶段组成,标记阶段标记出根节点所有可达的和已分配的后继,清除阶段释放每个未被标记的已分配块
- 相关函数:
ptr定义为typedef void *ptr
ptr isPtr(ptr p):如果p指向一个已分配块中的某个字,那么就返回一个指向这个块的起始位置的指针b,否则返回NULL
int blockMarked(ptr b):如果已经标记了块b,那么就返回true
int blockAllocated(ptr b):如果块b是已分配的,那么久返回ture
void markBlock(ptr b):标记块b
int length(ptr b):返回块b的以字为单位的长度,不包括头部
void unmarkBlock(ptr b):将块b的状态由已标记的改为未标记的
ptr nextBlock(ptr b):返回堆中块b的后继
C程序中常见的与存储器有关的错误
- 间接引用坏指针:在进程的虚拟地址空间中有较大的洞,没有映射到任何有意义的数据,如果试图引用一个指向这些洞的指针,操作系统就会以段异常来终止程序
- 典型的错误:scanf("%d",val);
- 读未初始化的存储器:bass存储器位置总是被加载器初始化为0,但对于堆存储器却并不是这样的
- 典型的错误:假设堆存储器被初始化为0
- 允许栈缓冲区溢出:如果一个程序不检查输入串的大小就写入栈中的目标缓冲区,程序就会出现缓冲区溢出错误
- 假设指针和指向他们的对象大小是相同的
- 造成错位错误:一种很常见的覆盖错误来源
- 引用指针,而不是他所指向的对象(注意C的优先级和结合性)
- 误解指针运算:忘记了指针的算术操作是以它们指向的对象的大小为单位来进行,而这种大小单位不一定是字节
- 引用不存在的变量:不理解栈的规则,有时会引用不再合法的本地变量
- 引用空闲堆块中的数据
- 引起存储器泄露:当不小心忘记释放已分配块,而在堆里创建了垃圾时,就会引起存储器泄露
本周代码托管截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 130/130 | 1/1 | 17/17 | |
第二周 | 90/270 | 1/1 | 16/16 | |
第三周 | 120/390 | 2/2 | 16/16 | |
第四周 | 89/479 | 1/1 | 17/17 | |
第五周 | 120/599 | 1/1 | 16/16 | |
第六周 | 110/709 | 1/1 | 18/18 | |
第七周 | 128/837 | 1/1 | 18/18 | |
第八周 | 5/842 | 1/1 | 16/16 | |
第九周 | 65/907 | 1/1 | 13/13 | |
第十周 | 160/1067 | 1/1 | 14/14 | |
第十一周 | 380/1447 | 1/1 | 15/15 | |
第十二周 | 0/1447 | 4/4 | 24/24 | |
第十三周 | 132/1579 | 1/1 | 13/13 | |
第十四周 | 421/2000 | 1/1 | 12/12 |