[操作系统] 进程地址空间管理

虚拟地址空间的初始化

缺页中断

缺页中断的概念

缺页中断(Page Fault Interrupt) 是指当程序访问的虚拟地址在页表中不存在有效映射(即该页未加载到内存中)时,CPU 会发出一个中断信号,请求操作系统加载所需的页面到内存。
这是现代操作系统实现 虚拟内存管理 的重要机制之一。


缺页中断的触发条件

缺页中断通常在以下情况下触发:

  1. 虚拟地址对应的页面不在内存中:
    • 页表中找不到对应的物理页帧(页表条目为空或无效)。
    • 常见于程序访问未加载到内存的代码段或数据段。
  2. 访问非法地址:
    • 程序试图访问一个不存在的虚拟地址(如超出地址空间范围)。
    • 操作系统会判断访问是否合法,非法访问将触发异常。

缺页中断的处理流程

以下是缺页中断的详细处理流程:

  1. 程序访问虚拟地址:
    • CPU 将虚拟地址拆分为页号和页内偏移量,根据页号查询页表。
  2. 检测页表:
    • 如果页表中没有找到对应的物理页帧(即页表项无效),触发缺页中断。
  3. 陷入内核:
    • 缺页中断引发陷入操作系统内核,操作系统负责处理。
  4. 检查页面是否合法:
    • 操作系统检查虚拟地址是否属于当前进程的合法地址范围:
      • 如果地址非法(如访问未分配的堆空间),操作系统会终止进程,抛出段错误(Segmentation Fault)。
      • 如果合法,进入下一步。
  5. 分配页面:
    • 操作系统为该虚拟页分配物理页帧。
    • 如果内存不足,则触发页面置换算法(如LRU、FIFO),将某些页面换出到硬盘(即交换分区或页面文件)。
  6. 加载页面:
    • 如果访问的页面是磁盘文件的一部分(如代码或数据),则将页面从磁盘加载到内存。
    • 将分配的物理页帧的地址填入页表,并将页表项标记为有效。
  7. 恢复程序执行:
    • 操作系统返回用户态,重新执行导致缺页的指令。
    • 由于页面已加载到内存,访问可以正常完成。

缺页中断的示例

假设一个程序试图访问数组元素,但数组较大,未全部加载到内存。以下是可能的情景:

int arr[100000]; // 数组在内存中未完全加载
for (int i = 0; i < 100000; i++) {arr[i] = i;  // 顺序访问
}
  1. 程序首次访问 arr[i] 时,CPU 查询页表,发现对应的虚拟页面未映射到物理内存,触发缺页中断。
  2. 操作系统加载对应页面到内存,并更新页表。
  3. 下一次访问已加载的页面时,程序可以直接读取,无需触发缺页中断。

示例总结

当一个应用程序数据过大的话,不会立即将所有的数据全部从硬盘上加载到物理内存中,会先加载一部分。但是在进程的虚拟地址空间中会将所有的数据对应的地址全部建立。于是当需要使用一个虚拟地址空间的时候会在页表中进行查找映射的物理地址,但是没有物理地址,还未加载进内存中。操作系统会动态加载数据,当需要的时候再申请物理空间,加载数据,然后建立映射关系。

即使加载到物理内存的数据是乱序存储的,通过页表的映射关系也可以进行有序的管理。

虚拟地址空间初始化

虚拟地址空间就是mm_struct,在task_struct中。作为对象,需要被初始化。

当一个新进程被创建(例如通过 forkexec 系统调用)时,操作系统会:

  1. 创建虚拟地址空间:
    • 为进程分配独立的虚拟地址空间,确保不同进程之间的地址空间隔离。
  2. 设置页表:
    • 页表初始状态为“未映射”(即页面不在物理内存中),以支持按需加载。
  3. 加载程序的基础信息:
    • 通过程序文件(如 ELF 文件)中的头部信息,划分代码段、数据段等区域。
    • 堆区和栈区只初始化元信息(如起始地址),实际分配时动态增长。
    • 所以大部分会在程序加载的时候就被初始化。

为什么要有进程地址空间

将地址从无需变有序

