Linux操作系统~什么是虚拟地址?深度剖析进程地址空间

article/2025/9/30 10:28:20

目录

1.所以进程的地址空间是什么呢?

2.mm_struct内部有什么?

3.虚拟地址空间与物理内存如何关联

页表

4.为什么设计这样一个进程地址空间,不让程序直接访问内存

Q:为什么子进程修改值以后,地址还是相同?

Q:常量字符串为什么地址相同


1.堆是堆,栈是栈,堆栈是栈

2.经过验证,C/C++的程序地址空间就是如图所示,栈区是往低地址方向增长的。

        子进程中对val进行修改,会发生写时拷贝。从而拷贝一份数据到另外的地方,变量的值是不一样的,但是他们两个变量对应的地址却是一样的,说明这个地址不是真正的物理地址,而是虚拟地址。

 1.所以进程的地址空间是什么呢?

A:实际上是一个结构体,里面存放了一个进程不同分区的虚拟地址空间。(不是真实的物理空间)

        每个进程都有一个地址空间,那么操作系统要管理,所以要先描述后管理,所以为每个进程定义一个mm_struct存放该进程的地址空间(里面有各个分区的起始和结束位置,总大小等)。

        因为这个进程地址空间是属于这个进程的,在进程的task_struct里面会存放指针指向这个mm_struct,通过task_struct可以找到mm_struct


2.mm_struct内部有什么?

        有代码段,常量区,堆区,栈区的起始位置和结束位置等信息。

        虽然这里只有start和end,但是每个进程都认为mm_struct代表整个内存,且所有的地址都是0x00 00 00 00 (1共1字节)到0xFF FF FF FF,这些地址也就是虚拟地址

        每个进程都认为地址空间的划分是按照4GB空间划分的(即每个进程都认为自己有4GB的空间)但是实际上物理内存可能只有1G

struct mm_struct
{unsigned int code_start;unsigned int code_end;unsigned int init_data_start;unsigned int init_data_end;unsigned int uninit_data_start;unsigned int uninit_data_end;unsigned int heap_start;unsigned int heap_end;//......
}

3.虚拟地址空间与物理内存如何关联

        mm_struct相当于描绘了一个4GB的虚拟地址空间,里面对应的地址叫做虚拟地址。(mm_struct内部并不是真的有这么大的地址空间,而是用起始位置和结束位置来表示的)

        那么物理地址和虚拟地址如何映射到一起的呢? ————使用页表+MMU(一种硬件,称为内存管理单元,用于查找页表,一般集成在CPU中)

页表

        页表是操作系统为每个进程维护的一张表,存放着虚拟地址和物理地址(就像hash表),用于根据虚拟地址去找到对应的物理地址。(将虚拟地址映射到对应的物理地址)


4.为什么设计这样一个进程地址空间,不让程序直接访问内存

原因一:操作系统可以对进程进行风险管理(权限管理),保证物理内存数据安全

原因一:如果进程直接访问物理内存的话,无法保证物理内存中的数据安全,通过添加一层软件层,操作系统可以完成有效的对进程操作内存进行风险管理(权限管理)(比如通过页表转地址,你有没有权限访问这个区域里面的内容,有没有越界访问等),本质目的是为了保护物理内存以及各个进程的数据安全!

        你虚拟地址转换成物理地址的时候,是交给操作系统来转的,它就可以决定帮不帮你转,转完以后你有没有权限可以访问。

const char* str = "zebra"

*str = "hello";

e.g:像这个地方,我们无法对常量区的内容进行修改,实际上是因为OS给你的权限只有r权限,这个和页表也有关系,你在使用str指针的时候,实际上也是使用了虚拟地址,在通过页表转换为物理地址的时候操作系统就会发现你没有权限对常量区的内容进行修改,所以就拒绝你的这次修改。

原因二:屏蔽内存申请内存的过程,将普通进程读写内存和OS对内存进行管理在软件层面上进行解耦(申请的时候只是划分虚拟地址空间,访问内存时才真正开辟物理内存,你申请了空间不使用的话,操作系统可以把空间给其他人用,等你真的要使用了才为你开辟内存)

        在OS角度,如果空间立马给你,意味着整个系统会有一部分空间,本来可以给别人立马用的,现在却可能被你闲置着,造成了空间浪费。

        一开始进程申请了空间的时候,操作系统并不会马上将物理空间分配给进程,而是给进程划分了一片虚拟内存空间(比如在mm_struct中将heap_end+100),当进程真正要读取这段空间的时候,操作系统说等等(缺页中断),然后为进程开辟一片物理空间给进程使用(基于缺页中断进行物理内存申请)。

原因二:将内存申请和内存使用的概念在时间上划分清楚(申请的时候申请,使用的时候使用,等你使用了我才真的为你开辟内存,类似写时拷贝),通过虚拟地址空间,来屏蔽内存申请内存的过程将普通进程进程读写内存和OS进行内存管理操作,进行软件层面上的分离(解耦)

