2021-07-19虚拟化内存(一)

本文深入探讨了虚拟化内存的三个目标:效率、易用性和保护。介绍了C语言中栈和堆的内存分配,以及动态内存管理的常见错误。接着,详细阐述了动态地址重定位、硬件在内存管理中的作用、操作系统的任务,以及内存分段和分页技术,包括段寄存器、页表、页表项的各个字段。最后,讨论了内存管理中的外部碎片问题和解决方案,如空闲列表管理和紧凑方法。
摘要由CSDN通过智能技术生成

虚拟化内存要达到的三个目标
1、效率。早期由一个程序独占全部的内存,上下文切换的时候要将当前所有的状态(寄存器中的数据,代码段,数据段,堆栈段等)移入磁盘,将磁盘上其他的程序移入到内存中去。磁盘和内存中交换数据的效率较低。因此在进行程序切换的时候依然将程序有关的数据保存在内存当中。
2、易于使用。将分散的物理地址抽象成一大快给程序的连续的地址,便于程序的使用。
3、保护。不同的程序不能将数据存储在同一块内存里面防止被覆盖,禁止程序访问其他程序所在的内存空间。

C语言中的内存接口——通过此来了解、分配和回收内存。
C语言中获取的数据地址通常是操作系统为该程序分配的虚拟地址。
栈:局部变量、函数调用的返回地址。通常在函数调用结束后,该函数栈所占用的空间被系统回收。
堆:由程序员主动请求的分配的内存来自堆。在程序结束后由操作系统回收。
全局变量和静态局部变量是否存储在堆中?
转自一个回答
在这里插入图片描述
通常用malloc来进行分配

type* p = (type*)malloc(sizeof(type));

1)分配成功后得到指向该内存空间的地址,分配失败返回null
2)给字符串分配空间

char* p = (char*)malloc(sizeof(char)*(strlen(str)+1));

不要忘记字符串末尾的空字符,否则会造成缓冲区溢出(缓冲区是指动态分配的缓冲区?)
相应的,用free()来释放被分配的内存。接受malloc()分配后的指针作为参数。

常见的错误
1)忘记分配内存
没有为要拷贝目的的指针分配所指向的内存
(不是特别清楚会有什么安全隐患)
发生段错误
2)没有分配足够的内存
可能会覆盖其他已经使用的内存上面的数据造成安全隐患。
3)忘记初始化内存
4)忘记释放内存
会造成内存泄漏,导致堆区可用的内存越来越少直到无法分配内存导致程序崩溃。自己写的小程序泄漏的内存在程序结束后很快被系统回收,但是大型服务器上面一直运行的程序,内存泄漏会导致程序崩溃。
5)在用完之前释放内存
(会导致段错误?)
6)反复释放内存
7)错误的调用free()
(6、7不明白)

简单的使用内存(内存被分为同样大小的一块,最上面放代码段,紧接着代码段之后是向下增长的堆段,分配到的内存的底部是向上增长的栈段。但是这样会导致堆栈之间有不少内存未被使用,内部碎片由此而来。)
1、动态地址的重定位——基地址(基址寄存器)+界限(界限寄存器)
每个CPU里面有一对,在内存管理单元当中(MMU)
当前程序要执行某条指令时
指令的物理地址 = 基址寄存器 + 偏移地址(程序计数器)
每次访问该物理地址前,由硬件检查是否越界(过大或为负数)。
在进行上下文切换的时候,需要保存基址寄存器和界限寄存器中的数据。

2、硬件的任务
1)提供基地址寄存器和界限寄存器。
2)特权模式,能够在用户程序试图更改基址寄存器和界限寄存器(更改这两个寄存器为特权操作,由操作系统完成)的时候发出异常指令
3)用户程序非法访问其他地址的时候会发出异常。

3、操作系统的任务
1)创建程序的时候,从空闲列表(free list)中选取一段内存,分配给进程,然后初始化相应的base和bounds
2)结束进程的时候,回收内存,更新空闲列表
3)上下文切换的时候,保存当前进程的base和bounds,读入下一个要运行的进程的base和bounds。
4)处理异常机制,当进程试图越界访问的时候终止进程

内存分段(泛化的基地址和界限)
在逻辑上分成多段,如代码段,栈段,堆段等。
按照这样的话,需要多对寄存器,分别存放每一段的基地址和段界限。但是这样是不现实的,所以会在虚拟地址的前一位或者前两位用不同的01组合来表示不同的段)

