【Linux】进程地址空间的初步理解

news2024/12/26 13:10:41

目录

  • 程序地址空间空间布局图
  • 引入物理地址与虚拟地址的概念
  • 虚拟地址空间
  • 虚拟地址与物理地址是如何对应的?
  • ※父子进程独立性的理解(重点)
  • fork两个返回值的原理
  • 地址空间为什么要存在?
  • 补充理解

程序地址空间空间布局图

在这里插入图片描述
(这些划分都是在虚拟地址空间的划分)

引入物理地址与虚拟地址的概念

首先编写一个测试程序进行测试:

#include <stdio.h>
#include <assert.h>
#include <unistd.h>

int g_value = 100;

int main()
{
    pid_t id = fork();
    assert(id >= 0);
    if(id == 0)
    {
    	//子进程
    	while(1)
		{
	    	printf("这是子进程,ID=%d, 父进程ID=%d, g_value = %d, &g_value = %p\n", getpid(), getppid(), g_value, &g_value);
 	    	sleep(1);
	    	g_value++;
		}
    }
    else
    {
    	//父进程
    	while(1)
		{
	    	printf("这是父进程,ID=%d, 父进程ID=%d, g_value = %d, &g_value = %p\n", getpid(), getppid(), g_value, &g_value);
 	    	sleep(1);
		}
    }
}

运行结果:
在这里插入图片描述
问题解析:
为什么子进程对变量进行修改的时候父进程中的变量不会变化?
—— 进程具有独立性! g_value是一个全局变量,当子进程对全局数据进行修改的时候,不会影响父进程。
进程 == 内核数据结构 + 代码和数据,进程具有独立性,就说明内核数据结构、代码和数据都要各自保持独立性。

那么数据如何保持独立性呢?
——写时拷贝

同时发现父子进程g_value的地址都相同,但是打印时出现的值是不相同的,按理说应该存在两个不同的地址,为什么二者的地址相同呢?
——假设这个地址是“物理地址”,那就不可能读取同一个地址取到不同的值。所以这个地址不可能是物理地址!即我们平时在语言层面所使用的地址,就不是物理地址

那么这个地址是什么地址?
——虚拟地址/线性地址

虚拟地址空间

进程地址空间也需要被OS所管理,其本质就是一个内核数据结构struct mm_struct{},通过它来描述地址空间。
当我们创建一个进程时,会在内核定义一个task_struct对象,定义一个mm_struct对象(也在内核中),task_struct中存在一个指针指向进程对应的地址空间。
而这些虚拟的地址最终都要存放在物理内存当中。


那么我们要如何理解mm_struct中所划分的堆区、栈区、代码段…呢?
——地址空间本质是一个线性结构,其宽度为1字节(一个int形4字节占用4个地址),其编址为0x000…0~0xFFF…F,每个地址对应一个字节,地址是连续的。
所以在mm_struct中通过各自的start与end来划分各个区域。这些划分出来区域也就是虚拟地址。
所以堆区的扩大和栈区向下调整本质就是改变了对应的start与end对应的数据。(本质比较复杂,理解原理)
在这里插入图片描述


虚拟地址与物理地址是如何对应的?

我们平时直接使用的地址都是虚拟地址,那么虚拟地址是如何找到物理地址中存储的数据的呢?
——通过页表,页表可以将虚拟地址转换为物理地址。可以将页表理解为KV映射,左侧是虚拟地址,右侧是物理地址,当我们使用虚拟地址时,会通过CPU将虚拟地址转换成物理地址,进而读取物理地址中的数据(在CPU内部有一个集成硬件MMU内存管理单元)。
在这里插入图片描述

※父子进程独立性的理解(重点)