数据从磁盘加载到物理内存是动态加载的,顺序会变得无规则,甚至乱序。但是有了虚拟地址空间和页表,虚拟地址空间中各个区域的地址是有序的,然后通过页表进行映射,找到无序的物理内存地址,从而将物理地址进行有序管理。

地址转换时的合法性判定

当地址转换的时候,通过虚拟地址空间和页表可以对地址和操作进行合法性判定,防止直接操作物理内存造成损坏,进而保护物理内存

页表中对于每一个映射关系也会有权限(rwx...)存在,当进程又不合法操作的时候,操作系统会拒绝地址映射转换,甚至杀死进程。

野指针

从操作系统层面理解野指针:

  • 未初始化指针与页表:当一个指针未初始化时,它指向的虚拟地址是随机的。这个随机地址很可能在页表中没有对应的映射项。因为正常的内存分配(如通过mallocnew等操作)会由操作系统分配一段合法的虚拟地址,并在页表中建立映射。而未初始化的指针所指向的地址没有经过这样的分配过程,所以在页表中找不到对应的物理地址。
  • 已释放指针与页表:指针所指向的内存被释放后,操作系统会将这块内存对应的页表项标记为未使用或者分配给其他进程。如果继续使用这个指针访问内存,操作系统在查找页表时会发现这个虚拟地址对应的页表项已经不再有效。例如,一个动态分配的内存块被deletefree后,它所占用的虚拟地址范围在页表中的映射会被撤销或者改变,再次访问这个地址就会引发错误。
  • 越界指针与页表:当指针越界时,它可能会指向虚拟地址空间中未分配的区域。比如,一个数组指针越界后指向了数组之外的地址,这个地址可能超出了操作系统为该数组分配的合法虚拟地址范围。在页表中,这个越界的地址没有合法的物理地址映射,因为操作系统只会在合法的内存分配范围内建立页表映射。
  • 错误赋值指针与页表:如果指针被错误地赋值为一个非法的地址,这个地址在页表中很可能没有对应的映射。因为操作系统在进行正常的内存分配时,会确保分配的虚拟地址在页表中有合法的映射。而人为错误地给指针赋一个非法地址,打破了这种正常的映射关系。

查页表失败后,会反馈给操作系统,操作系统会处理进程,所以野指针会导致操作系统杀死进程,导致进程崩溃。

字符串常量为什么无法修改

常量字符串字面量(如<font style="color:rgb(6, 6, 7);">char* ptr = "Hello, World!"</font>)通常被存储在代码段(Text Segment)中。这是因为常量字符串在程序的整个运行过程中不需要修改,将它们放在代码段可以利用代码段的只读特性来保护这些字符串不被意外修改。

这就是在地址转换的时候权限拒绝了对数据的写操作,所以无法修改。

解耦合!

简单回顾一下程序加载进内存的过程:

  1. 在虚拟地址空间申请指定大小的空间(调整区域划分)。
  2. 加载程序,申请物理空间。
  3. 在页表中进行虚拟地址和物理地址的映射构建。

完成后,此时的物理地址就转化为了虚拟地址。提供给上层使用,用户无需关心底层的物理地址是什么,物理内存中是如何加载的,只是使用虚拟地址就可以了。

作为进程也只是关心对于虚拟地址的使用,而不关心实际物理内存的存储。

如图所示。task_struct和其中管理的mm_struct所形成的关系对于进程来说只是负责进程的调度,而不关心如何管理调度的数据存储和加载。

而对于物理内存部分来说,只进行内存的管理,加载物理内存。

二者通过页表的映射关系来解耦合,让进程管理和内存管理进行一定程度的解耦合!

Tips

  1. 可以不加载代码和数据,只加载task_structmm_struct(只拿到main代码的起始地址),页表。

CPU在拿到起始地址后,当访问虚拟地址时,会有标识证明没有加载过物理内存,所以会缺页中断,然后开始加载物理内存。

  1. 创建进程的时候,先有task_struct,mm_struct等,还是先加载代码和数据?

先有内核数据结构,然后再陆续加载物理内存。

  1. 如何理解进程挂起?

