chapter10_虚拟存储器

本文深入解析虚拟存储器的三大核心功能:作为缓存工具高效利用主存,为进程提供一致的地址空间,以及保护进程地址空间不受侵害。阐述了物理寻址与虚拟寻址的区别,详细介绍了地址空间、页表、页命中、缺页异常处理、局部性原理及虚拟存储器在存储器管理与保护中的应用。
  1. 前言

    虚拟存储器提供了三个重要的能力

    (1) 将主存当成是存储在磁盘上的地址空间的高速缓存。在主存中只保存活动区域,高效使用主存

    (2) 为每个进程提供了一个一致的地址空间

    (3) 保护了每个进程的地址空间不被其他进程破坏

  2. 物理和虚拟寻址

    1. 物理寻址

       CPU ----物理地址---- 主存
      

      应用场景:嵌入式微控制器、超级计算机

    2. 虚拟寻址

       CPU ----虚拟地址---- MMU ----物理地址---- 主存
      

      地址翻译:将虚拟地址转换为物理地址。需要CPU上的MMU硬件,和存放在主存的查询表(页表)来动态翻译虚拟地址,这个表由操作系统管理。

      应用场景:现代计算机

  3. 地址空间

    1. 虚拟地址空间:包含

       N = 2^n      {0, 1, 2, ... , N - 1}
      

      个地址,称为n位地址空间(一般n=32或64)

    2. 物理地址空间:与系统中物理存储器的M个字节相对应

       M = 2^m      {0, 1, 2, ... , M - 1}
      
    3. 主存中的每个字节,都有一个选自虚拟地址空间的虚拟地址,和一个选自物理地址空间的物理地址

  4. 虚拟存储器作为缓存的工具

    1. 虚拟存储器分割为多个虚拟页,每个虚拟页大小固定 P=2^p;物理存储器也被分割为多个物理页,每个页的大小也为P

    2. 虚拟页可以分为3种

      (1) 未分配的

      没有数据关联,不占磁盘空间

      (2) 分配但未缓存的

      没有缓存在物理存储器中的页

      (3) 分配且缓存的

      缓存在物理存储器中的页

    3. 由于不命中处罚很大,且访问第一字节的开销很高 —> 虚拟页倾向于很大(典型值为4-8KB)

    4. 页表

      (1) 示例

      名称有效位物理存储器地址
      PTE00null
      PTE11VP1
      PTE71VP7

      (2) 磁盘上创建空间时,使用的是页表中的虚拟地址,也就是PTEx。所以虚拟页可以分为三种,有一种是未分配的

      (3) 物理器存储器地址存的是映射的内存块的首地址。VPx可能是在物理存储器(DRAM)上,也可能在虚拟存储器(磁盘)上

      (4) 页表的本质是一个数组,而且这个数组往往非常大,例如32位虚拟地址,页大小为4K,数组内将有

       2^32 / 2^12 = 2^20
      

      个元素

    5. 三种情况

      (1) 页命中

      当CPU读取虚拟存储器的一个字时,地址翻译硬件将地址作为索引,找到页表中的元素,发现有效位为1(说明已分配),并且指向的物理存储器地址在内存中,则命中

      (2) 缺页

      有效位为1(说明已分配),但是指向的物理存储器地址不在内存中,此时触发缺页异常

      内核中的缺页异常处理程序会选择内存中的一个牺牲页,把它拷贝回磁盘;然后把那个需要的页从磁盘拷贝到内存

      最后,更新页表

      注:只有命中不发生时,才进行页面调度,称为按需页面调度

      (3) 分配页面

      在磁盘上创建空间时(例如malloc),内核会在磁盘上分配页,然后更新页表

    6. 局部性

      虽然页表很大,换页调度很耗时,但是绝大部分用到的页都是在一个较小的活动页面集合中,所以一般虚拟存储器系统都能工作得很好。

      经常发生换页叫做颠簸,应该可以用 getruusage 检测缺页的数量

  5. 虚拟存储器作为存储器管理的工具

    1. 事实上操作系统为每一个进程提供了一个独立的页表,所以os中页表其实有好几个

      作为存储器管理的工具,好处主要有4个

    2. 简化链接

      独立的地址空间允许每个进程为它的存储器映像使用相同的基本格式

       文本区总是从 0x08048000 处开始向上增长
      
       栈总是从 0xbfffffff 向下扩展
      
       共享库代码总是从 0x40000000 处向上增长
      
       操作系统代码和数据总是从 0xc0000000 向上增长
      

      —> 简化了链接器的设计和实现

    3. 简化共享

      进程私有的代码和数据,操作系统为每个进程创建页表,映射到不同的物理页面;

      共享的代码和数据(例如系统函数),操作系统将不同进程中适当的虚拟页面映射到相同的物理页面,从而安排多个进程共享这部分代码的一个拷贝

    4. 简化存储器分配

      只需要在虚拟存储器层面连续分配页面,实际映射到的物理存储器页面可以不连续

    5. 简化加载

      Linux加载程序分配一个从地址 0x08048000 处开始连续的虚拟页面区域,将它们标识为分配但未缓存的,需要用到的时候再调入存储器

  6. 虚拟存储器作为存储器保护的工具

    1. 页表中添加许可位

      示例

      进程 i 的页表

      虚拟地址SUPREADWRITE物理地址
      VP0010PP6
      VP1011PP4
      VP2111PP2

      进程 j 的页表

      虚拟地址SUPREADWRITE物理地址
      VP0010PP9
      VP1111PP6
      VP2011PP11

      其中 SUP 代表是否必须内核模式可访问,READ代表是否可读,WRITE代表是否可写

      其中进程i的VP0和进程j的VP1映射到的物理地址是相同的,代表这个页它们是共享代码或数据

  7. 存储器映射

    1. 定义

      虚拟存储器区域磁盘上的一个对象关联起来

    2. 磁盘上的对象包括2类

      (1) Unix普通文件

      (2) 匿名文件

      由内核创建,包含的都是二进制0

    3. 共享对象

      (1) 每个进程都有私有的虚拟地址空间,可以免受其他进程的错误读写

      (2) 磁盘上的对象可以被映射到虚拟存储器的一个区域,要么是共享对象,要么是私有对象

      (3) 如果是共享对象,那么物理存储器中只放共享对象的一份拷贝,这显而易见

      (4) 如果是私有对象,则采取写时拷贝技术(copy-on-write)

      (5) 写时拷贝技术

      1° 一开始,一个私有对象和共享对象一样,物理存储器中只有它的一份拷贝。这个对象可能占据了几个页,所有页的页表许可位都初始标记为只读,并且整个区域标记为私有的写时拷贝

      2° 当某个进程想要对私有对象修改时(写操作),此时由于初始标记为只读,因此会触发异常;异常处理程序会将修改的部分涉及的页,在物理存储器中创建这些页的新拷贝,然后恢复这些页面的可写权限

      写时拷贝技术保证了只有在不得已的时刻才对私有对象进行拷贝,延迟到最后时刻,节省了物理存储器这个稀缺资源。

      —> fork函数就用到了写时拷贝,一开始子进程和父进程的私有对象是放在一起的,需要写的时候再拷贝涉及到的页

    4. 用户级存储器映射

      (1) mmap 函数

       void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
      
       // 成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1).
      

      要求内核创建一个新的虚拟存储器区域,并将文件描述符fd指定的对象的一个连续的组块映射到这个新的区域

      进程之间通过这个方法映射同一个普通文件实现共享内存

      (2) munmap 函数

       int munmap(void* start, size_t length);
      

      删除从虚拟地址start开始,长为length字节组成的区域

  8. 动态存储器分配

    1.          用户栈
                 |
                 |
                 V
                ...
       共享库的存储器映射区域
                ...
                 ^
                 |
                 |
                堆顶 <--- brk 指针
                 ^
                 |
                 |
                 堆 
        未初始化的数据 .bss
        已初始化的数据 .data
        程序文本       .text
      

      对于每个进程,内核维持一个brk指针指向堆

    2. (1) 显式分配器

      要求应用显式释放任何已分配的块

       C:    malloc / free
      
       C++:  new / delete
      

      (2) 隐式分配器 --> 又叫垃圾收集器

      分配器检测何时一个块不再被程序使用,然后释放这个块

      例如 Java

    3. malloc

       #include <stdlib.h>
      
       void* malloc(size_t size);
      
       // 返回一个指针,指向大小为至少size的存储器块,至少的意思是可能因为对齐而扩大
      
    4. free

       #include <stdlib.h>
      
       void free(void* ptr);
      
       // 释放已分配的块,ptr必须是已分配块的起始位置
      

      free以后,ptr指针指向的还是原有位置,所以手动置为null比较好

    10.9.3——10.9.14没看

  9. 垃圾收集

    1. 一种经典的方法是 Mark&Sweep 标记——清除

      Java中可以通过可达性分析,准确进行GC,因为Java中指针的创建和使用有严格的控制,但是C是不行的,因此只能叫保守的垃圾收集器

    2. C中无法精确控制GC的原因

      C语言不会用类型信息来标记存储器位置,所以int这样的标量可以伪装成指针

      例如一个int值恰好等于某个不可达块的地址,那么垃圾收集器无法分清传入的是个标量值还是一个真正的指针

  10. C程序中常见的与存储器有关的错误

    1. 间接引用坏指针

      错误示例

       scanf("%d", val);   // 应该传入变量val的地址而非值,但是C语言却无法判断到底是指针还是标量
      
    2. 读未初始化的存储器

      .bss 存储器(例如未初始化的全局C变量)总是被初始化为0

      但是堆存储器不是被初始化为0

    3. 允许栈缓冲区溢出

      不检查输入串的大小,就写入栈中的目标缓冲区,就可能发生缓冲区溢出错误

    4. 假设指针和它们指向的对象是相同大小的

      sizeof(int) 未必等于 sizeof(int*)

    5. 错位错误

       int** A = (int**)malloc(n*sizeof(int)); // 这里分配的是n
      
       for (int i = 0; i <=n; i++)   // 这里用的是n+1
           A[i] = (int*)malloc(m * sizeof(int));
      
    6. 引用指针,而不是它所指向的对象

       *size-- 代表的是 *(size--),不是(*size)--
      
    7. 误解指针运算

      指针的算术操作以它们指向的对象的大小为单位进行

       int* p = (int*)malloc(sizeof(int) * 4);
       p++;   // 代表每次移动 sizeof(int)这么多地址,不需要 p += sizeof(int);
      
    8. 引用不存在的变量

      例如返回一个栈空间的变量的地址

       int* stackRef() {
      
           int val;
           return &val;
       }
      
    9. 引用空闲堆块中的数据

      一个空间已经被释放,但是指针没有置为null

       int* x = (int*)malloc(n * sizeof(int));
      
       free(x);
      
       (*x) = 6;
      
    10. 引起存储器泄漏

      只 malloc/new,不 free/delete

  11. 应用程序可以使用mmap函数来手工创建和删除虚拟地址空间的区域;

    大多数程序依赖动态存储器分配器malloc/free