回到开头所写的测试代码问题,为什么在子进程中修改全局数据不会影响父进程中读取的数据?二者的地址明明是一样的。(虚拟地址)
解析:
创建父进程时,在内核中维护父进程task_struck与父进程虚拟地址空间mm_struct,虚拟地址通过页表映射到物理地址中存储g_value = 100。
接下来fork创建了子进程,创建子进程的时候就要以父进程的pcb和mm_struct为模版,创建子进程的pcb和mm_struct地址空间并维护子进程对应的页表结构。
即子进程内核数据结构中的属性字段绝大部分会继承自父进程。(pcb与mm_struct)
所以二者打印的数据都是相同的,数据地址也是相同的,如下图所示:
在这里插入图片描述
而当子进程对数据进行修改的时候,如果在原物理地址进行修改,那么父进程读取的数据也会发生变化。
但是事实是子进程对数据的修改不能影响父进程。
所以当子进程想要修改数据的时候,OS会在内存中为其重新申请一块空间,并将原来空间的数据拷贝到新的空间,然后OS会重新构建子进程页表的映射关系,指向新的空间。所以当子进程修改数据的时候,不会影响父进程。
又因为修改的时候是在物理内存中申请空间,修改的是页表的映射关系,原来的虚拟地址不变,所以才有了之前观察到的地址相同而存储内容不同的现象。
如下图所示:
在这里插入图片描述

这就是进程独立性的一种表现方式:
PCB-地址空间-页表互相解耦,不会互相影响。

fork两个返回值的原理

fork函数在返回的时候,因为父子进程一定都已经创建完成了,所以其return语句会执行两次,而返回的本质就是写入,谁先返回,谁就让操作系统发生写时拷贝。

地址空间为什么要存在?

如果没有地址空间,OS如何工作?
当需要执行进程的时候,直接从磁盘读取代码数据到物理内存,然后CPU通过PCB直接找到物理内存中的代码进行运行,看似没有什么问题。
在这里插入图片描述
但是如果一个程序中有寻址的操作,并且这个程序写的有问题,发生了越界操作(野指针),访问到了下一个进程的物理内存区域,这时候如果是写入操作,就可能会导致下一个进程出现问题。这样就无法保证进程的独立性了。如下图所示。
在这里插入图片描述
所以引入了页表和虚拟地址空间,访问物理空间就不再是直接访问而是要通过虚拟地址与页表的映射才能访问,但是只是添加一层映射并不能解决问题。
真正解决问题的原理是:添加的映射还可以决定程序能否成功去访问对应的物理空间,通过对应的映射只能访问自己进程所属的物理空间,会通过OS来检测访问是否合法,不合法会进行拦截(比如野指针),只会使自己崩溃,不会影响别的进程。
所以虚拟地址空间与页表其实就是添加了一层软件层,来保证进程的独立性。

总结: 虚拟地址空间存在的意义:

  1. 防止地址随意访问,保护了物理内存与其他进程。
  2. 将进程管理和内存管理解耦合。
  3. 可以让进程以统一的视角看待自己的代码和数据。(接收的都是虚拟地址)

在这里插入图片描述

补充理解

1.为什么常量区的数据不能被修改?
——因为虚拟地址通过页表映射时,发现访问的数据在常量区,页表中给的权限都是r权限,只能读不能写。

2.malloc的本质,相OS申请空间的时候,是直接给你还是你需要的时候才给你呢?
——在需要的时候才给。
OS不允许任何浪费与不高效行为。当我们malloc在堆区申请空间的时候,不一定是申请了立马就用的,那么在你申请好但是没有使用的期间,这部分空间就处于闲置状态,如果内存中的进程多的话,就会出现低效的情况。
所以当我们malloc在堆区申请空间的时候,OS只会在虚拟地址空间中给我们申请空间(堆区扩大,向上增长,修改end指针即可),页表将虚拟地址填入K,而物理地址V侧暂时先不填任何东西,同时也不在物理地址申请空间,也就不用维护任何映射关系,直接返回虚拟地址空间的地址,当进程对这块空间进行写入时,OS再进行映射申请实际的物理空间。
这种行为称之为缺页中断

3.重新理解地址空间
程序编译的时候,没有被加载到内存,这时候程序中有没有地址呢?
——有。
程序在磁盘上形成可执行程序的时候就已经有了一定的格式,比如代码段、已初始化全局数据区、未初始化全局数据区等。当它被加载到内存中的时候,分批式的将自己的数据段加载到地址空间中。
所以其实源代码在被编译的时候,就是已经按照虚拟地址空间的格式对代码和数据进行了编址。
虚拟地址这种策略不仅仅只会影响OS,编译器也遵守虚拟地址的规则!(这也是为什么反汇编中可以查看虚拟地址)
——所以在CPU中读到的数据中涵盖的地址,是虚拟地址,不是物理地址。(大致原理如下图所示)
在这里插入图片描述