进程进入挂起状态时,操作系统找到对应的进程,清空页表的物理地址部分,将物理地址对应的数据全部换入磁盘swap分区。只保留虚拟地址空间中的虚拟地址和页表的虚拟地址部分。

当挂起状态结束时,将swap分区的数据全部换出加载到物理内存中,然后再页表中建立映射。这就是解耦的好处,将进程调度与内存管理完全解耦。

vm_area_struct

对于在程序中动态申请的空间来说,一般会申请在堆区中。但是在程序中可能会频繁的申请,难道对于虚拟地址空间中的堆区来说,不只有一个起始地址吗?可能是离散分布的吗?

虚拟空间的组织⽅式有两种:

  1. 当虚拟区较少时采取单链表,由mmap指针指向这个链表;
  2. 当虚拟区间多时采取红⿊树进⾏管理,由mm_rb指向这棵树。

mm_struct 并不是直接就是整个虚拟地址空间,而是包含了一个指向虚拟内存区域(VMA)列表的指针,这个列表是由 vm_area_struct组成的。每个 vm_area_struct 表示进程地址空间中的一个连续区域,具有相同的权限和映射类型。所以离散申请的堆空间可以用vm_area_struct进行管理,并且所有的区域都可以统一使用vm_area_struct进行管理。

为了高效查找区域,所以用红黑树来进行管理——struct rb_root mm_rb

struct mm_struct {// ... 其他成员 ...struct vm_area_struct *mmap; // 指向虚拟内存区域列表的指针struct rb_root mm_rb;         // 红黑树,用于快速查找虚拟内存区域// ... 其他成员 ...
};
struct vm_area_struct {struct mm_struct *vm_mm;         // 指向所属进程的mm_structunsigned long vm_start;          // 虚拟内存区域的起始地址unsigned long vm_end;            // 虚拟内存区域的结束地址pgprot_t vm_page_prot;           // 页面保护标志unsigned long vm_flags;           // 标志位,如VM_READ, VM_WRITE等// ... 其他成员,如链表指针、红黑树节点等 ...
};

因为存在vm_area_struct这个数据结构,即使堆区频繁申请,但是每一段申请的空间都可以使用vm_area_struct来进行管理,最后用建表进行管理所有的vm_area_struct,在mm_struct中用*mmap作为链表的头指针。

进程地址空间管理(总结)

关于进程地址空间整体的管理结构如上图所示(虚拟区间较少情况下)。

task_struct管理整体进程,其中包括管理进程地址空间的mm_struct。在虚拟区间较少的情况下用在mm_struct中的vm_area_struct *mmap;指向由vm_area_struct链接而成的链表。每个vm_area_struct对应一段虚拟区间,而对于堆这种可能频繁申请的来说,堆这个区间会由多个vm_area_struct组成,而其他的区域使用一个vm_area_struct管理即可。

为什么要有进程地址空间(总结)

在早期的计算机中,程序直接操作物理内存,所有内存地址都是物理地址。当同时运行多个程序时,内存管理必须确保程序使用的内存总量小于物理内存。然而,这种直接操作物理内存的方式存在以下问题:


安全风险
  • 直接操作物理内存允许每个进程访问任意内存空间。
  • 恶意程序(如木马病毒)可以随意修改系统内存区域,导致设备瘫痪。

地址不确定性
  • 编译完成的程序存储在硬盘上,运行时需加载到内存。
  • 由于内存分配动态变化,程序的物理内存地址在不同运行时可能不同。
    • 例如:第一次运行时,程序加载到地址0x00000000
    • 第二次运行时,内存已被占用,加载地址可能变为其他位置。

效率低下
  • 使用物理内存时,进程以整体内存块操作。
  • 当物理内存不足时,将进程从内存拷贝到磁盘(交换分区)需要拷贝整个进程,耗时较长,效率低下。

虚拟地址空间与分页机制的优势

1. 内存安全
  • 地址空间和页表由操作系统创建和维护。
  • 所有地址空间和页表映射必须经过操作系统监管。
  • 保护物理内存中的合法数据,防止非法访问。
