『 Linux 』进程地址空间概念

文章目录

    • 🫙 前言
    • 🫙 进程地址空间是什么
    • 🫙 写时拷贝
    • 🫙 可执行程序中的虚拟地址
    • 🫙 物理地址分布方式


🫙 前言

请添加图片描述

在c/C++中存在一种内存的概念;

一般来说一个内存的空间分布包括栈区,堆区,代码段等等;

且内存是自底向上(由0x000000000xFFFFFFFF);
以该图为例:

在这里插入图片描述

该图即为常见的内存分布图;

  • 正文代码段

    正文代码段所存放的数据一般为函数体的二进制代码;

  • 已初始化数据区

    已初始化数据区所存放的数据是在程序中声明的,并且具有初始值的变量,这些变量需要占用存储器的空间;

  • 未初始化数据区

    未初始化数据区所存放的数据是没有进行初始化或者初始值为0的数据,这些数据在存储时不需要额外占用存储器的空间;

  • 堆空间一般为动态空间,即需要成需要手动分配释放;若是分配了堆区空间但使用过后未对堆空间进行手动释放则将会出现内存泄漏的问题;

  • 一般情况下栈所存放的数据基本上都为局部变量;

  • 命令行参数/环境变量

    命令行参数/环境变量,顾名思义该段空间用来存放OS给程序所传递的命令行参数与环境变量;

  • 内核空间

    在Linux操作系统当中,内存的分布一般为其中3G为用户空间,1G为内核空间;

以下操作均在CentOS7_x64环境下进行

存在一个程序 ( mytest ) :

  int init = 10; int uninit; int main(int argc,char *argv[],char *env[])
{char*ch1= new char[10]; char*ch2= new char[10];char*ch3= new char[10];char*ch4= new char[10];char*ch5= new char[10];printf("init : %p\n",&init);//已初始化数据printf("uninit : %p\n",&uninit);//未初始化数据printf("text : %p\n",main);//正文代码段cout<<"--------------"<<endl;//堆区printf("heap1 : %p\n",ch1);printf("heap2 : %p\n",ch2);printf("heap3 : %p\n",ch3);printf("heap4 : %p\n",ch4);printf("heap5 : %p\n",ch5);cout<<"--------------"<<endl;//栈区printf("stack1 : %p\n",&ch1);printf("stack2 : %p\n",&ch2);printf("stack3 : %p\n",&ch3); printf("stack4 : %p\n",&ch4);printf("stack5 : %p\n",&ch5);cout<<"--------------"<<endl;//命令行参数for(int i = 0;i<argc;++i){printf("argv[%d] : %p\n",i,argv[i]);}cout<<"--------------"<<endl;//环境变量for(int i = 0;env[i];++i){printf("env[%d] : %p\n",i,env[i]);}return 0;
}

从这段代码中可以打印出内存中不同数据的内存分布情况;

但实际上在OS层面中,这些所谓的内存并非物理内存;


🫙 进程地址空间是什么

请添加图片描述
在上文中说到,进程所访问的地址并不是物理地址;

存在一个程序(证明):

using namespace std;int tmp = 100;int main()
{pid_t id = fork();if(id == 0){int s = 5;while(1){cout<<"pid : "<<getpid()<<" ppid : "<<getppid()<<" tmp : "<<tmp<<" &tmp : "<<&tmp << endl;sleep(1);s--;if(!s) tmp = 200;}}else{while(1){cout<<"pid : "<<getpid()<<" ppid : "<<getppid()<<" tmp : "<<tmp<<" &tmp : "<<&tmp << endl;sleep(1);}}return 0;
}

在该程序中定义了一个全局变量,并使用fork()函数对该进程创建了一个子进程,同时分别在父子进程中打印该全局变量的值与地址;

pid : 28930 ppid : 28929 tmp : 100 &tmp : 0x60108c
pid : 28929 ppid : 28812 tmp : 100 &tmp : 0x60108c
pid : 28930 ppid : 28929 tmp : 200 &tmp : 0x60108c
pid : 28929 ppid : 28812 tmp : 100 &tmp : 0x60108c

当五秒过后,子进程修改了全局变量的值;

可在父进程当中的这个全局变量并未被更改,且父子进程中所显示的这个全局变量tmp地址相同;

然而实际上,一个程序在运行的过程中所使用的内存地址为虚拟地址(线性地址);

在过去的计算机中,进程对于内存的访问是以直接访问的形式,即运行程序时程序载入至内存当中称为进程,CPU根据进程中的代码数据对内存的各个地址(物理地址)进行操作;

在这里插入图片描述

但是由于访问的是物理内存地址,所以若是程序在内存当中误操作则会导致某些进程的崩溃;

这种操作是十分不安全的操作;

所以为了保证安全性同时也保证进程间的独立性,现在的OS当中,出现了进程地址空间的概念;

