【Linux】进程的程序地址空间①

 

目录

前言:

1.什么是地址空间

区域划分

页表:

2.为什么要有地址空间

2.1 进程与内存解耦合

2.2安全

3.凭什么说进程具有独立性:

4.用地址空间解释一下申请内存


前言:

在C语言中,我们说我们将内存分为,栈区,堆区,静态区。栈区存放形式参数和临时的数据变量,堆区主要是用于开辟内存,静态区是存放全局变量,和static修饰的变量的。也说了内存的使用是从低地址到高地址,今天我们也可以一一来验证一下: 

首先对于栈区堆区和全局变量的地址验证:

*int g_unval;//未初始化的变量
 48 int g_val = 100;//初始化的全局变量
 49 int main(int argc,char*argv[],char*env[])
 50 {
 51 
 52 printf("code addr: %p\n ",main);//main函数地址
 53 printf("Initdata addr: %p\n ",&g_val);
 54 printf("Unitdata addr: %p\n ",&g_unval);
 55 //验证堆区地址
 56 char*heap1 = (char*)malloc(20);
 57 char*heap2 = (char*)malloc(20);
 58 char*heap3 = (char*)malloc(20);
 59 char*heap4 = (char*)malloc(20);
 60 
 61 
 62 int c = 0;
 63 printf("heap1 addr:%p\n",heap1);
 64 printf("heap2 addr:%p\n",heap2);
 65 printf("heap3 addr:%p\n",heap3);
 66 printf("heap4 addr:%p\n",heap4);
 67 //验证栈区地址                                                                  
 68 printf("stack addr:%p\n",&heap1);//变量在栈区上定义
 69 printf("stack addr:%p\n",&heap2);//变量在栈区上定义
 70 printf("stack addr:%p\n",&heap3);//变量在栈区上定义
 71 printf("stack addr:%p\n",&heap4);//变量在栈区上定义
 72 printf("c addr:%p\n",&c);

验证命令行参数和环境变量所以堆栈是相对而生的

 74 int i = 0;
 75 for(i = 0;argv[i];i++)
 76 {
 77   printf("&argv[%d] = %p\n",i,argv+i);//命令行参数                              
 78 
 79 }
 80 //环境变量
 81 
 82 int j = 0;
 83 for(j = 0;env[j];j++)
 84 {
 85   printf("&env[%d] = %p\n",j,env+j);//命令行参数
 86 
 87 }
 88 //环境变量
 89 
 90 
 91   return 0;
 92 

 

两张表,先有命令行参数表才有了环境变量表

换成argv[i]看环境变量字符串和命令参数字符串在那个位置:

int i = 0;                 
   75 for(i = 0;argv[i];i++)     
   76 {                          
   77   printf("argv[%d] = %p\n",i,argv[i]);//命令行参数                            
   78                                                                
   79 }                                                              
   80 //环境变量                                                     
   81 
   82 int j = 0;
   83 for(j = 0;env[j];j++)
   84 {
   85   printf("env[%d] = %p\n",j,env[j]);//命令行参数
   86                                               
   87 }                 

结论:无论是表还是表指向的项目,都在栈上部

未初始化数据和已初始化数据在进程中一直存在,我们现在创建一个变量

加上static 修饰


   62 static int c ;
   63 c = 0;                                                                        

static 定义的变量,不随着函数调用释放,只初始化一次,语言上已经是全局的了,不随着函数调用而释放。即使不初始化也是会初始化为0

以上的测试结果基本和第一张图是保持一致的,但是 这套规则在vs下可能不一样,在linux中是一样的。我们还能发现,在linux中我们每次运行对于同一个量,其地址是一样的,但是在vs中不同

地址为什么一样?vs下不一样

我们现在只能说:一样不一样对于linux来说无所谓,windows有所谓,差别大,windows防止代码或者数据,被固定编制到其他地方,做了很多策略,地址随机化就是一种,也就是说是出于安全考虑,而linux的做法优雅得多。(vc6.0打印出来是一样的。)

现在有这样一个问题:

上面这张图是内存吗?

我们现在来写这样一段代码来看一下一个现象:

int g_val = 100;
   17 int main()
   18 {
   19   pid_t id = fork();
   20   if(id == 0)
   21   {
   22     //子进程
   23     int cnt = 0;
   24     while(1)
   25     {
   26       printf("child,pid :%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
   27       sleep(1);
   28       cnt++;
   29       if(cnt == 5)
   30       {                                                                                                                                                                                  
   31         g_val  = 200;
   32         printf("child change g_val: 100->200");
   33       }
   34     }
   35 
   36   }
   37   else{
   38 
   39     while(1)
   40     {
   41       printf("father,pid :%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
   42       sleep(1);
   43   }
   44   }
   45   return 0;
   46 }

内容不一样,地址一样?

同一个地址打印出不同的值,父子进程分别看值不一样吗?

这个地址,绝对不是物理地址,因为如果是一个物理地址绝对不会拿出两个值。

现在只能说这个地址叫做虚拟地址或者叫做线性地址,更为关键的是,我们都是取语言级别的地址,那么也就是所说我们C语言c++打印出来的都不是物理地址而是虚拟地址或者线性地址。

 打印出这么地址都是上述图片结构当中对应的地址,这个地址空间排布的情况不是物理内存,正确的叫法为:进程地址空间

开启正文:

不管是C语言还是c++程序变成进程,每一个进程都存在一个进程地址空间。所以这个和语言没有多少区别。C语言和c++要遵守这样的规则是系统决定的,我们的虚拟机也是进程,也要遵守规则。

1.什么是地址空间

程序运行时,每个进程都要有自己的pcb,都会有自己的进程地址空间,对于这个进程的数据肯定是存放在物理内存中的,但是,我们的进程运行时如何拿到或者访问到这块内存,拿到数据,我们在操作系统内部还存在一种映射关系,大家可以理解为一张表,这个映射表,会将虚拟地址和物理地址建立一个映射。

这是一个进程,子进程也是会拥有同样的配置。同样的子进程将相同的映射关系也继承了下来:

所以子进程指向同一个物理地址,所以虚拟地址打印也会是一样的。 然后我们的子进程对数据进行修改,但是进程具有独立性,两个进程指向同一个内存空间,所以操纵系统会在物理内存中再给子进程开辟一块空间。让后子进程修改数据就不影响父进程,最后再修改一下映射关系。

此时子进程就只想自己的变量内存空间,打印的地址是虚拟地址,所以打印的地址一样,但是由于映射关系不同,父子进程拿到了不同的值。 

本质原因就是:虚拟关系对应不一样

当时我们说fork函数的返回值对于父子进程不一样的原因也就是:

fork分流的根本原因,同一个虚地址,不同的对应关系

每个程序运行时都有自己的pcb,每一个进程都会有自己对应的地址空间。然后,进程地址空间在32位操作系统下为0到4gb,那么我们就要来讲一下什么是地址空间:操作系统给每个进程画的饼,每个进程一个饼,饼也需要管理 ,操作系统也要对进程地址空间进行管理,如何管理:新描述再组织

所以什么是进程地址空间:进程地址空间是是数据结构,具体到进程中,就是特定的数据结构对象。

struct 进程地址空间
{
//进程地址空间的属性
struct *next

}

所以我们的进程pcb中肯定还会存在一个这样的指针来指向程序的进程地址空间:

struct tast_struct
{
struct 进程地址空间* p;
}

所以进程和进程地址空间之间的关系就是数据结构之间的关系。

那么进程地址空间中都有什么呢?

区域划分

我们原先说这个空间分为栈区,堆区,静态区等等,那么又如何理解这些区域呢?

同桌之间会划线,三八线本质是区域划分,如何用计算机语言描述区域划分的工作:

struct area

{

int start;

int end;



}
struct destop_area

{

astruct area xiaopaamh ara

struct xiaoaua_area;
}
struct destop_area = {{1,50},{51,100}}

这样就将长度为100的空间进行了划分,为了更好表述,结构体可以这样书写:

struct destop_ara
{
int person1_start;
int persosn1_end;
int person2_start;
int person2-end;
}

可以实现这样的划分:

struct deatop_area ={1,50}{51,100}}

怎么判断越界:有了划分,可以随时判断越界。你放东西放到另外区域内很容易判别,因为区域是一个范围。

怎么扩大区域划分,怎么扩大自己的区域划分

person1_end -=20;
area.person2_star +=20

所以,如果我们设计进程的地址空间,进程地址空间的栈区啦,堆区啦,静态区等等都是一个区域,所以该地址空间中必须有的字段是:整数描述的各个区域

struct xxx
{
int code_strt,code_end;
int init,intit end;
int heao_start,heap_end

}

我们来看一下linux内核的源代码看一下他的进程地址空间的实现方式:

存在大量的sart end,代码从哪里开始,到哪里结束,所以进程的地址空间是 进程的一种数据结构,地址空间里将范围划分好了,进程创建时,进程地址空间就被创建出来,每个字段被初始化,所以进程的视角就有区域的概念了。

页表:

理解一下空间划分:空间划分不仅仅限定一个区域,而是区域划分本质为区域内的各个地址都可以使用,每一个地址都可以被进程使用,但是我们的地址空间不具备对我们的代码和数据的保存能力,是在物理内存中存放的那么现在需要一种机制,将地址空间上的地址(虚拟地址)转换到物理内存,操作系统给我们的进程准备一个映射表:我们称为:页表

当可执行程序是一个文件存放在磁盘中,双击运行变成一个进程,操作系统要为这个进程创建pcb,在创建进程的地址空间,然后要将进程的数据和代码加载到实际的内存中进程怎么找到代码和数据,此时需要页表,页表左侧叫做虚拟地址,右侧叫做物理地址,每行代码都有地址,都会经过页表通过虚拟地址转换映射到实际地址,最后找到相应的代码和数据

这个转化工作是由cpu做的,当cpu读取正文代码时,cpu内有一个寄存器cr3:cr3寄存器保存页表的起始地址,cpu读取指令时,指令里面比如对变量进行++,要读取变量的内容拿到的是虚拟地址,然后通过页表找到实际内容,实际上这个转换工作,是用MMu来做的,叫做内存管理单元,是集成在cpu上的,所以映射,查找等工作是由硬件自动完成的。

MMu

MMU是Memory Management Unit的缩写,中文名是内存管理单元,有时称作分页内存管理单元(英语:paged memory management unit,缩写为PMMU)。它是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件。它的功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制,在较为简单的计算机体系结构中,负责总线的仲裁以及存储体切换(bank switching,尤其是在8位的系统上)

虚拟地址是给用户看的,cr3保存的地址是物理地址,页表也是一种数据结构,我们的操作系统也要对其进行管理,也是需要保存的,虚拟地址是给进程的,进程是用户的,自己用的是物理内存

所以为什么fork过后父子进程的代码一样,是因为页表是继承来的,页表也是一样的。关于页表我们后续还会慢慢补充。

2.为什么要有地址空间

为什么要存在转化,直接访问地址不好吗?

2.1 进程与内存解耦合

因为有了映射的存在,左边叫做进程管理,右边叫做内存管理,将来对应的程序

可不可以将磁盘的数据在内存中任意加载呢,也就是说操作系统给进程申请内存空间不用考虑进程的感受,只用建立好映射。

因为有地址空间和页表存在:可以将物理内存从无序变为有序,也就是说页表左侧有序,右侧随便放,那么进程就可以以统一的视角,看待整个内存。而同时可以做到进程和内存互不干扰。,将内存管理和进程管理解耦合,这两部分代码可以没有直接关联。 方便操作系统设计

2.2安全

.除了正常转化,对非法请求进行拦截

当进程想要访问内存中的某个数据,要是请求合法就正常访问,如果要是访问不正常就拦截,拦截非法请求。所以地址空间+页表是保护内存安全的重要手段。(指针越界崩溃,不影响操作系统和其他进程)转换工作是cpu+MMU共同完成

3.凭什么说进程具有独立性:

进程=内核数据1结果+自己的代码,即使是父子进程,内核数据结构各自独立,代码可以共有,因为是只看,数据通过写实拷贝的方式各自拥有一份。独立性得到保证。我们删除增加一个进程也不会影响其他进程

进程拥有:进程的pcb,进程的地址空间;进程的页表,进程的映射关系,自己的代码和数据

4.用地址空间解释一下申请内存

解释:内存申请

我们喜欢在C语言中使用malloc等申请内存

申请内存立马使用吗?

.申请的内存本质在哪里申请呢?

操作系统内部有很多进程,如果进程申请内存不立马使用,如果内存分配给你,时间在走,这块内存操作系统也拿不走,但是进程短时间不使用,我们的操作系统要为效率和资源使用率负责,操作系统必须确保用户要用,再给用户,所以我们经常使用的malloc,reallloc等函数申请内存并不是直接就在物理内存上去开辟空间,用户拿到的是虚拟地址,用户拿到虚拟地址过后,页表不会建立映射关系,因为操作系统不会立马将内存资源给这个进程,当用户尝试通过虚拟地址进行写入的时候,写入的时候,当前访问地址空间合法,二页表没有建立映射,操作系统此时就会将用户写入暂停,然后在物理内存上开辟空间,建立映射,此时这个内存是立马就给用户,所以地址空间就像一张支票。这样做,我们可以充分保证我们内存的使用率,用户要用再给,这个操作不会慢,时因为,不论是立马给还是用户要用的时候给都设计到扩大我们的那个进程地址区间和内存开辟以及建立映射关系,立马给和用才给无非就是将这几件事一次性做完和分开做完的确保,时间成本是不变的,但是带来的好处new 、malloc速度会变快。操作系统的整体效率整体提升。

我们申请了空间,当我们尝试写入的时候,访问合法,页表没有建立关系,操作系统将用户拦截住,然后到物理内存开辟内存,建立映射关系,这个过程我们叫做缺页中断。

在l不管是什么语言,要在内存中跑起来,都要变成进程,都要能够以进程地址空间的形式出现,任何语言都有,这个图出现最多在c类语言,是编译语言,编译形成什么样子,也就是说我们写的代码就是cpu要执行的代码,但是有些语言它是在语言层给我们做了很多分装,叫做解释型或半解释型语言,python没有编译器只有解释器,java有虚拟机,这些都是c语言写的,也会有自己的虚拟地址空间。只要在系统里面,誰都一样。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【目标跟踪】ByteTrack详解与代码细节

文章目录 一、前言二、代码详解2.1、新起航迹2.2、预测2.3、匹配2.4、结果发布2.5、总结 三、流程图四、部署 一、前言 论文地址:https://arxiv.org/pdf/2110.06864.pdf git地址:https://github.com/ifzhang/ByteTrack ByteTrack 在是在 2021 年 10 月…

同元软控专业模型库系列——热流篇

一、引言 传热与流动是自然界与科学技术领域最普遍的物理现象。聚焦工业领域,传热、流体流动和燃烧问题是热工、核能、动力机械等行业所需研究解决的主要问题。复杂热流系统往往具有高复杂性、高成本性和高可靠性的特点,传统研制模式已逐渐无法满足现有…

【UE5.1 C++】提升编译速度

步骤 1. 在“C:\Users\用户\AppData\Roaming\Unreal Engine\UnrealBuildTool”目录下找到“BuildConfiguration.xml”文件 打开“BuildConfiguration.xml”&#xff0c;添加如下部分内容 <?xml version"1.0" encoding"utf-8" ?> <Configuratio…

干货:40个数据统计和分析的术语,让你的可视化大屏有理有据

1. 总体&#xff08;Population&#xff09;&#xff1a;指研究对象的全体&#xff0c;即研究问题所涉及的所有个体或事物的集合。 2. 样本&#xff08;Sample&#xff09;&#xff1a;从总体中选取的一部分个体或事物&#xff0c;用于代表总体进行研究。 3. 参数&#xff08…

MySQl-8.3.0版本安装下载教程(超详细保姆级教程)

第一步&#xff0c;去百度找到MySQl官网 第二步,找到DOWNLOAD&#xff08;下载&#xff09; 第三步 第四步 第五步 第六步.选择倒数第2个 第七步 第八步然后根据步骤安装就好了

我最重要的三个女人都生病了,两个已经住院了

往年的金三银四&#xff0c;大部分时间我都在面试&#xff0c;今年的金三银四&#xff0c;却一直往医院跑了。 我最重要的三个女人全生病了&#xff0c;病毒感染&#xff0c;20号我妈办理了住院&#xff0c;21 号我闺女小白牙办理了住院&#xff0c;她俩还不是同一家医院媳妇儿…

2024Xtu程设第一次练习题解

程设练习题谢大会专门查重 1.1531奇怪的数字 题目让我们从小到大输出1e6以内所有的答案&#xff0c;其实也没什么好的思路 就是将一个数n的所有位都拆出来&#xff0c;遍历这些位&#xff08;每次取一个x&#xff09;&#xff0c;然后通过作除法&#xff08;y n / x&#xf…

研究助理(博士后),院所两级共同资助经费80万

一、声学所介绍 1964年&#xff0c;为落实国家声学规划&#xff0c;满足国家迫切需要&#xff0c;形成全国声学学科研究中心&#xff0c;经国务院副总理聂荣臻元帅批准&#xff0c;成立中国科学院声学研究所。 声学所是从事声学和信息处理技术研究的综合性研究所&#xff0c;…

在React项目中试用Tailwind

TailwindCSS TailwindCSS 是一个套 CSS 的工具类&#xff0c;把常用的功能都进行了定义&#xff0c;下面是一个官网的例子&#xff0c;可以看到Tailwind对一元页面素写了很多类&#xff0c;日常开发中只要定义一两个类就可以搞定类似的功能了。这里写了这么多 p-6 max-w-sm mx…

线程池的核心参数有哪些???

线程池的核心参数包括以下七个&#xff1a; corePoolSize&#xff1a; 这是线程池中的核心线程数&#xff0c;即池中会保留的最少线程数。当提交任务时&#xff0c;如果当前线程数小于核心线程数&#xff0c;线程池会创建新的线程来执行任务。如果当前线程数等于或大于核心线程…

#天空星按键点灯(不中断与中断方式)

&#xff08;1&#xff09;非中断按键点灯 &#xff0c;弹起阻塞&#xff08;天空星的用户按键为PA0&#xff0c;按下高电平&#xff0c;不按下低电平&#xff0c;含有硬件消抖&#xff09; /** 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源* 开发板官网&#xff1…

MySQL修改数据表的结构

创建数据库 -- create database 创建的数据库名; create database test; 这里创建了一个名为 test 的数据库 选择需要使用的数据库 -- use 数据库名; use test; 这里使用 test 数据库 创建数据表 -- create table 表名(字段名1 数据类型(长度) 约束,字段名2 数据类型(长…

辽宁梵宁教育设计培训:赋能大学生,新技能学习再升级

辽宁梵宁教育设计培训&#xff1a;赋能大学生&#xff0c;新技能学习再升级 在当今这个日新月异、信息爆炸的时代&#xff0c;大学生们面临着前所未有的挑战与机遇。为了帮助他们更好地适应社会的快速变化&#xff0c;提升个人的综合素质和竞争力&#xff0c;辽宁梵宁教育设计…

【Node.js】01 —— fs模块全解析

&#x1f525;【Node.js】 fs模块全解析 &#x1f4e2; 引言 在Node.js开发中&#xff0c;fs模块犹如一把万能钥匙&#xff0c;解锁着整个文件系统的操作。从读取文件、写入文件、检查状态到目录管理&#xff0c;无所不能。接下来&#xff0c;我们将逐一揭开fs模块中最常用的那…

高级数据结构—树状数组

引入问题&#xff1a; 给出一个长度为n的数组&#xff0c;完成以下两种操作&#xff1a; 1. 将第i个数加上k 2. 输出区间[i,j]内每个数的和 朴素算法&#xff1a; 单点修改&#xff1a;O( 1 ) 区间查询&#xff1a;O( n ) 使用树状数组&#xff1a; 单点修改&#xff1a…

文档分享怎么用二维码?扫码获得文档的制作方法

现在日常工作和生活中&#xff0c;经常会看到可以用于展示文件的二维码图片&#xff0c;使用这种方式可以向其他人传递一些资料、通知、数据等情况。比如常见的内容有企业介绍、产品内容、使用说明、活动流程等类型的内容&#xff0c;那么这些不同类型的文件该如何制作二维码呢…

医学图像三维重建与可视化系统 医学图像分割 区域增长

医学图像的三维重建与可视化&#xff0c;这是一个非常有趣且具有挑战性的课题&#xff01;在这样的项目中&#xff0c;可以探索不同的医学图像技术&#xff0c;比如MRI、CT扫描等&#xff0c;然后利用这些图像数据进行三维重建&#xff0c;并将其可视化以供医生或研究人员使用。…

el-select多选非空校验

一、首先是前端版本&#xff08;不建立在版本上的bug修改就是耍流氓&#xff01;&#xff09;&#xff1a; 二、原来页面是下拉单选&#xff0c;新需求要改成下拉多选&#xff0c;改成多选后就发现非空校验失效了。 三、el-select多选&#xff0c;绑定v-model的就是一个数组了…

Unity导出package

C#代码导出后为一个dll&#xff0c;原有的不同平台的库不变。 以下操作均在build PC 平台下操作。 1.在要导出的文件夹下建assembly definition (Any platform) 2.将项目文件夹下的\Library\ScriptAssemblies中的相应assembly definition的dll复制到要导出的文件夹下 3.在uni…

【ES】springboot集成ES

1. 去Spring官方文档确认版本兼容性 这一版的文档里没有给出springboot的版本对应&#xff0c;但我在一个博主的文章里看到的es8.0以前的官方文档中就有给出来&#xff0c;所以还需要再去寻找spring framework和springboot的对应关系&#xff1f;&#xff1f;&#xff1f; 还…