『 Linux 』进程地址空间概念
文章目录
- 🫙 前言
- 🫙 进程地址空间是什么
- 🫙 写时拷贝
- 🫙 可执行程序中的虚拟地址
- 🫙 物理地址分布方式
🫙 前言

在c/C++中存在一种内存的概念;
一般来说一个内存的空间分布包括栈区,堆区,代码段等等;
且内存是自底向上(由0x00000000至0xFFFFFFFF);
以该图为例:

该图即为常见的内存分布图;
-
正文代码段
正文代码段所存放的数据一般为函数体的二进制代码;
-
已初始化数据区
已初始化数据区所存放的数据是在程序中声明的,并且具有初始值的变量,这些变量需要占用存储器的空间;
-
未初始化数据区
未初始化数据区所存放的数据是没有进行初始化或者初始值为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)的一种内存攻击缓存技术;
当对应的进程地址空间的虚拟地址在初始化时通过页表映射至物理内存时将会采用这种方式;
使得对应进程的物理内存地址无法被预测,也保证了进程在运行时的安全性;
相关文章:
『 Linux 』进程地址空间概念
文章目录 🫙 前言🫙 进程地址空间是什么🫙 写时拷贝🫙 可执行程序中的虚拟地址🫙 物理地址分布方式 🫙 前言 在c/C中存在一种内存的概念; 一般来说一个内存的空间分布包括栈区,堆区,代码段等等; 且内存是…...
PySpark大数据处理详细教程
欢迎各位数据爱好者!今天,我很高兴与您分享我的最新博客,专注于探索 PySpark DataFrame 的强大功能。无论您是刚入门的数据分析师,还是寻求深入了解大数据技术的专业人士,这里都有丰富的知识和实用的技巧等着您。让我们…...
三(五)ts非基础类型(对象)
在ts里面定义对象的方式也有很多。 普通定义 let obj1:{} {} // obj1.name fufu 报错,只能定义为空对象且不能修改 // 但是可以在赋初始值的时候直接添加属性,这是ts在类型推断时,它会宽容地匹配对象的结构。 let obj2:{} {name: fufu}…...
HeartBeat监控Redis状态
目录 一、概述 二、 安装部署 三、配置 四、启动服务 五、查看数据 一、概述 使用heartbeat可以实现在kibana界面对redis服务存活状态进行观察,如有必要,也可在服务宕机后立即向相关人员发送邮件通知 二、 安装部署 参照文章:HeartBeat监…...
FairGuard无缝兼容小米澎湃OS、ColorOS 14 、鸿蒙4!
随着移动互联网时代的发展,各大手机厂商为打造生态系统、构建自身的技术壁垒,纷纷投身自研操作系统。 而对于一款游戏安全产品,在不同操作系统下,是否能够无缝兼容并且提供稳定的、高强度的加密保护,成了行业的一大痛…...
【Copilot】Edge浏览器的copilot消失了怎么办
这种原因,可能是因为你的ip地址的不在这个服务的允许范围内。你需要重新使用之前出现copilot的ip地址,然后退出edge的账号,重新登录一遍,最后重启edge,就能够使得copilot侧边栏重新出现了。...
C++入门【6-C++ 修饰符类型】
C 修饰符类型 C 允许在 char、int 和 double 数据类型前放置修饰符。 修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。 下面列出了数据类型修饰符: signed:表示变量可以存储负数。对于整型变量来说,signe…...
STP笔记总结
STP --- 生成树协议 STP(Spanning Tree Protocol,生成树协议)是根据 IEEE802.1D标准建立的,用于在局域网中消除数据链路层环路的协议。运行STP协议的设备通过彼此交互信息发现网络中的环路,并有选择地对某些端口进行阻…...
Qt开发 之 记一次安装 Qt5.12.12 安卓环境的失败案例
文章目录 1、安装Qt2、安卓开发的组合套件2.1、CSDN地址2.2、官网地址2.3、发现老方法不适用了 3、尝试用新方法解决3.1、先安装JDK,搞定JDK环境变量3.1.1、安装jdk3.1.2、确定jdk安装路径3.1.3、打开系统环境变量配置3.1.4、配置系统环境变量3.1.5、验证JDK环境变量…...
基于SpringBoot的就业信息管理系统设计与实现(源码+数据库+文档)
摘 要 在新冠肺炎疫情的影响下,大学生的就业问题已经变成了一个引起人们普遍重视的社会焦点问题。在这次疫情的冲击之下,大学生的就业市场的供求双方都受到了不同程度的影响,大学生的就业情况并不十分乐观。目前,各种招聘平台上…...
Java面试整理(四)Java IO流
我记得自己刚开始学Java的时候,都听过师兄的分享,说IO流是很重要,而且很难。 自己正式接触之后,其实IO流这块知识并不是特别难,而且随着IT的发展,IO流这块反而用得不是很多。特别是在应用开发这个层面,用得更少。 当然,可能会有朋友跳出来说“这怎么可能?你不懂Java吧…...
《安富莱嵌入式周报》第328期:自主微型机器人,火星探测器发射前失误故障分析,微软推出12周24期免费AI课程,炫酷3D LED点阵设计,MDK5.39发布
周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程: 【实战技能】 单步运行源码分析,一期视频整明白FreeRTOS内核源码框架和运行…...
产品经理在项目周期中扮演的角色Axure的安装与基本使用
目录 一.项目周期流程 二.Axure是什么 三.Axure安装 3.1 一键式安装 3.2 汉化 3.3 授权登录 四.Axure的界面介绍及基本使用 4.1 菜单栏的使用 4.2 工具栏的使用 4.3 页面概要的使用及组件的使用 4.4 组件的样式设计 一.项目周期流程 在一般的项目周期中包含的工作内容有&…...
Dockerfile创建镜像介绍
1.介绍 Docker 提供了一种更便捷的方式,叫作 Dockerfile,docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build语法: # docker build [OPTIONS] <PATH | URL | -> 常用选项说明 --build-arg,设置构建时的…...
Android 滥用 SharedPreference 导致 ANR 问题
SharedPreference 是 Android 平台提供的一种轻量级的数据存储方式,它用于存储应用的配置信息或者一些简单的数据。SharedPreference 基于键值对的存储,并且支持基本的数据类型,如整型、字符串、布尔值等。它的使用非常简单方便,适…...
虚幻商城 道具汇总
文章目录 载具Vehicle Variety Pack(车辆品种包)Vehicle Variety Pack Volume 2(车辆品种包第 2 卷)家具Free Furniture Pack(免费家具包)Old West - VOL 1 - Interior Furniture(旧西部 - 第1卷 - 家具包)Old West VOL.3 - Travel Supplies and Goods(旧西部 - 第3卷…...
docker: Error response from daemon: failed to create shim task: OCI runtime
1 概述 在解决"Docker: Error response from daemon: failed to create shim task: OCI runtime"问题之前,我们先来了解一下Docker和OCI runtime的基本概念。 Docker是一个开源的应用容器引擎,可以帮助开发者将应用程序和其依赖打包到一个可…...
SpringBoot+线程池实现高频调用http接口并多线程解析json数据
场景 SpringbootFastJson实现解析第三方http接口json数据为实体类(时间格式化转换、字段包含中文): SpringbootFastJson实现解析第三方http接口json数据为实体类(时间格式化转换、字段包含中文)-CSDN博客 Java中ExecutorService线程池的使用(Runnable和Callable多…...
java实现局域网内视频投屏播放(一)背景/需求
一 背景 我们在用电视上投屏电影或者电视剧时,如果没有vip,用盗版电影网站投屏的话会有两个问题,1:他们网站没有投屏功能。2:卡!!!。还有就是不能随心所欲的设置自己先要自动播放的视频列表(如…...
【Spring】手写一个简易starter
需求: 自定义一个starter,里面包含一个TimeLog注解和一个TimeLogAspect切面类,用于统计接口耗时。要求在其它项目引入starter依赖后,启动springboot项目时能进行自动装配。 步骤: (1)引入pom依赖…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