在这里插入图片描述

每个进程都存在一个称为进程地址空间的数据结构(mm_struct结构体);

在这个结构体当中以一种类似于区间的方式模拟出地址(在Linux2.6的版本中使用unsigned long类型实现);

/*释放线性区的调用方法*/void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
#endifunsigned long mmap_base;		/* base of mmap area ,内存映射区的基地址*/unsigned long task_size;		/* size of task vm space */unsigned long cached_hole_size; 	/* if non-zero, the largest hole below free_area_cache */unsigned long free_area_cache;		/* first hole of size cached_hole_size or larger */pgd_t * pgd;                            /* 页表目录指针*/atomic_t mm_users;			/* How many users with user space?,共享进程的个数 */atomic_t mm_count;			/* How many references to "struct mm_struct" (users count as 1),主使用计数器,采用引用计数,描述有多少指针指向当前的mm_struct */int map_count;				/* number of VMAs ,线性区个数*/struct rw_semaphore mmap_sem;spinlock_t page_table_lock;		/* Protects page tables and some counters,保护页表和引用计数的锁 (使用的自旋锁)*/struct list_head mmlist;		/* List of maybe swapped mm's.	These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long hiwater_rss;	/* High-watermark of RSS usage,进程拥有的最大页表数目 */unsigned long hiwater_vm;	/* High-water virtual memory usage ,进程线性区的最大页表数目*/unsigned long total_vm, locked_vm, shared_vm, exec_vm;unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long start_code, end_code, start_data, end_data;     /*维护代码区和数据区的字段*/unsigned long start_brk, brk, start_stack;       /*维护堆区和栈区的字段*/unsigned long arg_start, arg_end, env_start, env_end;  /*命令行参数的起始地址和尾地址,环境变量的起始地址和尾地址*/unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

除此之外在进程地址空间这个结构体中有一个指针,这个指针所指向的位置即为页表;

所谓的页表就是一种映射关系,这种映射关系以一种key/value的模型将对应的物理地址与虚拟地址进行一种存储,在查找或访问时将访问至虚拟地址,通过该虚拟地址通过页表的key/value模型找到其对应的物理内存再进行访问;

在CPU中存在一个内存管理单元(MMU),这个内存管理单元是CPU中的一个模块,这个模块具体的作用为负责虚拟地址到物理地址的转换;

在这里插入图片描述

以该图为例,其中task_struct表示PCB结构体,即进程控制块;

mm_struct即为该进程的进程地址空间,mm_struct中的pgd即为页表;


🫙 写时拷贝

请添加图片描述

当多个进程或线程共享同一块内存时,内核会使用写时拷贝来优化内存的复制行为;

当有一个进程尝试修改共享内存页面时,Linux内核会触发写时拷贝机制;

它会为修改的进程创建一个新的私有副本,并将修改的内容写入新的副本中,而不是立即修改原始的共享页面;

以该例子为例:

using namespace std;int tmp = 100;int main()
{pid_t id = fork();if(id == 0){int s = 5;while(1){cout<<"pid : "<<getpid()<<" ppid : "<<getppid()<<" tmp : "<<tmp<<" &tmp : "<<&tmp << endl;sleep(1);s--;if(!s) tmp = 200;}}else{while(1){cout<<"pid : "<<getpid()<<" ppid : "<<getppid()<<" tmp : "<<tmp<<" &tmp : "<<&tmp << endl;sleep(1);}}return 0;
}

在该例子中程序运行的结果为:

pid : 28930 ppid : 28929 tmp : 100 &tmp : 0x60108c
pid : 28929 ppid : 28812 tmp : 100 &tmp : 0x60108c
pid : 28930 ppid : 28929 tmp : 200 &tmp : 0x60108c
pid : 28929 ppid : 28812 tmp : 100 &tmp : 0x60108c

两个进程中的变量的地址相同但其值不同的原因就是在于其所在的虚拟地址相同但页表中虚拟地址所映射的物理地址不同;

在这个程序当中,使用fork()函数创建了子进程,由于子进程是由父进程创建的,所以对应的子进程的PCB结构体继承于父进程,即当父进程创建出一个子进程时,该子进程将会对父进程的PCB结构体进行一次浅拷贝,所以父子进程所对应的代码资源是共享的;

在这里插入图片描述

在只读的情况下两个进程的页表所映射至的物理地址也许相同的,而当一个进程要修改该物理内存中的内容时,OS将会重新在物理内存中申请一块空间,同时修改该进程所对应的页表映射关系;

在这里插入图片描述


🫙 可执行程序中的虚拟地址

请添加图片描述
实际在可执行程序当中也存在着所谓的虚拟地址,在一般的教材当中也被称为"逻辑地址";

存在一个程序:

#include<iostream>
using namespace std;int g_val = 100;int main()
{cout<<&g_val<<endl;return 0;
}

