所在小组
第六组
组内昵称
undefined
你的心得体会
地址翻译
地址翻译:程序如何从虚拟地址转换为物理地址从过程
初始数据
逻辑地址=0x80495b0
段基地址=0x1000000
线性地址=(逻辑+段基)=0x90495b0
预设页表=[0x9049:0x0000000]
物理地址=(页表+offset)=0x00005b0
核心问题:
段基地址的管理?
页表的管理?
权限的控制?
各种寄存器的设计
GDT: Global Descriptor Table,全局描述符表
GDTR:用来存放GDT的入口地址
LDT: Local Descriptor Table,局部描述符表
LDTR:用来存放LDT的入口地址
PDBR:页目录基址寄存器,CR3寄存器
CS:代码段寄存器
DS:数据段寄存器
特权级保护
zsy619
2020 年11 月 15 日 13:47
43
所在小组
第六组
组内昵称
慎思明辨笃行
你的心得体会
虚拟内存9.6
第 1 步:处理器生成一个虚拟地址,并把它传送给 MMU。
第 2 步:MMU 生成 PTE 地址,并从高速缓存/主存请求得到它。
第 3 步:高速缓存/主存向 MMU 返回 PTE。
第 4 步:MMU 构造物理地址,并把它传送给高速缓存/主存。
第 5 步:高速缓存/主存返回所请求的数据字给处理器。
虚拟内存9.7
Linux内核下的缺页异常处理步骤:
判断给定的虚拟地址值本身是否合法。判断方法是把给定值和区域结构链表的每个结点的起止点比较。如果结果不合法,则触发一个段错误,终止进程。
判断对这个地址进行的操作是否合法。如果不合法,触发一个保护异常并终止进程。
如果前两步骤均未终止进程,则内核选择一个牺牲页面。如果该页面之前被修改过,则先写回磁盘。之后换入新的页面,更新页表。缺页处理程序返回,并把控制流交还给引发缺页异常的指令。之后MMU能正常地翻译给定的虚拟地址。
虚拟内存9.8
Linux 通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping),虚拟内存区域可以映射到两种类型的对象中的一种:
Linux 文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。如果区域比文件区要大,那么就用零来填充这个区域的余下部分。
匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU 第一次引用这样一个区域内的虚拟页面时,内核就在物理内存中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在内存中的。注意在磁盘和内存之间并没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页(demand-zero page)。
虚拟内存9.9
申请(虚拟)内存空间时,可以使用mmap、munmap函数,不过使用经过封装的动态内存分配器,更方便,移植性更好。
动态内存分配器维护一个进程的虚拟内存区域。对每个进程,内核维护一个变量brk,是一个指向堆顶的指针。
分配器将堆视为一组不同大小的块的集合。每个块是一个连续的虚拟内存片,或是已分配的,或是空闲的。
分配器存在两种风格:显式分配器、隐式分配器。显式分配器的典型例子就是malloc、free。隐式分配器广泛被如Java、Lisp等依赖垃圾回收机制的高级语言应用。本节之后讨论的内容都属于显式分配器。
unistd.h内声明了sbrk函数,使用该函数手动扩展和收缩堆大小。
显式分配器在设计时需要满足如下要求:
处理任意请求序列。分配器不可以假设未来任何分配和释放请求的顺序。
立即响应请求。意味着缓存、重新排列请求等策略在分配器设计中不可用。
只使用堆。为保证分配器扩展性,分配器使用的任何非标量数据结构(是啥我也不知道)都必须保存在堆中。
对齐块。分配器所分配的块必须满足操作系统和硬件的字节对齐要求,使块可以保存任何数据对象。
不修改已分配的块。只能操作或改变空闲块,意味着压缩已分配块这类技术在分配器设计中无法使用。
物理主存被组织成一个由连续字节组成的数组,每个字节都有一个唯一的物理地址。早期的CPU使用物理寻址,即CPU通过存储器总线直接读取主存上特定物理地址的内容到寄存器中。现代处理器使用虚拟寻址方式,通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址(地址翻译)。CPU上的存储器管理单元(MMU)利用存放在主存中的查询表(页表)来动态地翻译虚拟地址,该表的内容由操作系统管理。
地址翻译
当页命中时,CPU硬件的执行步骤如下:
处理器生成一个虚拟地址,并把它传送给MMU;
MMU生成PTE地址,并从高速缓存/主存中请求得到它;
高速缓存/主存向MMU返回PTE;
MMU构造物理地址,并把它传送给高速缓存/主存;
高速缓存/主存返回所请求的数据字给处理器; 可见页命中完全是由硬件处理的。
当缺页时,需要硬件和操作系统内核协作完成,步骤如下:
处理器生成一个虚拟地址,并把它传送给MMU;
MMU生成PTE地址,并从高速缓存/主存中请求得到它’
高速缓存/主存向MMU返回PTE;
PTE中的有效位为零,所以MMU触发一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序;
缺页处理程序确定出物理存储器中的牺牲页,如果这个页已经被修改了,则把它换出到磁盘;
缺页处理程序调入新的页,并更新存储器中的PTE;
缺页处理程序返回到原来的进程,再次执行导致缺页的指令,CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面现在缓存在物理存储器中,所以就会被命中,主存会将所请求的字返回给处理器。
在MMU中包括了一个关于PTE的小的缓存,称为TLB(翻译后备缓冲器)。在有TLB,且TLB命中的情况下,CPU的执行步骤如下:
处理器生成一个虚拟地址,并把它传送给MMU;
MMU从TLB中取出相应的PTE;
MMU构造物理地址,并把它传送给高速缓存/主存;
高速缓存/主存返回所请求的数据字给处理器;
垃圾收集
垃圾收集器将存储器视为一张有向图,由堆节点组成,每个堆节点对应堆中一个已分配的块,当存在一条从任意根节点(根节点是一些不在堆中的位置,但是它们包含指向堆中的指针)出发并达到p节点的有向路径时,就说节点p是可达的,而不可达的节点就是垃圾,是不能被应用再次使用的。垃圾收集器的角色是维护可达图的某种表示,并通过释放不可达节点来定期地回收它们。
所在小组
第七组
组内昵称
hayden
心得体会
为了更加有效的管理内存并且少出错,现在操作系统提供了一种主存的抽象概念,叫做虚拟内存。虚拟内存是计算机系统管理内存的一种技术,使得应用程序操作内存更加简单,应用程序认为内存是一个连续可用的内存,其实物理内存是有很多的碎片,部分存储在磁盘上,进行数据交换,使得编程更加容易,也方便了操作系统管理内存。
每个进程都有自己的虚拟地址空间,进程可以简单的认为在操作线性空间.虚拟内存带来的另一个好处就是可以简化链接和载入的结构。
操作系统使用虚拟寻址,将虚拟地址转换成物理地址进行地址翻译。虚拟内存的定义是基于对地址空间的重定义的,使的程序以为使用线性的连续的虚拟内存地址”,以为自己正在使用一大块的“连续”地址。
在进行地址映射时,如果发现要访问的页面不在内存中,会发生中断,当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统 必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法,页面置换算法的作用是实现虚拟存储管理。
sadame
2020 年11 月 15 日 13:57
49
所在小组
第一组
组内昵称
SADAME
心得体会
使用虚拟内存主要是基于下面三个考虑:
可以更有效率的使用内存:使用 DRAM 当做部分的虚拟地址空间的缓存
简化内存管理:每个进程都有统一的线性地址空间
隔离地址控件:进程之间不会相互影响;用户程序不能访问内核信息和代码
程序员通过动态内存分配(例如 malloc
)来让程序在运行时得到虚拟内存。动态内存分配器会管理一个虚拟内存区域,称为堆(heap)。
分配器以块为单位来维护堆,可以进行分配或释放。有两种类型的分配器:
显式分配器:应用分配并且回收空间(C 语言中的 malloc
和 free
)
隐式分配器:应用只负责分配,但是不负责回收(Java 中的垃圾收集)
程序可以用任意的顺序发送 malloc
和 free
请求,free
请求必须作用与已被分配的 block。
分配器有如下的限制:
不能控制已分配块的数量和大小
必须立即响应 malloc
请求(不能缓存或者给请求重新排序)
必须在未分配的内存中分配
不同的块需要对齐(32 位中 8 byte,64 位中 16 byte)
只能操作和修改未分配的内存
不能移动已分配的块
内存陷阱
关于内存的使用需要注意避免以下问题:
解引用错误指针
读取未初始化的内存
覆盖内存
引用不存在的变量
多次释放同一个块
引用已释放的块
释放块失败
zhilin
2020 年11 月 15 日 14:20
52
所在小组
第四组
组内昵称
zhilin
你的心得体会
虚拟内存
虚拟内存不只是“用磁盘空间来扩展物理内存”的意思,把内存扩展到磁盘只是使用虚拟内存技术的一个结果,
它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。
对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,
以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址。
是现代系统提供的一种对主存的抽象概念,
它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域按需在磁盘和主存之间来回传送数据;为每个进程提供了一致的地址空间,简化了内存管理;并且保护每个进程的地址空间不被其他进程破坏。
内存映射(memory mapping)
Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,已初始化这个虚拟内存区域的内容,这个过程称为内存映射。虚拟内存区域可以映射到两种 类型的对象中的一种:
Linux 文件系统中的普通文件: 一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。
文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。如果区域比文件区要大,那么就用零来填充这个区域的余下部分。
匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。
在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数
写时复制
只要没有进程试图写自己的私有区域,它们就可以在继续共享物理内存中所存储的共享对象的一个单独副本。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障(Protection failure). 当故障处理程序注意到保护异常是由于进程试图写私有的写时复制区域中的一个页面而引起的,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。当故障处理程序返回时,CPU重新执行这个写操作,现在在新创建的页面上这个写操作就可以正确执行了。
关于内存的常见的错误示例
间接引用坏指针,读取未初始化的内存,允许栈缓冲区溢出,假设指针和它们指向的对象大小相同,引用指针而不是它所指向的对象,误解指针运算,引用不存在的变量,以及引起内存泄漏
指针运算
指针的算术操作是以它们指向的对象的大小为单位来进行计算的。这个单位的大小并不一定是字节。例如,下面函数的目的是扫描一个int的数组,并返回一个指针。指向val首次出现的位置。
动态内存分配
动态内存分配器,将堆作为大小不同的块集合来维护。每个块要么是已分配的,要么是空闲的。
分配器核心目标:1.最大化吞吐率 2.最大化内存利用率
垃圾收集
Mark&Sweep收集器:由标记和清楚阶段组成