4.进程的代码和数据必须一直在内存中吗?
——不一定,用多少加载多少,边加载边执行,这也是通过虚拟地址空间实现的。

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

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

相关文章

IDEA 新版安装教程

目录 一、安装IDEA 1、双击安装&#xff0c;然后下一步 2、修改默认安装路径&#xff0c;自定义目录。(建议所有开发工具都放在同一个盘符) 3、改为自定义安装路径&#xff0c;下一步。&#xff08;不用使用中文或空格&#xff09; 4、创建桌面图标等 5、点击安装&#x…

02.DolphinScheduler数据源中心

文章目录 MySQLHIVE数据源使用HiveServer2使用 HiveServer2 HA Zookeeper Clickhouse MySQL 填写参数 数据源&#xff1a;选择 MYSQL数据源名称&#xff1a;输入数据源的名称描述&#xff1a;输入数据源的描述IP 主机名&#xff1a;输入连接 MySQL 的 IP端口&#xff1a;输入…

一维卷积与一维平均池化的时间复杂度

计算请参考这篇文章&#xff1a; (284条消息) 卷积神经网络的时间、空间复杂度以及数据流的变化_卷积的时间复杂度_Briwisdom的博客-CSDN博客 1. 时间复杂度 时间复杂度即模型的运行次数。 单个卷积层的时间复杂度&#xff1a;Time~O(M^2 * K^2 * Cin * Cout) //有的好奇小宝…

Spring(11. 循环依赖 - 周阳)学习笔记

上一篇 &#xff1a;10. 面试问题简析 文章目录 1. Spring AOP1.1. Aop 常用注解1.2 测试前的准备工作1.2.1 业务类1.2.2 切面类 1.3 Spring4 下的测试1.3.1 POM 文件1.3.2 创建测试类1.3.3 Aop 测试结果 1.4 Spring 5 下的测试1.4.1 POM 文件1.4.2 创建测试类1.4.3 Aop 测试结…

e签宝,「进化」在2023

精准布局生态化、统一化、智能化、信创化&#xff0c;辅以具体产品落地&#xff1b;加速产业、行业、企业、业务&#xff0c;“四业”互通互联&#xff0c;提高产业数字化渗透率。电子签“群战”时代&#xff0c;e签宝再次进化。 作者|斗斗 出品|产业家 “印章在谁手上&…

Camtasia 2023版强悍来袭,会哪些新功能呢?

Camtasia Studio 是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录 屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等&#xff0c;另外&#xff0c;它还具有即时播放和编 辑压缩的功能&#xff0c;可对视频片段进行剪接、添加转场效果…

享受简单上传体验:将Maven仓库迁移到GitHub

前言&#xff1a;我为什么放弃了Maven Central 之前我写过一篇《Android手把手&#xff0c;发布开源组件至 MavenCentral仓库》&#xff0c;文中详细介绍了如何发布组件到Maven Central中供所有开发者共用。但是最近使用下来&#xff0c;发现Sonatype JIRA 的Maven Center上传…

python接口自动化测试 requests库的基础使用

目录 简单介绍 Get请求 Post请求 其他类型请求 自定义headers和cookies SSL 证书验证 响应内容 获取header 获取cookies 简单介绍 requests库简单易用的HTTP库 Get请求 格式&#xff1a; requests.get(url) 注意&#xff1a;若需要传请求参数&#xff0c;可直接在 …

c++STL标准库排序函数std::sort使用

Qt系列文章目录 文章目录 Qt系列文章目录前言一、错误原因二、修改后的代码 前言 C sort()排序函数 C STL 标准库中的 sort() 函数&#xff0c;本质就是一个模板函数。正如表 1 中描述的&#xff0c;该函数专门用来对容器或普通数组中指定范围内的元素进行排序&#xff0c;排序…

【ML】windows 安装使用pytorch