e.g.:比如现在内存已经满了,你还要内存,我操作系统仍然可以给你(虚拟),然后执行对应的内存管理算法释放一些内存,等到你要用的时候,我把空间给你

原因三:不同进程中每个区域的相对位置比较确定,CPU可以以统一的视角看待内存;程序的代码数据可以加载到物理内存的任意位置

如果没有进程的地址空间,CPU没法以统一的方式去寻找每个进程的起始位置。

e.g:比如CPU执行不同进程,每个进程一个main函数的话,CPU每次开始执行进程的时候只需要从虚拟地址0x1234开始执行,根据不同进程的页表可以找到对应进程main函数的物理地址(每个进程的页表中,虚拟地址都是0x1234),这样CPU执行多个进程的时候就很方便。

原因三:有了地址空间以后,站在CPU和应用层的角度,进程统一可以看做统一使用4GB空间,(1).而且不同进程中每个空间区域的相对位置(比如堆栈大概在哪个位置),是比较确定的,CPU可以以统一的视角看待内存,执行进程/读取数据的时候会方便许多。(CPU执行一个进程的时候,可以知道堆,栈,常量区大概在地址空间的哪个位置,因为每个进程的地址空间都是一致的)

对操作系统来说,(2).程序的代码和数据可以被加载到物理内存的任意位置,只需要通过进程的页表将其位置和对应的地址空间映射起来就行,大大减少了内存管理的负担


Q:为什么子进程修改值以后,地址还是相同?

因为这个地址是虚拟地址,子进程和父进程中这个变量的物理地址是不相同的。

        子进程的创建是以父进程为模板的,所以子进程的进程地址空间和页表也是继承的父进程的,所以g_val的虚拟地址都是一样的。

        子进程修改了g_val的值以后,发生了写时拷贝,为子进程重新开辟了一块物理空间,将父进程g_val的值拷贝进入这个物理空间,然后修改子进程页表中原来g_val的虚拟地址和物理地址的映射关系,将物理地址改为新开辟内存的地址。但是此时虚拟地址没有发生变化,从而也就发生了这种情况。

        代码共享一份的实现也很简单,只要将父进程和子进程页表中存放代码的虚拟地址映射到同一块物理空间即可。


Q:常量字符串为什么地址相同

char* str = "hello world";

char* p = "hello world";

  • hello这个字符串常量字符串,是只读的,所以操作系统只会保存一份,存放在常量区
  • 字符串常量是一个表达式,表达式的值就是这个字符串常量第一个字母对应的地址,这是一个虚拟地址,然后把这个地址给p和str
  • 所以最后p和str的值打印出来都是相同的,表示的都是string这个字符串的虚拟地址
  • 虽然p和str两个指针变量的值是相同的,但是他们的虚拟地址是不相同的

 


http://chatgpt.dhexx.cn/article/c7OjAEnd.shtml

相关文章

初识虚拟地址空间

物理地址和虚拟地址 物理寻址:CPU访问存储器的最原始方法就是直接用物理地址(Physical Address, 可简称PA)。物理地址是唯一的。 虚拟寻址:CPU通过生成一个虚拟的地址来访问内存,在访问前会把虚拟地址转化为物理地址…

虚拟地址空间,虚拟文件系统

1、虚拟地址空间 1、概念与原因 虚拟地址空间是一个抽象的概念,在IBM中,这样说道:它存在,但你看不见,就是虚拟的。虚拟地址空间就是这样一个东西。(注意区分虚拟内存与虚拟地址空间) 虚拟地址空…

彻底搞懂虚拟内存,虚拟地址,虚拟地址空间

程序经过编译后,变成了可执行的文件,可执行文件主要包括代码和数据两部分,代码是只读的,数据则是可读可写的。 可执行文件由操作系统加载到内存中,交由CPU去执行,现在问题来了,CPU怎么去访问代…

使用POI导出Excel(并使用公式)

使用POI导出Excel(并使用公式) 使用java直接生成Excel并填充数据 可以参考POI官方文档,就是对Sheet,row,cell,Formula等操作, https://poi.apache.org/components/index.html 这种方式也可以生成…

poi导出excel日期格式问题

POI导出Excel的时候有时需要日期格式,在筛选时是这样的 private XSSFWorkbook wb null; String dateFormat "yyyy-MM-dd";//或者"yyyy/MM/dd"格式,"yyyy/M/d"这样的格式不会自动补0 public void setCell(int index,Date value,bool…

POI导出Excel详细教程

文章目录 前言一、引入jar包依赖二、创建自定义导出Excel样式类三、创建核心导出工具类四、创建导出对象实体Bean五、具体使用案例5.1.创建SQL脚本和初始化数据5.2.写一个查询所有学生信息接口5.3.查询学生基本信息返回数据格式5.4.导出Excel方法5.5.通过页面导出按钮导出Excel…

Java使用POI导出Excel文件

Java使用POI导出Excel文件 POI概述Apache POI 下载依赖引用关系图如下所示:直接下载Maven下载 POI实例总结 POI概述 HSSF 是 POI 项目的 Excel 97(-2007) 文件格式的纯 Java 实现。XSSF 是 POI 项目的 Excel 2007 OOXML (.xlsx) 文件格式的纯 Java 实现。 HSSF 和 XSSF 提供了读…

