2017-2018-1 20155308
《信息安全系统设计基础》第十一周学习总结
教材学习内容总结
前序
- 虚拟内存重要性(为什么需要程序员理解它?):
- 虚拟存储器是核心的
- 虚拟存储器是强大的
- 虚拟存储器是危险的。
- 虚拟存储器提供的的三个重要能力:
- 它的主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效的使用了主存。
- 它为每个进程提供了一致的地址空间,从而简化了存储器管理。
- 它保护了每个进程的地址空间不被其他进程破坏。
物理和虚拟寻址
CPU根据物理地址访问存储器的方式是物理寻址。
使用虚拟寻址时,CPU通过生成一个虚拟地址VA来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址。
虚拟内存作为内存保护的工具
在PTE上添加一些额外的许可位来控制一个虚拟页面内容的访问十分简单
地址空间
-
地址空间是一个非负整数地址的有序集合:{0,1,2,……}
-
虚拟地址空间:在一个带虚拟内存的系统中,CPU从一个有 N=2^n 个地址的地址空间中生成虚拟地址,这个地址空间成为称为虚拟地址空间:{0,1,2,……,N-1} 。
-
物理地址空间:对应于系统中的物理存储器的M个字节:{0,1,2,……,M-1} 。
虚拟存储器作为缓存的工具
- VM系统将虚拟内存分割为虚拟页,每个虚拟页大小为P=2^p字节。
- 物理存储被分割为物理页,大小也为P字节。
- 任意时刻,虚拟页面的集合都被分为三个不相交的子集:
- 未分配的:VM系统还没分配(创建)的页,不占用任何磁盘空间。
- 缓存的:当前缓存在物理存储器中的已分配页。
- 未缓存的:没有缓存在物理存储器中的已分配页。
页表事例:
如果设置了有效位:地址字段表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。
如果没有设置有效位:
- 空地址:表示该虚拟页未被分配
- 不是空地址:这个地址指向该虚拟页在磁盘上的起始位置。
虚拟存储器作为存储器管理的工具
- 按需页面调度和独立的虚拟地址空间的结合,对系统中内存使用和管理造成了深远理解。VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配。
- 简化链接:独立的地址空间允许每个进程的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处。
- 简化加载:虚拟存储器使得容易想存储器中加载可执行文件和共享文件对象。
- 简化共享:独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。
有一些情况中,还需要进程来共享代码和数据:
- 简化存储器分配:虚拟存储器为向用户进程提供一个简单的分配额外存储器的机制。
地址翻译
本节所需符号的表格:
- 当页面命中时,CPU硬件执行步骤 •处理器生成虚拟地址,传给MMU
- MMU生成PTE地址,并从高速缓存/主存请求得到他
- 高速缓存/主存向MMU返回PTE
- MMU构造物理地址,并把它传给高速缓存/主存
- 高速缓存/主存返回所请求的数据给处理器。
- 处理缺页时,CPU硬件执行步骤
- 处理器生成虚拟地址,传给MMU
- MMU生成PTE地址,并从高速缓存/主存请求得到他
- 高速缓存/主存向MMU返回PTE
- PTE中有效位为0,触发缺页异常
- 确定牺牲页
- 调入新页面,更新PTE
- 返回原来的进程,再次执行导致缺页的指令,会命中
利用TLB加速地址翻译
- 步骤:
- CPU产生一个虚拟地址
- MMU从TLB中取出相应的PTE
- MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存
- 高速缓存/主存将所请求的数据字返回给CPU
研究:Intel Core i7/Linux存储器系统
Linux虚拟存储器系统:
linux将虚拟存储器组织成一些区域(也叫做段)的集合。一个区域就是已经存在的(已分配的)虚拟存储器的连续片
- 一个具体区域的区域结构:
- vm _start:指向这个区域的起始处;
- vm _end:指向这个区域的结束处;
- vm _prot:描述这个区域内所包含的所有页的读写许可权限;
- vm _fags:描述这个区域内的页面是与其他进程共享的,还是这个进程私有的,等等;
- vm _next:指向链表的下一个结构。
- Linux缺页异常处理:
- 看虚拟地址A是否合法?
- 看试图进行的内存访问是否合法?
- 处理缺页
内存映射
内存映射:Linux通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容的过程,这个过程称为内存映射。
- 对象:
- Unix文件系统中的普通文件
- 匿名文件(全都是二进制0)
-
再看共享对象
-
再看fork函数
fork函数被当前进程调用时,它创建了当前进程的mm_struct、区域结构和页表的原样拷贝。它将两个进程中的每个页面都为标记只读,并将两个进程中的每个区域结构都标记为私有的写时拷贝。 -
再看execve函数
使用execve函数将a.out程序加载到内存
步骤:
- 删除已存在的用户区域。
- 映射私有区域。
- 映射共享区域。
- 设置程序计数器。
- 使用mmap函数的用户级存储器映射
动态存储器分配
当运行时需要额外虚拟存储器时,使用动态存储器分配器维护一个进程的虚拟存储器区域。
-
系统调用malloc函数,从堆中分配块:
-
系统调用free函数来释放已分配的堆块:
-
实现一个简单的分配器:
- 通用分配器设计
- 操作空闲链表的基本常数和宏
- 创建初始空闲链表
- 释放和合并块
- 分配块
C程序中常见的与存储器有关的错误
- 间接引用坏指针
- 读未初始化的存储器
- 允许栈缓冲区溢出
- 假设指针和指向他们的对象大小是相同的。
- 造成错位错误。
- 引用指针,而不是他所指向的对象。
- 误解指针运算
- 引用不存在的变量
- 引用空闲堆块中的数据
- 引起存储器泄露
教材学习中的问题和解决过程
如何进行一个简单分配器的实现?
- 创建初始空闲链表:
创建带一个初始空闲块的堆
int mm_init(void) //初始化,成功返回0,失败返回
{
mem_init();
if ( (heap_listp = mem_sbrk(4 * WSIZE)) == (void *)-1)
return -1;
PUT(heap_listp, 0);
PUT(heap_listp + WSIZE, PACK(8, 1)); //序言块头部
PUT(heap_listp + 2*WSIZE, PACK(8, 1)); //序言块尾部
PUT(heap_listp + 3*WSIZE, PACK(0, 1)); //结尾块
heap_listp += 2*WSIZE;
if (extend_heap(CHUNKSIZE/WSIZE) == NULL)
return -1;
return 0;
}
用一个新的空闲块扩展堆
static void *extend_heap(size_t words)
{
char *bp;
size_t size;
size = (words % 2) ? (words + 1) * WSIZE : words * WSIZE;
if ((long)(bp = mem_sbrk(size)) == -1)
return NULL;
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));
return coalasce(bp);
}
释放一个块
void mm_free(void *bp)
{
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
coalesce(bp);
static void *coalesce(void *bp)
{
size_t prev_alloc = GET_ALLOC(HDRP(PREV_BLKP(bp)));
size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
size_t size;
if (prev_alloc && next_alloc)
{
return bp;
}
else if (prev_alloc && !next_alloc)
{
size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size,0));
}
else if (!prev_alloc && next_alloc)
{
size += GET_SIZE(HDRP(PREV_BLKP(bp)));
PUT(FTRP(bp), PACK(size, 0));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
bp = PREV_BLKP(bp);
}
else
{
size += GET_SIZE(HDRP(PREV_BLKP(bp))) +
GET_SIZE(FTRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
bp = PREV_BLKP(bp);
}
return bp;
}
}
分配块
void *mm_malloc(size_t size)
{
size_t asize; //调整过的size
size_t extendsize;
void *bp;
if (size == 0)
return NULL;
if (size < DSIZE)
asize = 2 * DSIZE;
else
asize = DSIZE * ((size + (DSIZE) + (DSIZE - 1)) / DSIZE);
if ( (bp = find_fit(asize)) != NULL)
{
place(bp, asize);
return bp;
}
extendsize = MAX(asize, CHUNKSIZE);
if ( (bp = extend_heap(extendsize/WSIZE)) == NULL )
return NULL;
place(bp, asize);
return bp;
}
代码中的问题
缺少csapp.h头文件,所以我从网上下载后
https://gitee.com/haowenfei25/XinXiAnQuanXiTongSheJiJiChu20155308/blob/master/week10/csapp.h
运行
结果显示
上周考试错题总结
- Unix/Linux中,对于代码fd=open("foo",O_WRONLY,0766),umask=022,下面说法正确的是()
A.进程对foo是只写的
B.同组成员能写foo
C.使用者可以执行foo
D.任何人都可以写foo
- 正确答案: A C
- 我的答案: A B C D
- 解析:见书p624,p625 九个权限可以用0777表示,07(使用者)7(同组成员)7(其他人)
7(111)(读:写:执行),文件实际权限是mode&~mask
10
- 关于open(2),下面说法正确的是( )
A.flag 参数中O_RDONLY,O_WRONLY,O_RDWR至少要有一个
B.O_RDONLY|O_WRONLY == O_RDWR
C.fd=open("foo.txt",O_WRONLY|O_APPEND,0),调用write(fd,buff,n)写入foo.txt的数据不会破坏已有数据。
D.fd=open("foo.txt",O_WRONLY|O_APPEND,0644),必将导致其他人不能写foo.txt
- 正确答案: A C
- 我的答案: A C D
- 解析:熟悉umask命令, open第三个参数实际是mode & ~umask
- Linux中下列概念中可以用Unix I/O处理的是()
A.普通文件
B.设备文件
C.目录
D.套接字
- 正确答案: A B C D
- 我的答案: A C D
- 解析:书p623,Linux中其他文件类型,包含命名通道、符号链接,以及字符和块设备,都是可以的,但是在本书中不加讨论。
- 输入输出是针对()来讲的?
A.CPU
B.主存
C.I/O设备
D.计算机
- 正确答案: B
- 我的答案: C
- 解析:p622。所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作对应的文件的读和写来执行。这种将设备映射为文件的方式,允许Linux内核引出了一个简单低级的应用接口,成为Unix I/O。
本周代码托管截图
本周结对学习情况
- 20155316
- 一起学了第10章。
其他(感悟、思考等,可选)
本周学习了第九周-虚拟内存,主要学习了如何有效地管理内存的方法,通过虚拟内存的方法,合理地分配了内存地址,使得进程能够正常进行。
这个部分的学习内容在其他的科目中以及有所提及,所以学习起来比较容易。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 2000行 | 30篇 | 800小时 | |
第十周 | 200/200 | 2/5 | 30/85 |
- 计划学习时间:20小时
- 实际学习时间:30小时
- 改进情况:学习时间增加了。