2. 地址灵活性
  • 地址空间和页表映射机制允许物理内存中的数据以任意位置加载。
  • 内存分配与进程管理解耦,进程不再直接依赖物理内存地址。
3. 延迟分配
  • 在C/C++中,通过newmalloc申请的空间实际上分配在虚拟地址空间。
  • 物理内存分配延迟到真正访问内存时执行。
  • 操作系统自动完成内存分配和页表映射,用户或进程无需感知。
4. 提高效率
  • 程序在物理内存中的加载可以是任意位置,通过页表实现虚拟地址与物理地址的映射。
  • 虚拟地址空间使得进程内存分布在逻辑上保持有序,简化了程序管理。

总结

虚拟地址空间通过操作系统的地址空间管理和页表机制,解决了直接操作物理内存带来的安全性、灵活性和效率问题,使得内存管理更安全、更高效,同时简化了程序开发与运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/67053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML5 Web Worker 的使用与实践

引言 在现代 Web 开发中&#xff0c;用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应&#xff0c;用户很可能会流失。HTML5 引入了 Web Worker&#xff0c;它允许我们在后台运行 JavaScript 代码&#xff0c;从而避免阻塞主线程&#xff0c;保…

Nginx配置中的常见错误:SSL参数解析

摘要 在高版本的Nginx中&#xff0c;用户可能会遇到unknown directive “ssl”的错误提示。这是因为旧版本中使用的ssl on参数已被弃用。正确的配置SSL加密的方法是在listen指令中添加ssl参数。这一改动简化了配置流程&#xff0c;提高了安全性。用户应更新配置文件以适应新版本…

适用于IntelliJ IDEA 2024.1.2部署Tomcat的完整方法,以及笔者踩的坑,避免高血压,保姆级教程

Tips:创建部署Tomcat直接跳转到四 一、软件准备 笔者用的是IntelliJ IDEA 2024.1.2和Tomcat 8.5。之前我使用的是Tomcat 10&#xff0c;但遇到了许多问题。其中一个主要问题是需要使用高于1.8版本的JDK&#xff0c;为此我下载了新的JDK版本&#xff0c;但这又引发了更多的兼容…

微信阅读网站小程序的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

从零开始学 HTML:构建网页的基本框架与技巧

系列文章目录 01-从零开始学 HTML&#xff1a;构建网页的基本框架与技巧 文章目录 系列文章目录前言一、HTML 文档的基本框架1.1 <!DOCTYPE html>、<html>、<head>、<body> 标签解析1.1.1 <!DOCTYPE html> 标签1.1.2 <html> 标签1.1.3 &l…

C#加密方式