这个程序运行之后可以打印出该程序中全局变量g_val的地址;

在Linux中存在一个命令可以打印出一个可执行程序中的逻辑地址(虚拟地址),即objdump;

语法:

objdump -x <executable_file>

在此处配合| grep打印出该可执行程序中的虚拟地址,即:

objdump -x mytest | grep g_val

使用该命令后运行该程序:

$ objdump -x mytest | grep g_val
00000000004007f7 l     F .text	0000000000000015              _GLOBAL__sub_I_g_val
000000000060105c g     O .data	0000000000000004              g_val
$ ./mytest 
0x60105c

在上面的程序当中,程序运行的结果(打印全局变量地址)与使用objdump所显示出磁盘中的全局变量g_val地址相同,由此可见其进程中的虚拟地址与本在磁盘中的虚拟地址相同;

实际上在计算机当中,本质上无论是磁盘中的虚拟地址(逻辑地址)还是在进程当中的虚拟地址都是相同的;

只不过是在进程与磁盘中的表现形式不同;

当程序编译链接完成时生成的可执行程序当中将会存在代码数据等,在这些代码数据当中存在着静态的虚拟地址,这些地址被称作逻辑地址;

当这个程序被执行后即被加载至内存当中成为进程时,进程将会去初始化自身的PCB结构体;相对应的PCB结构体内的各种数据结构也将要被进行维护与初始化;

磁盘中的虚拟地址(逻辑地址)将会初始化PCB结构体中对应的进程地址空间,使得进程地址空间中的虚拟地址与原本磁盘内的虚拟地址(逻辑地址)保持一致;

在这里插入图片描述


🫙 物理地址分布方式

请添加图片描述
在上面的图中可以发现:

在对进程地址空间进行初始化时,真正将虚拟地址与物理地址进行关联的时候,其物理地址并没有按照原本的虚拟地址原模原样的进行对应的初始化;

在对对应物理地址进行初始化时更像是以一种随机的方式;

为了物理内存的安全性,Linux中采用了一种地址空间随机化(ASLR)的一种内存攻击缓存技术;

当对应的进程地址空间的虚拟地址在初始化时通过页表映射至物理内存时将会采用这种方式;

使得对应进程的物理内存地址无法被预测,也保证了进程在运行时的安全性;

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

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

相关文章

Notes2024节气和日历来了

大家好&#xff0c;才是真的好。 还有三周就是2024年了。 2024的节假日安排其实早就发布&#xff0c;有些人已经把这些节假日安排都写在自己的日历上了&#xff1b;同时我们这里也设置了一份&#xff0c;包括节假日和农历二十四节气以及中西传统的节日日期等。 如果你需要的…

均匀分布的随机变量

如果连续型随机变量的概率密度满足如下公式&#xff1a; 那么就称在区间(a,b)上服从均匀分布&#xff0c;记为。

国际语音呼叫中心有什么功能特点?

国际语音呼叫中心的功能特点 智能客服 国际语音呼叫中心通过智能客服系统&#xff0c;可以为客户提供快捷、高效的服务。可以根据客户需求&#xff0c;自动回答部分常见问题&#xff0c;提高客户服务效率。 个性化定制 国际语音呼叫中心平台可以根据客户需求&#xff0c;为…

el-tree搜索的使用

2023.12.11今天我学习了如何对el-tree进行搜索的功能&#xff0c;效果如下&#xff1a; 代码如下&#xff1a; 重点部分&#xff1a;给el-tree设置ref&#xff0c;通过监听roleName的变化过滤数据。 default-expand-all可以设置默认展开全部子节点。 check可以拿到当前节点的…

线程安全集合类

文章目录 1. ConcurrentHashMap2. LinkedBlockingQueue 阻塞队列3. ConcurrentLinkedQueue4. CopyOnWriteArrayList JDK1.7 hashmap采用数组加链表头插的方式&#xff0c;在扩容时会出现循环死链问题&#xff0c;A->B->C扩容后C->B->A AB BA出现循环死链。 1. Conc…

msvcp140.dll丢失怎么办?这些方法值得一试

小编将探讨计算机系统中MSVCP140.DLL文件的重要性及其潜在的问题和相应的修复措施。此文件对实现软件应用的特定功能起着关键性的作用&#xff0c;当其丢失时&#xff0c;某些运行环境下的应用程序和游戏便无法正常运作。因此&#xff0c;了解并解决相关问题非常必要。 一、msv…

SpringBoot Maven 项目打包的艺术--主清单属性缺失与NoClassDefFoundError的优雅解决方案

Maven项目的Jar包打包问题-没有主清单属性&&ClassNotFoundException 与 NoClassDefFoundError 文章目录 Maven项目的Jar包打包问题-没有主清单属性&&ClassNotFoundException 与 NoClassDefFoundError1、问题出现1.1、Jar包运行&#xff1a;没有主清单属性解决方…