使用pytorch需要python环境&#xff0c;建议是直接装anaconda &#xff0c;IDE用visual studio anaconda安装 Anaconda 是一个用于科学计算的 Python 发行版&#xff0c;支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包 官网链接anaconda 本人下载…

(转)maven安装及配置(详细版)

1.下载&#xff1a; 方式一可以从官方下载&#xff0c;下载页面&#xff1a;http://maven.apache.org/download.cgi 方式二&#xff1a;或者题主提供的版本下载maven安装包 提取码&#xff1a;ysns 下载好后是一个压缩文件 2.安装&#xff1a; maven压缩包解压到一个没有中文&a…

AI 编程

GitHub Copilot&#xff08;收费&#xff09; 开发者&#xff1a;微软 openAI 2022年8月22日之后开始收费&#xff0c;10美元/月&#xff0c;100美元/年。 CodeGeeX&#xff08;免费&#xff09; CodeGeeX 可以根据自然语言注释描述&#xff08;支持中英文注释&#xff09…

20.$refs

$refs是vue操作DOM用的&#xff0c;每一个vue组件实例上&#xff0c;都包含一个$refs对象&#xff0c;里面存储对应的DOM元素或组件的引用&#xff0c;默认情况下$refs对象为空 目录 1 $refs在哪 2 使用ref操作DOM 3 使用ref操作组件 3.1 使用组件方法 3.2 操作组件…

13 JS04——运算符

目标&#xff1a; 1、运算符 2、算数运算符 3、递增和递减运算符 4、比较运算符 5、逻辑运算符 6、赋值运算符 7、运算符优先级 一、运算符 1、概念 运算符&#xff08;operator&#xff09;也被称作操作符&#xff0c;是用于实现赋值、比较和执行算数运算等功能的符号。 2…

解决java普通项目读取不到resouces目录下资源文件的办法

现象如下&#xff1a; 可以看到resources目录已经在idea中标记成了资源目录resources root&#xff0c;而且target/classes目录下也编译出了resources目录下的pci.properties文件&#xff0c;换句话说&#xff1a;java在编译时是读取到了resources下的文件的。 可是为什么new F…

App性能优化方案——布局层级太多怎么优化?

作者&#xff1a;小海编码日记 View整体布局是通过深度优先的方式来进行组织的&#xff0c;整体形似一颗树&#xff0c;所以优化布局层级主要通过三个方向来实施&#xff1a; 降低布局深度&#xff1a;使用merge标签或者布局层级优化等手段来减少View树的深度&#xff1b;布局…

代码随想录算法训练营第四十三天|1049. 最后一块石头的重量 II 、494. 目标和、474.一和零

文章目录 背包问题题型1049. 最后一块石头的重量 II494. 目标和474.一和零 背包问题题型 等和子集 —0-1背包能否装满最后一块石头—0-1背包尽量装满目标和—0-1背包装满&#xff0c;且有多少种装的方式&#xff08;组合问题&#xff09; 1049. 最后一块石头的重量 II 题目链…

网页爬虫之WebPack模块化解密(JS逆向)

WebPack打包: webpack是一个基于模块化的打包&#xff08;构建&#xff09;工具, 它把一切都视作模块。 概念&#xff1a; webpack是 JavaScript 应用程序的模块打包器,可以把开发中的所有资源&#xff08;图片、js文件、css文件等&#xff09;都看成模块&#xff0c;通过loade…

Java中Lambda表达式(面向初学者)

目录 一、Lambda表达式是什么&#xff1f;什么场景下使用Lambda&#xff1f; 1.Lambda 表达式是什么 2.函数式接口是什么 第二章、怎么用Lambda 1.必须有一个函数式接口 2.省略规则 3.Lambda经常用来和匿名内部类比较 第三章、具体使用场景举例&#xff08;&#xff09; …

水果店(库)管理系统 —— 实现了管理员模式与顾客模式 JAVA

水果店&#xff08;库&#xff09;管理系统 1.前言&#xff1a;2.功能简介及部分测试视频&#xff1a;3.本管理系统的构建原理&#xff08;简介)&#xff1a;(1).如何跳转页面&#xff1a;(2).如何让控制台能输出彩色字体&#xff1a;(3).如何稳定存储数据&#xff1a;(4).如何…