Java用POI导出Excel表格中的数据

poi操作Excel 主要通过HSSF,XSSF两种方式。 HSSF只能解析.xls格式的excel文件,XSSF支持.xls与.xlsx两种格式。 功能: 传递一个Excel文件,拿到里面所有的数据,返回一个集合。 Excel中的数据是什么类型,就返回什么类型的…

springboot+poi导出excel

在web开发中经常遇到将数据写入excel并导出的需求&#xff0c;下面整理springbootpoi实现导出excel的实例。 搭建springboot工程&#xff0c;引入依赖&#xff0c;细节不在赘述。 引入poi依赖 <dependency><groupId>org.apache.poi</groupId><artifactId…

POI实现导入导出excel

poi在日常的导入导出中是比较常用到的&#xff0c;最近也总结了下接触到的poi相关的导入导出的一些代码&#xff0c;有问题可以指出&#xff1a; package com.poi;import km.org.apache.poi.hssf.usermodel.*; import km.org.apache.poi.hssf.util.HSSFColor; import km.org.a…

java使用Poi导出excel表格

随时随地阅读更多技术实战干货&#xff0c;获取项目源码、学习资料&#xff0c;请关注源代码社区公众号(ydmsq666) 在之前的一篇文章java操作Excel实战干货中展示了使用poi库读取excel表格的的用法&#xff0c;今天演示另一个常用功能&#xff0c;将数据导出到excel中&#xff…

POI导出Excel文件中文乱码

使用POI组件生产Excel文件时中文乱码&#xff0c;总结后可能错误原因如下&#xff1a; 后台导出Excel文件格式混乱 POI组件可生成.xls和.xlsx两种格式的Excel文件&#xff0c;设置文件格式时应注意与导出的格式相匹配。如果文件格式设置出错&#xff0c;则会出现以下错误&…

Java使用poi导出Excel之格式设置

最近接到一个需求&#xff0c;客户不满意原本导出的csv文件&#xff0c;想要导出Excel文件。不就导出Excel文件嘛&#xff0c;小意思&#xff0c;于是乎信心满满从网上扒导出的代码&#xff0c;一顿CV大法&#xff0c;搞定&#xff01;代码如下: import lombok.extern.slf4j.S…

Java使用POI导出Excel

目录 一、前景二、概念2.1. 简介2.2.Excel版本和相关对象2.3.WorkBook2.4.POI依赖 三、POI - 写3.1.代码示例3.2. 性能对比3.3. 测试rowAccessWindowSize3.4. 导出Excel样式设置 四、POI - 读4.1.代码示例4.2.读取不同的数据类型4.3.读取公式 五、POI - 遇到的坑5.1.为什么模板…

Java实现八大排序算法

原文链接&#xff1a; 八大排序算法总结与java实现 - iTimeTraveler 概述 因为健忘&#xff0c;加上对各种排序算法理解不深刻&#xff0c;过段时间面对排序就蒙了。所以决定对我们常见的这几种排序算法进行统一总结&#xff0c;强行学习。首先罗列一下常见的十大排序算法&…

通过java实现八大排序的功能

八大排序(java实现) 常见的排序算法如下&#xff1a; 直接插入排序希尔排序简单选择排序堆排序冒泡排序快速排序归并排序基数排序 它们都属于内部排序&#xff0c;也就是只考虑数据量较小仅需要使用内存的排序算法&#xff0c;他们之间关系如下&#xff1a; 稳定与非稳定 …

c语言基数为3变为基数为10,必须知道的C语言八大排序算法(收藏)

概述 排序有内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 我们这里说说八大排序就是内部排序。 当n较大&#xff0c;则应采用时…

数据结构( 排序)

排序 1、排序的基本概念2、插入排序①.直接插入排序②.折半插入排序③.希尔排序 3、交换排序①.冒泡排序②.快速排序 4、选择排序①.简单选择排序②.树形选择排序③.堆排序 5、归并排序6、基数排序7、总结8、例题与应用 1、排序的基本概念 排序是计算机内经常进行的一种操作&a…

十大经典排序算法python版本_【程序员面试必备】动画详解十大经典排序算法(C语言版)...

欢迎访问我的博客原文 排序算法是程序员必备的基础知识&#xff0c;弄明白它们的原理和实现很有必要。本文中将通过非常细节的动画展示出算法的原理&#xff0c;配合代码更容易理解。 概述 由于待排序的元素数量不同&#xff0c;使得排序过程中涉及的存储器不同&#xff0c;可将…

八大排序算法详解(Java语言实现)

概述 因为健忘&#xff0c;加上对各种排序算法理解不深刻&#xff0c;过段时间面对排序就蒙了。所以决定对我们常见的这几种排序算法进行统一总结&#xff0c;强行学习。首先罗列一下常见的十大排序算法&#xff1a; 直接插入排序希尔排序简单选择排序堆排序冒泡排序快速排序归…