【3D应力敏感度分析拓扑优化】【基于p-范数全局应力衡量的3D敏感度分析】基于伴随方法的有限元分析和p-范数应力敏感度分析(Matlab代码实现)内容概要:本文档介绍了基于伴随方法的有限元分析与p-范数全局应力衡量的3D应力敏感度分析,并结合拓扑优化技术,提供了完整的Matlab代码实现方案。该方法通过有限元建模计算结构在载荷作用下的应力分布,采用p-范数对全局应力进行有效聚合,避免传统方法中应力约束过多的问题,进而利用伴随法高效求解设计变量对应力的敏感度,为结构优化提供关键梯度信息。整个流程涵盖了从有限元分析、应力评估到敏感度计算的核心环节,适用于复杂三维结构的轻量化与高强度设计。; 适合人群:具备有限元分析基础、拓扑优化背景及Matlab编程能力的研究生、科研人员与工程技术人员,尤其适合从事结构设计、力学仿真与多学科优化的相关从业者; 使用场景及目标:①用于实现高精度三维结构的应力约束拓扑优化;②帮助理解伴随法在敏感度分析中的应用原理与编程实现;③服务于科研复现、论文写作与工程项目中的结构性能提升需求; 阅读建议:建议读者结合有限元理论与优化算法知识,逐步调试Matlab代码,重点关注伴随方程的构建与p-范数的数值处理技巧,以深入掌握方法本质并实现个性化拓展。
下载前必看:https://pan.quark.cn/s/9f13b242f4b9 Android 平板设备远程操控个人计算机的指南 Android 平板设备远程操控个人计算机的指南详细阐述了如何运用 Splashtop Remote 应用程序达成 Android 平板设备对个人计算机的远程操控。 该指南被划分为四个环节:首先,在个人计算机上获取并部署 Splashtop Remote 应用程序,并设定客户端密码;其次,在 Android 平板设备上获取并部署 Splashtop Remote 应用程序,并与之建立连接至个人计算机的通道;再次,在 Splashtop Remote 应用程序中识别已部署个人计算机端软件的设备;最后,运用平板设备对个人计算机实施远程操控。 关键点1:Splashtop Remote 应用程序的部署与配置* 在个人计算机上获取并部署 Splashtop Remote 应用程序,可通过官方网站或其他获取途径进行下载。 * 部署结束后,必须输入客户端密码,该密码在平板控制计算机时用作验证,密码长度至少为8个字符,且需包含字母与数字。 * 在配置选项中,能够设定是否在设备启动时自动运行客户端,以及进行互联网搜索设置。 关键点2:Splashtop Remote 应用程序的 Android 版本获取与部署* 在 Android 平板设备上获取并部署 Splashtop Remote 应用程序,可通过 Google Play Store 或其他获取途径进行下载。 * 部署结束后,必须输入客户端密码,该密码用于连接至个人计算机端软件。 关键点3:运用 Splashtop Remote 远程操控个人计算机* 在 Splashtop Remote 应用程序中识别...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值