如何在Python控制台中运行程序

一、打开cmd&#xff0c;并进入程序所在的目录 这里可以先进入要运行的程序所在的目录&#xff0c;然后在地址栏输入cmd并回车&#xff0c;就可以打开cmd并定位到当前目录。 二、在控制台中运行程序 python hello.py运行结果就会打印出来了

【运维】Kafka高可用: KRaft(不依赖zookeeper)集群搭建

文章目录 一. kafka kraft 集群介绍1. KRaft架构2. Controller 服务器3. Process Roles4. Quorum Voters5. kraft的工作原理 ing 二. 集群安装1. 安装1.1. 配置1.2. 格式化 2. 启动测试2.1. 启功节点服务2.2. 测试 本文主要介绍了 kafka raft集群架构&#xff1a; 与旧架构的不…

Unity | Shader基础知识(第六集:语法<如何加入外部颜色资源>)

目录 一、本节介绍 1 上集回顾 2 本节介绍 二、语法结构 1 复习 2 理论知识 3 Shader里声明的写法 4 Properties和SubShader毕竟不是一家人 三、 片元着色器中使用资源 四、代码实现 五、全部代码 六、下集介绍 相关阅读 Unity - Manual: Writing Surface Shaders…

jpa 修改信息拦截

实现目标springbootJPA 哪个人&#xff0c;修改了哪个表的哪个字段&#xff0c;从什么值修改成什么值 import jakarta.persistence.*; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; im…

死锁(JAVA)

死锁在多线程代码中是非常严重的BUG&#xff0c;一旦代码中出现死锁就会导致线程卡死。 当单个线程连续两次对同一个对象进行加锁操作时&#xff0c;如果该锁是不可重入锁就会发生死锁&#xff08;线程卡死&#xff09; 两个线程两把锁&#xff0c;如果出现这种情况也是会发生…

逻辑回归代价函数

逻辑回归的代价函数通常使用交叉熵损失来定义。这种损失函数非常适合于二元分类问题。 本篇来推导一下逻辑回归的代价函数。 首先&#xff0c;我们在之前了解了逻辑回归的定义&#xff1a;逻辑回归模型是一种用于二元分类的模型&#xff0c;其预测值是一个介于0和1之间的概率…

【产品设计】零代码核心模块之一:表单

在数字化越来越高级、越来越智能的场景下&#xff0c;信息收集依旧是适用场景最为丰富的方式之一。 应用开发工作台开三大核心模块&#xff1a;表单、流程、报表。 表单&#xff1a;一般适合数据录入&#xff0c;专人管理&#xff0c;如&#xff1a;用户调研、产品入库&#x…

项目篇 | 图书管理系统 | 图像加载与绘制

项目篇 | 图书管理系统 | 图像加载与绘制 基本介绍 首先解释清楚什么叫图像加载与绘制,意思就是说项目中需要用到一些图片资源(各种图标),我们要在图书管理系统中展示这些图片,就需要先导入图片到项目中,再加载图片资源(通过资源路径)、绘制图片(即展示)。 注:如果…

商业印刷市场分析:预计2029年将达到53004亿元

商业印刷技术显示了强大的生命力。电子商务的扩张性发展&#xff0c;传统的商务印刷行业也在逐渐的转型。中国印刷业已深度融入全球印刷加工产业链&#xff0c;为国际社会超过50个国家提供印刷包装服务。数据显示&#xff0c;中国印刷业对外加工贸易额已达842亿元。 商业印刷是…

【无标题】C++ STL -->模拟实现vector

这篇文章将模拟实现vector类的常用函数 vector类的函数接口 namespace ding {template<class T>class vectot{public:typedef T* iterator;typedef const T* const_iterator;//Member functionsvector(); vector(size_t n, const…

JVM的内存分区以及垃圾收集

1.JVM的内存分区 1.1方法区 方法区(永久代&#xff09;主要用来存储已在虚拟机加载的类的信息、常量、静态变量以及即时编译器编译后的代码信息。该区域是被线程共享的。 1.2虚拟机栈 虚拟机栈也就是我们平时说的栈内存&#xff0c;它是为java方法服务的。每个方法在执行的…

最大公因数,最小公倍数详解

前言 对于初学编程的小伙伴们肯定经常遇见此类问题&#xff0c;而且为之头疼&#xff0c;今天我来给大家分享一下&#xff0c;最大公因数和最小公倍数的求法。让我们开始吧&#xff01; 文章目录 1&#xff0c;最大公因数法1法2法3 2&#xff0c;最小公倍数3&#xff0c;尾声 …

[C++] 虚函数、纯虚函数和虚析构(virtual)

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/weixin_43197380&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 Loewen丶原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&…