using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Security.Cryptography;using System.Text;namespace PwdDemo{public class AESHelper{/// <summary>/// AES 加密/// </summary>/// <param name"str&qu…

【12】WLC配置internal DHCP服务器

1.概述 WLC无线控制器包含内部DHCP(internal DHCP)服务器。该功能通常用于尚未拥有DHCP服务器的分支机构中。 无线网络通常包含最多10个AP或更少的AP,并且AP在与控制器的同一IP子网上。内部DHCP服务器为无线客户端、直连AP和从AP中继的DHCP请求提供了DHCP地址。 2.内部DHC…

vue2中trhee.js加载模型展示天空盒子

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/13b9193d6738428791fc1ff112e03627.png 加载模型的时候需要把模型放在public文件下面 创建场景 this.scene new THREE.Scene()创建相机 this.camera new THREE.PerspectiveCamera(45,this.viewNode.clientWidth / t…

汽车免拆诊断案例 | 2007 款日产天籁车起步加速时偶尔抖动

故障现象  一辆2007款日产天籁车&#xff0c;搭载VQ23发动机&#xff08;气缸编号如图1所示&#xff0c;点火顺序为1-2-3-4-5-6&#xff09;&#xff0c;累计行驶里程约为21万km。车主反映&#xff0c;该车起步加速时偶尔抖动&#xff0c;且行驶中加速无力。 图1 VQ23发动机…

对神经网络基础的理解

目录 一、《python神经网络编程》 二、一些粗浅的认识 1&#xff09; 神经网络也是一种拟合 2&#xff09;神经网络不是真的大脑 3&#xff09;网络构建需要反复迭代 三、数字图像识别的实现思路 1&#xff09;建立一个神经网络类 2&#xff09;权重更新的具体实现 3&am…

新项目传到git步骤

1.首先创建远程仓库,创建一个空白项目,即可生成一个克隆URL,可以是http也可以是SSH,copy下这个地址 2.找到项目的本机目录,进入根目录,打开git bash here命令行 3.初始化: git init 4.关联远程地址: git remote add origin "远程仓库的URL" 5.查看关联 git re…

PAT甲级-1024 Palindromic Number

题目 题目大意 一个非回文数&#xff0c;加上它的翻转数所得的和&#xff0c;进行k次&#xff0c;有可能会得到一个回文数。给出一个数n&#xff0c;限制相加次数为k次&#xff0c;如果小于k次就得到回文数&#xff0c;那么输出该回文数和相加的次数&#xff1b;如果进行k次还…

appium自动化环境搭建

一、appium介绍 appium介绍 appium是一个开源工具、支持跨平台、用于自动化ios、安卓手机和windows桌面平台上面的原生、移动web和混合应用&#xff0c;支持多种编程语言(python&#xff0c;java&#xff0c;Ruby&#xff0c;Javascript、PHP等) 原生应用和混合应用&#xf…

C#高级:常用的扩展方法大全

1.String public static class StringExtensions {/// <summary>/// 字符串转List&#xff08;中逗 英逗分隔&#xff09;/// </summary>public static List<string> SplitCommaToList(this string data){if (string.IsNullOrEmpty(data)){return new List&…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.1 从零搭建NumPy环境:安装指南与初体验

1. 从零搭建NumPy环境&#xff1a;安装指南与初体验 NumPy核心能力图解&#xff08;架构图&#xff09; NumPy 是 Python 中用于科学计算的核心库&#xff0c;它提供了高效的多维数组对象以及用于处理这些数组的各种操作。NumPy 的核心能力可以概括为以下几个方面&#xff1a…

【SpringBoot教程】Spring Boot + MySQL + HikariCP 连接池整合教程

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 在前面一篇文章中毛毛张介绍了SpringBoot中数据源与数据库连接池相关概念&#xff0c;今天毛毛张要分享的是关于SpringBoot整合HicariCP连接池相关知识点以及底层源码…

Java进阶(一)

目录 一.Java注解 什么是注解&#xff1f; 内置注解 元注解 二.对象克隆 什么是对象克隆? 为什么用到对象克隆 三.浅克隆深克隆 一.Java注解 什么是注解&#xff1f; java中注解(Annotation)又称java标注&#xff0c;是一种特殊的注释。 可以添加在包&#xff0c;类&…

【PyCharm】将包含多个参数的 shell 脚本配置到执行文件来调试 Python 程序

要配置 PyCharm 以使用包含多个参数的 shell 脚本&#xff08;如 run.sh&#xff09;来调试 Python 程序&#xff0c;您可以按照以下步骤操作&#xff1a; 创建一个新的运行/调试配置&#xff1a; 在 PyCharm 中&#xff0c;点击“运行”菜单旁边的齿轮图标&#xff0c;选择“…

即梦(Dreamina)技术浅析(二):后端AI服务

1. 文本处理(Text Processing) 1.1 功能概述 文本处理模块的主要任务是将用户输入的文字提示词转换为机器可以理解的向量表示。这一过程包括分词、词嵌入和语义编码,旨在捕捉文本的语义信息,为后续的图像和视频生成提供准确的指导。 1.2 关键技术 1.分词(Tokenization…

蓝桥杯之c++入门(一)【第一个c++程序】

目录 前言一、第⼀个C程序1.1 基础程序1.2 main函数1.3 字符串1.4 头文件1.5 cin 和 cout 初识1.6 名字空间1.7 注释 二、四道简单习题&#xff08;点击跳转链接&#xff09;练习1&#xff1a;Hello,World!练习2&#xff1a;打印飞机练习3&#xff1a;第⼆个整数练习4&#xff…