2017-2018-1 20155339 《信息安全系统设计基础》第十一周学习总结
2017-2018-1 20155339 《信息安全系统设计基础》第十一周学习总结
教材学习内容总结
- 虚拟存储器是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。
- 虚拟存储器是计算机系统最重要的概念之一,它是对主存的一个抽象。
- 虚拟存储器的三个重要能力:
1.将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效的使用了主存。
2.为每个进程提供了一致的地址空间,从而简化了存储器管理。
3.保护了每个进程的地址空间不被其他进程破坏。 - 计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,每字节都有一个唯一的物理地址PA。
物理和虚拟寻址
- 物理寻址:主存被组织成一个由M个连续字节大小的单元组成的数组,依次类推的寻址方式称为物理寻址。
- 虚拟存储器被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。
- 虚拟寻址:CPU生成一个虚拟地址(VA)来访问主存,在被传送到存储器之前先转换成适当的物理地址。地址翻译通过CPU芯片上的存储器管理单元完成。
- 地址翻译通过CPU芯片上的存储器管理单元完成。
地址空间
- 地址空间是一个非负整数地址的有序集合:
- 线性地址空间:有序集合中整数是连续的。
- 虚拟地址空间:cpu从一个有 N=2^n 个地址的地址空间中生成虚拟地址。
- 物理地址空间:与系统中的物理存储器的M个字节相对应。
- 地址空间大小:由表示最大地址所需要的位数来描述。
虚拟存储器作为缓存的工具
-
虚拟存储器:虚拟页VP,每个虚拟页大小为P=2^p字节。
-
物理存储器:物理页PP,也叫页帧,大小为P字节。
-
任意时刻,虚拟页面的集合都被分为三个不相交的子集:
1.未分配的:VM系统还没分配、创建的页,不占用任何磁盘空间。
2.缓存的:当前缓存在物理存储器中的已分配页。
3.未缓存的:没有缓存在物理存储器中的已分配页。 -
页表:是一个数据结构,存放在物理存储器中,将虚拟页映射到物理页。
-
页表就是一个页表条目PTE的数组。页表的组成:有效位+n位地址字段。
-
PTE:由一个有效位和一个n位地址字段组成的,表明了该虚拟页是否被缓存在DRAM中。
-
若已经设置了有效位:地址字段表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。
-
如果没有设置有效位
1.空地址:表示该虚拟页未被分配。
2.不是空地址:这个地址指向该虚拟页在磁盘上的起始位置。
缺页
- 缺页:指DRAM缓存不命中。
- 缺页异常:会调用内核中的缺页异常处理程序,选择一个牺牲页。
- 页面调度(交换):磁盘和存储器之间传送页的活动。
- 直到发生不命中时才换入页面的策略称为按需页面调度,所有现代系统都使用这个。
虚拟存储器中的局部性
- 局部性原则:保证了在任意时刻,程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集/常驻集。
- 只要程序有良好的时间局部性,虚拟存储器系统就能工作的相当好。
- 颠簸:工作集大小超出了物理存储器的大小。
虚拟存储器作为存储器管理的工具
- 操作系统为每个进程提供了一个独立的页表,也就是一个独立的虚拟地址空间。
- 按需页面调度和独立的虚拟地址空间的结合简化了链接和加载、代码和数据共享,以及应用程序的存储器分配。
- 简化链接:独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处。
- 简化加载:虚拟内存使得容易想存储器中加载可执行文件和共享文件对象。
- 简化共享:独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。
- 简化内存分配:虚拟内存为向用户进程提供一个简单的分配额外存储器的机制。
虚拟存储器作为存储器保护的工具
- 通过在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问。
地址翻译
- 形式上来说,地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)之间的映射。MAP:VAS→PAS∪空集。
(1)其中MAP = A' ,如果虚拟地址A处的数据在PAS的物理地址A'处。
(2)MAP = ∅ ,如果虚拟地址A处的数据不在物理内存中。 - CPU中的一个控制寄存器,页表基址寄存器(PTRB)指向当前页表。
- n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页(VPN)。
- MMU利用VPN选择适当的PTE。
- 当页面命中时,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)返回原来的进程,再次执行导致缺页的指令。
结合高速缓存和虚拟内存
- 在任何既使用虚拟内存又使用SRAM高速缓存的系统中,大多数系统是选择物理寻址的。
- 注意:页表目录可以缓存,就像其他的数据字一样。
利用TLB加速地址翻译
- 在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(TLB),其中每一行都保存着一个由单个PTE组成的块。
- TLB:翻译后援缓冲器,是一个小的、虚拟存储的缓存,其中每一行都保存着一个由单个PTE组成的块。
- 步骤:
(1)CPU产生一个虚拟地址。
(2)(3)MMU从TLB中取出相应的PTE。
(4)MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
(5)高速缓存/主存将所请求的数据字返回给CPU。
多级页表
- 多级页表:采用层次结构,用来压缩页表。
- 以两层页表层次结构为例,好处是:
(1)如果一级页表中的一个PTE是空的,那么相应的二级页表就根本不会存在。
(2)只有一级页表才需要总是在主存中,虚拟存储器系统可以在需要时创建、页面调入或调出二级页表,只有最经常使用的二级页表才缓存在主存中。
案例研究
core i7地址翻译
- PTE的三个权限位:
(1)R/W位:确定内容是读写还是只读。
(2)U/S位:确定是否能在用户模式访问该页。
(3)XD位:禁止执行位,64位系统中引入,可以用来禁止从某些存储器页取指令。
内存映射
- Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。
- 虚拟内存区域可以映射两种类型对象中的一种:
(1)Linux文件系统中的普通文件。
(2)匿名文件。匿名文件由内核创建,包含的全是二进制的0。 - 一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫交换空间或者交换区域。
再看共享对象
- 如果虚拟内存系统可以集成到传统的文件系统中,那么就能够提供一种简单而又高效的吧程序和数据加载到内存中的方法。
- 一个对象可以被映射到虚拟内存的一个区域,要么称为共享对象,要么称为私有对象。
- 共享对象对于所有把它映射到自己的虚拟内存进程来说都是可见的。即使映射到多个共享区域,物理内存中也只需要存放共享对象的一个拷贝。
再看fork、execve函数
- 当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给他一个唯一的PID。
- 他将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标为私有的写时复制。
- 当当前进程中的程序执行了如下的execve调用时
execve("a.out",NULL,NULL);
,加载并运行a.out需要以下几个步骤:
(1)删除已存在的用户区域。
(2)映射私有区域。
(3)映射共享区域。
(4)设置程序计数器(PC)。
使用mmap函数的用户级内存映射
- Linux进程可以使用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
并将对象映射到这些区域中。
- munmap函数删除虚拟内存的区域:
#include <unistd.h>
#include <sys/mman.h>、
int munmap(void *start, size_t length);
//成功则返回0,失败返回-1
动态内存分配
- 堆是一个请求二进制0的区域,紧接在未初始化的bss区域后开始,并向上(更高的地址)生长。有一个变量brk指向堆的顶部。
- 分配器有两种基本风格,都采用显示地分配块:
(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获得的已分配块的起始位置。
- ptr参数必须指向一个从malloc、calloc或者realloc获得的已分配的起始位置。
分配器的要求和目标
- 显示分配器的一些严格的约束条件:
(1)处理任意请求序列
(2)立即响应请求
(3)只使用堆
(4)对齐块
(5)不修改已分配的块 - 目标:
(1)最大化吞吐率。
(2)最大化内存利用率。
碎片
- 虽然有未使用的存储器,但是不能用来满足分配请求时,就发生这种现象。
- 内部碎片:发生在一个已分配块比有效载荷大时。
- 外部碎片:发生在当空闲存储器合计起来足够满足一个分配请求,但是没有一个单独的空间块足以处理这个请求时。
- 内部碎片量化简单明了,外部碎片比内部碎片难以量化,不可预测。
隐式空闲链表
- 堆块的组成:一个字的头部+有效荷载+和可能的额外填充组成。
- 将堆组织成一个连续的已分配块和空闲块的序列:空闲块通过头部中的大小字段隐含地连接,分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。
- 是需要特殊标记的结束块。
放置已分配的块
- 首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。
- 下一次适配:从上一次搜索的结束位置开始搜索。
- 最佳适配:检索每个空闲块,选择适合所需请求大小的最小空闲块。
合并空闲块
- 合并是针对于假碎片问题的,任何实际的分配器都必须合并相邻的空闲块。
- 两种策略:
(1)立即合并
(2)推迟合并
实现简单的分配器
- 序言块是一个8个字节的已分配块,只由一个头部和一个尾部构成,初始化时创建的,并且永不释放。
- 结尾块是一个大小为零的结束块,是由一个头部组成,总是以它为结束。
- 为了方便,可以将重复使用的,操作复杂又有重复性的,这些可以定义成宏,方便使用也方便修改。
- 注意强制类型转换。
分离的空闲链表
- 分离存储,是一种流行的减少分配时间的方法。维护多个
空闲链表,其中每个链表中的块都有大致相等的大小。,思路是将所有可能的块大小分成一些大小类。 - 简单分离存储:每个大小类的空闲链表包含大小相等的块,每个块的大小就是这个大小类中最大元素的大小。
- 分离适配:每个空闲链表是和一个大小类相关联的,并且被组织成某种类型的显示或隐式链表,每个链表包含潜在的大小不同的块,这些块的大小是大小类的成员。这种方法快速并且对存储器使用很有效率。
- 伙伴系统是分离适配的一种特例,其中每个大小类都是2的幂。
- 伙伴系统分配的主要优点是:快速检索,快速合并。
垃圾收集
- 垃圾收集器是一种动态内存分配器,它自动释放程序已经不再需要的已分配块,这些块称为垃圾。
- 垃圾收集器将内存视为一张有向可达图,图的节点被分配为一组根节点和一组堆节点。当存在一条从任意根节点出发到并到达P的有向路径时,就称节点P是可达的。
- Mark&Sweep垃圾收集器由标记阶段和清除阶段组成,标记阶段标记出根节点所有可达的和已分配的后继,而之后的清除阶段释放每个未被标记的已分配块。
C程序中常见的与内存有关的错误
- 间接引用坏指针:在进程的虚拟地址空间中有较大的洞,没有映射到任何有意义的数据,如果试图引用一个指向这些洞的指针,操作系统就会以段异常来终止程序。典型的错误是:
scanf("%d",val);
。 - 读未初始化的内存:bass内存位置总是被加载器初始化为0,但对于堆内存却并不是这样的。典型的错误:假设堆存储器被初始化为0。
- 允许栈缓冲区溢出:如果一个程序不检查输入串的大小就写入栈中的目标缓冲区,程序就会出现缓冲区溢出错误。
- 假设指针和它们指向的对象是相同大小的。
- 造成错位错误。
- 引用指针,而不是它所指向的对象。
- 误解指针运算。
- 引用不存在的变量。
- 引用空闲堆块中的数据。
- 引用内存泄漏。
教材学习中的问题和解决过程
(一个模板:我看了这一段文字 (引用文字),有这个问题 (提出问题)。 我查了资料,有这些说法(引用说法),根据我的实践,我得到这些经验(描述自己的经验)。 但是我还是不太懂,我的困惑是(说明困惑)。【或者】我反对作者的观点(提出作者的观点,自己的观点,以及理由)。 )
代码调试中的问题和解决过程
本周无代码问题。
代码托管
本周虚拟机出现了问题 ,还没能修复成功。
上周考试错题总结
上周由于系统问题,测试未能提交,因此有博客补充。
结对及互评
点评模板:
- 博客中值得学习的或问题:
- xxx
- xxx
- ...
- 代码中值得学习的或问题:
- xxx
- xxx
- ...
- 其他
本周结对学习情况
- [20155306](https://home.cnblogs.com/u/0831j/)
- 结对照片
- 结对学习内容
- 第九章内容
其他(感悟、思考等,可选)
本章内容可以说比较基础,但是学习了本章发现很多基础的内容不清楚,这对我们平常的代码练习,有一定的阻碍,学习了本章,对这方面有了一定的提高,同时对内存系统有了一定的了解。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 300/1300 | 2/9 | 30/90 | |
第五周 | 300/1300 | 2/9 | 30/90 | |
第六周 | 706/2006 | 1/10 | 50+/140+ | |
第七周 | 838/2838 | 1/11 | 23/163 | |
第八周 | 150/3088 | 2/13 | 40/203 | |
第九周 | 1235/4323 | 3/16 | 27/280 | |
第十一周: 本周虚拟机出现了问题 ,所以暂时无法计算代码量。 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:30小时
-
实际学习时间:34小时
-
改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)