20145210《信息安全系统设计基础》第十四周学习总结
教材学习内容总结
·一个系统中的进程与其他进程共享CPU和主存资源
·虚拟存储器:对主存的抽象概念,是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,为每个进程提供了一个大的、一致的、私有的地址空间
·虚拟主存提供的三个重要能力:
·它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效的使用了主存
·它为每个进程提供了一致的地址空间,从而简化了存储器管理
·它保护了每个进程的地址空间不被其他进程破坏
物理和虚拟寻址
·计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,每字节都有一个唯一的物理地址(PA)
·物理寻址:使用物理地址寻址
·虚拟寻址:CPU生成一个虚拟地址(VA)来访问主存,在被传送到存储器之前先转换成适当的物理地址
·地址翻译:将一个虚拟地址转换为物理地址的任务
地址空间
·地址空间:地址空间是一个非负整数地址的有序集合:{0,1,2,……}
·线性地址空间:如果地址空间中的整数是连续的,那么我们说他是一个线性地址空间
·虚拟地址空间:CPU从一个有 N=2^n 个地址的地址空间中生成虚拟地址,这个地址空间成为称为虚拟地址空间
·一个地址空间的大小是由表示最大地址所需要的位数来描述的{0,1,2,...,N-1}
·物理地址空间:一个系统还有一个物理地址空间,他与系统中物理存储器的M个字节相对应:{0,1,2,...,M-1}
虚拟存储器作为缓存的工具
·虚拟页:VM系统通过将虚拟存储器分割为称为虚拟页(VP)的大小固定的块来处理这个问题,每个虚拟页的大小为P=2^p
·物理页:物理存储器被分割为物理页,大小也为P字节,物理页也称为页帧
·在任意时刻,虚拟页面的集合都分为三个不相交的子集:
·未分配的:VM系统还没分配/创建的页,不占用任何磁盘空间。
·缓存的:当前缓存在物理存储器中的已分配页
·未缓存的:没有缓存在物理存储器中的已分配页
DRAM缓存的组织结构
·SRAM缓存:位于CPU和主存之间的L1,L2,L3的高速缓存
·DRAM缓存:虚拟存储器系统的缓存,他在主存中缓存虚拟页
页表
·页表是一个数据结构,存放在物理存储器中,将虚拟页映射到物理页
·页表条目:页表就是一个页表条目(PTE)的数组,每个PTE是由一个有效位和一个n位地址字段组成的
·如果设置了有效位:地址字段表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页
·如果没有设置有效位:
·空地址:表示该虚拟页未被分配
·不是空地址:这个地址指向该虚拟页在磁盘上的起始位置。
缺页
·缺页:DRAM缓存不命中称为缺页
·交换(页面调度):磁盘和存储器之间传送页的活动
·按需页面调度:直到发生不命中时才换入页面的策略称为按需页面调度,所有现代系统都使用按需页面调度
局部性
·局部性原则保证了在任意时刻,程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集/常驻集
·只要程序有良好的时间局部性,虚拟存储器系统就能工作的相当好
·颠簸:工作集大小超出了物理存储器的大小
虚拟存储器作为存储器管理的工具
·虚拟存储器大大地简化了存储器管理,并提供给了一种自然的保护存储器的方法
·多个虚拟页面可以映射到同一个共享物理页面上
·按需页面调度和独立的虚拟地址空间的结合,对系统中存储器的使用和管理造成了深远的影响
·VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配:
·简化链接:独立的地址空间允许每个进程的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处
·简化加载:虚拟存储器还使得容易向存储器中加载可执行文件和共享对象文件
·简化共享:独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制
·简化存储器分配:虚拟存储器为向用户进程提供一个简单的分配额外存储器的机制
虚拟存储器作为存储器保护的工具
·PTE的三个许可位:
·SUP:表示进程是否必须运行在内核模式下才能访问该页
·READ:读权限
·WRITE:写权限
·如果一条指令违反了这些许可条件,那么CPU就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序,Unix外壳一般将这种异常报告为“段错误”
地址翻译
·地址翻译就是一个N元素的虚拟地址空间VAS中的元素和一个M元素的物理地址空间PAS中元素之间的映射
·页面基址寄存器PTBR指向当前页表
·n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页号(VPN)
·MMU利用VPN选择适当的PTE
·当页面命中时,CPU硬件执行的步骤:
·处理器生成虚拟地址,传给MMU
·MMU生成PTE地址,并从高速缓存/主存请求得到他
·高速缓存/主存向MMU返回PTE
·MMU构造物理地址,并把它传给高速缓存/主存
·高速缓存/主存返回所请求的数据给处理器
·处理缺页时
·处理器生成虚拟地址,传给MMU
·MMU生成PTE地址,并从高速缓存/主存请求得到他
·高速缓存/主存向MMU返回PTE
·PTE中有效位为0,触发缺页异常
·确定牺牲页
·调入新页面,更新PTE
·返回原来的进程,再次执行导致缺页的指令,会命中
结合高速缓存和虚拟存储器
·在既使用SRAM高速缓存又使用虚拟存储器的系统中,大多数系统选择物理寻址
·两者结合的主要思路是地址翻译发生在高速缓存之前
·页表目录可以缓存,就像其他的数据字一样
利用TLB加速地址翻译
·TLB:翻译后备缓冲器,是一个小的、虚拟存储的缓存,其中每一行都保存着一个由单个PTE组成的块
·TLB命中时包含的步骤:
·CPU产生一个虚拟地址
·MMU从TLB中取出相应的PTE
·MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存
·高速缓存/主存将所请求的数据字返回给CPU
多级页表
·多级页表:采用层次结构,用来压缩页表
·以两层页表层次结构为例,好处是:
·如果一级页表中的一个PTE是空的,那么相应的二级页表就根本不会存在
·只有一级页表才需要总是在主存中,虚拟存储器系统可以在需要时创建、页面调入或调出二级页表,只有最经常使用的二级页表才缓存在主存中
·多级页表的地址翻译:
存储器映射
·存储器映射:Linux通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容的过程
·映射对象:
·Unix文件系统中的普通文件
·匿名文件(全都是二进制0)
·共享对象:对于所有把它映射到自己的虚拟存储器进程来说都是可见的。即使映射到多个共享区域,物理存储器中也只需要存放共享对象的一个拷贝
·私有对象:运用写时拷贝的技术,在物理存储器中只保存有私有对象的一份拷贝
·fork函数:应用了写时拷贝技术。为每个进程保持了私有地址空间的抽象概念
·execve函数:将程序加载到存储器
·步骤:
·删除已存在的用户区域。
·映射私有区域。
·映射共享区域。
·设置程序计数器。
·map函数
·创建新的虚拟存储器区域:
#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
动态存储器分配
·堆:一个请求二进制0的区域,紧接在未初始化的bss区域后开始,并向上(更高的地址)生长。有一个变量brk指向堆的顶部
·使用动态存储器分配原因:经常直到程序实际运行时,才知道某些数据结构的大小
·分配器的两种基本风格:
·显示分配器-malloc和free
·隐式分配器/垃圾收集器
malloc和free函数
·系统调用malloc函数,从堆中分配块:
void *malloc(size_t size);
成功返回指针,指向大小至少为size字节的存储器块,失败返回NULL
·系统调用free函数来释放已分配的堆块:
void free(void *ptr);
无返回值
·ptr参数必须指向一个从malloc、calloc或者reallov获得的已分配块的起始位置
分配器的要求和目标
·要求
·处理任意请求序列
·立即响应请求
·只使用堆
·对齐块
·不修改已分配的块
·目标
·最大化吞吐率(吞吐率:每个单位时间里完成的请求数)
·最大化存储器利用率——峰值利用率最大化
碎片
·虽然有未使用的存储器,但是不能用来满足分配请求时,发生这种现象
·内部碎片:发生在一个已分配块比有效载荷大的时候,易于量化
·外部碎片:发生在当空闲存储器合计起来足够满足一个分配请求,但是没有一个单独的空间块足以处理这个请求时发生。难以量化,不可预测
隐式空闲链表
·堆块的格式:由一个字的头部,有效荷载,和可能的额外填充组成
·将堆组织成一个连续的已分配块和空闲块的序列:
·空闲块通过头部中的大小字段隐含地连接着,分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合
·系统对齐要求和分配器对块格式的选择会对分配器上的最小块大小有强制的要求
放置已分配的块
·首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块
·下一次适配:从上一次搜索的结束位置开始搜索
·最佳适配:检索每个空闲块,选择适合所需请求大小的最小空闲块
申请额外的堆存储器
·sbrk函数:
void *sbrk(intptr_t incr);
成功则返回旧的brk指针,出错为-1
·通过将内核的brk指针增加incr来扩展和收缩堆
合并空闲块
·合并是针对于假碎片问题的,任何实际的分配器都必须合并相邻的空闲块
·策略:
·立即合并
·推迟合并
垃圾收集
·垃圾收集器是一种动态存储分配器,自动释放程序已经不再需要的已分配块(垃圾)。
·垃圾收集器将存储器视为一张有向可达图,图的节点被分配为一组根节点和一组堆节点。
·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小时 |
第零周 | 0/0 | 1/1 | 10/10 |
第一周 | 100/100 | 1/2 | 20/20 |
第二周 | 120/220 | 1/3 | 35/55 |
第三周 | 226/446 | 1/4 | 30/85 |
第五周 | 141/587 | 1/5 | 30/115 |
第六周 | 150/737 | 1/6 | 25/140 |
第七周 | 100/837 | 1/7 | 20/160 |
第八周 | 0/837 | 2/9 | 30/190 |
第九周 | 183/1020 | 2/11 | 20/210 |
第十周 | 521/1541 | 3/14 | 20/230 |
第十一周 | 426/1967 | 1/15 | 35/265 |
第十二周 | 41/2008 | 3/18 | 50/315 |
第十三周 | 1281/2008 | 1/18 | 40/355 |
第十四周 | 0/2008 | 1/19 | 30/385 |