1、栈的特殊处理
栈的特殊之处在于地址是从高地址向低地址增长的,这和其他的段的地址增长方向不同,所以需要额外使用一位来标记地址的增长方向。
1)栈底的地址为栈段的基地址
2)给出的地址为正向偏移量
3)计算方法:反向偏移量 = 正向偏移量 - 段的长度
反向偏移量 + 基地址 = 实际物理地址

2、保护位
由于不同的程序可能有相同的读写操作和使用I/O的操作,为了节省空间,不同的程序可以共享同一段代码。为了支持共享,防止程序纂改共享代码,需要对共享的代码进行保护,设置该段代码只读或不能执行。需要在段寄存器内额外找一位为保护位,用来鉴定程序的权限。

3、分段的粗细
粗粒度:只分成几段
细粒度:分成很多的小段

4、操作系统的支持
1)上下文切换的时,保存和恢复段寄存器等寄存器里的值
2)非法访问的时候及时终止程序

5、问题——外部碎片
由于每个程序的大小不同,分到的段内的内存不同,但一个使用内存较小的程序结束运行内存被释放以后,留下来的内存空间是无法给需要内存较大的程序使用的。
有两个解决方法
1)空闲列表管理法
用列表来维护没有分配的空间的信息。
分割:在程序申请空间的时候,将足够多的空间分配给程序。(还要额外附加头块【头块有幻数作为完整性检查,和分配的空间的长度的信息】来记录头部信息)。

合并:在程序结束后,释放的内存加入空闲列表,并遍历空闲列表,将相邻的空闲空间合并成一块。

回收分配的空间:会通过分配的块的指针的位置来计算出头指针的位置,(后面的过程不是很清楚)。

增加堆的空间:如果向程序申请空间,堆的空间不够,此时会返回空指针;或者程序向操作系统申请更大的空间,得到了满足后会显示申请成功。

基本策略:

  • 最优匹配
    遍历列表,找到所有可以分配给程序的空闲空间,从中挑选出最小的空间分配给程序。
    遍历+排序,性能较差。
  • 最差匹配
    遍历列表,找到所有可以分配给程序的空闲空间,从中挑选出最大的空间分配给程序。
    遍历+排序,性能较差。
    此外,较长时间后内存的碎片较多不利于分配。
  • 首次匹配
    遍历空闲列表,直到遇到第一个可以分配的空闲空间后不再遍历。
    减少遍历所花费的开销。
  • 下次匹配
    用一个指针保留上次遍历到的位置,下次从上次保留的位置开始向下搜寻

空闲列表的其他方法

  • 分离空闲列表
    额外开辟一个空闲列表,通过该列表分配的空间的大小固定,其大小为程序频繁申请的空间的大小
    (具体实现的过程没有看懂)
  • 伙伴系统
    将空闲的空间不断一分为二,直到最小的程序再次分成两份无法满足程序所需要的大小。
    回收的时候则检查相邻的同样大小的空间是否为空,空的话就合并,然后向上逐级合并直到无法合并。
    不明白互为伙伴块的内存地址只有一位不同(这个真的不懂)
    缺点:每次分配的大小为2的n次方,可能会有内部碎片

2)紧凑的方法
将一个程序的代码移动到紧挨着前一个程序的代码的末尾(具体过程为:移动到内存——磁盘——内存,更改寄存器中的值),但是这样效率非常低下。

内存分页
内存分页存在着一些小问题
1)物理页号是操作系统分配的吗?
2)物理地址是固化在硬件里面的吗?

1、将物理内存分成同样大小的页帧,分配给程序的页也是同样大小的。页面的大小由页的偏移量所占有的位数决定(能够表示的存储单元的个数)。

2、页表。
1)VPN -> PFN(通过页表完成虚拟页到物理页帧的转换)
2)页表可以存放在内存或者是磁盘上

一些问题:
页表是每个进程有还是所有的进程共有?
物理页表表项需要存放偏移量吗?
内地址的页偏移量和块内偏移量是一致的,因此地址转换时就不必考虑偏移量

物理页表表项
1)物理页对应的虚拟帧的映射关系
2)有效位——是否空闲,有无分配给其他的进程
3)保护位——读写权限和访问权限
4)参考位——近期是否被访问过,和页面置换算法有关
5)存在位——是存在于内存还是在磁盘上
6)脏位——是否和磁盘上的数据不一致
好像和写回和写直达有关
(问题:内存上的一切是否在磁盘上都有备份?
页表在何处需要执行额外的内存引用?
每访问一次物理内存,需要查找页表,页表存在内存当中,这便是额外的内存访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值