【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 arastruct 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语言写的,也会有自己的虚拟地址空间。只要在系统里面,誰都一样。

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

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

相关文章

vue3组件之间的传参

1、父传子 defineProps 父组件 <script setup>import { reactive } from vue;import Children from ./children.vue;const parentProps reactive({name:zhangsan,age:20})</script><template><div>这是父组件</div><div>子组件:<Chil…

探索大型语言模型(LLM)在人类性格个性评估(MBTI)中的前景与应用

1.概述 大型语言模型&#xff08;LLM&#xff09;如ChatGPT在各个领域的应用确实越来越广泛&#xff0c;它们利用庞大的数据集进行训练&#xff0c;以模拟人类的语言理解和生成能力。这些模型在提供信息、解答问题、辅助决策等方面表现出了强大的能力&#xff0c;但它们并不具…

AI大模型之路 第二篇: Word2Vec介绍

你好&#xff0c;我是郭震 今天我来总结大模型第二篇&#xff0c;word2vec&#xff0c;它是大模型的根基&#xff0c;一切NLP都会用到它。 Word2Vec Word2Vec 是一种流行的自然语言处理&#xff08;NLP&#xff09;工具&#xff0c;它通过将词汇表中的每个单词转换成一个独特的…

优先级队列(概念理解/底层模拟/时间复杂度分析)

目录 1.概念理解 2.优先级队列的底层模拟 2.1堆的概念 2.2优先队列的模拟实现 2.2.1把Heap类定义好 2.2.2初始化堆 2.2.3创建大堆 1.思路 以此二叉树为例&#xff1a; 图文理解&#xff1a; 2.思路转化为代码 2.2.4堆操作之offer&#xff08;进队列&#xff09; 1…

机器学习-10-基于paddle实现神经网络

文章目录 总结参考本门课程的目标机器学习定义第一步&#xff1a;数据准备第二步&#xff1a;定义网络第三步&#xff1a;训练网络第四步&#xff1a;测试训练好的网络 总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍基于paddle实现神经网络。 参考 MNIST 训练_副…

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

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

vue ant form validate如何对数组下的表单校验

问题 使用Ant Design Vue校验表单时&#xff0c;通过validateFields&#xff0c;但是如何一个数组内部的校验呢&#xff1f; 效果图&#xff1a; 实现方式&#xff1a; 通过 v-for 循环渲染:name"[]"实现&#xff0c;我们直接看代码。 <template><a-for…

Spring Boot中JUnit 4与JUnit 5的如何共存

文章目录 前言一、先上答案二、稍微深入了解2.1 maven-surefire-plugin是什么2.2 JUnit4和JUnit5有什么区别2.2.1 不同的注解2.2.2 架构 前言 在maven项目中&#xff0c;生成单测时是否有这样的疑问&#xff1a;该选JUnit4还是JUnit5&#xff1f;在执行 mvn test 命令时有没有…

三、SpringBoot整合MyBatis

本章节主要描述MyBatis的整合&#xff0c;以及使用mybatis-generator-maven-plugin生成代码骨架&#xff0c;源码&#xff1a; jun/learn-springboot - Gitee.com 一、首先建数据库 本示例用的是MySQL8.0.23&#xff0c;建表t_goods、t_orders&#xff0c;略... 二、goods模块…

Java | Leetcode Java题解之第36题有效的数独

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isValidSudoku(char[][] board) {int[][] rows new int[9][9];int[][] columns new int[9][9];int[][][] subboxes new int[3][3][9];for (int i 0; i < 9; i) {for (int j 0; j < 9; j) {char …

随机森林原理及应用

目录 一、随机森林原理、优点、应用场景 1.1基本原理 1.2主要优点 1.3使用场景 二、具体实例 一、随机森林原理、优点、应用场景 随机森林是一种流行且强大的机器学习算法&#xff0c;属于集成学习方法的一部分&#xff0c;主要用于分类和回归任务。它通过组合多个决策树…

SSTV音频转图片

SSTV工具有很多&#xff0c;这里使用RX-SSTV慢扫描工具 下载安装 RX-SSTV解码软件 下载地址&#xff1a;https://www.qsl.net/on6mu/rxsstv.htm 一直点下一步&#xff0c;安装成功如下图: 虚拟声卡e2eSoft 由于SSTV工具是根据音频传递图片信息&#xff0c;正常解法需要一…

在【laravel框架】学习中遇到的常见的问题以及解决方法

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

Marching Cubes算法

Marching Cubes算法 1. 简介2. 算法原理的理解2.1 如何找到面经过的这些小块(六面体)&#xff1f;2.2 找到后&#xff0c;如何又进一步的找到面与这些小块(六面体)的交点&#xff1b;2.3 这些交点按照怎么的拓扑连接关系连接&#xff0c;是怎么操作的&#xff1f; 3. 总结4. 参…

金融时报:波场亮相哈佛大学并举办TRON Builder Tour活动

近日,波场TRON作为顶级白金赞助商出席哈佛区块链会议并成功举办TRON Builder Tour哈佛站活动,引发海外媒体热议。美联社、金融时报、Cointelegraph等国际主流媒体及加密知名媒体均对此给予了高度评价,认为本次大会对TRON Builder Tour活动具有里程碑意义,彰显了波场TRON致力于促…

mysql基础5——设置主键

业务字段尽量不要用做主键 删除主键&#xff0c;只是主键被删除&#xff0c;字段还存在 alter table demo.membermaster drop primary key; 添加一个字段设置为主键并给主键添加自增约束 alter table demo.membermaster add column id int primary key auto_increment; 自增…

Gitea 简单介绍、用法以及使用注意事项!

Gitea 是一个轻量级的代码托管解决方案&#xff0c;它提供了一个简单而强大的平台&#xff0c;用于托管和协作开发项目。基于 Go 语言编写&#xff0c;与 GitLab 和 GitHub Enterprise 类似&#xff0c;但专为自托管而设计。以下是对 Gitea 的详细介绍&#xff0c;包括常用命令…

anaconda配置的环境对应的地址查看,环境安装位置

打开conda指令窗口 这个和上面的都一样&#xff0c;哪个都行 点开后&#xff0c;输入 conda env list 这里显示的就是自己的每个环境对应的地址了

游戏黑灰产识别和溯源取证

参考&#xff1a;游戏黑灰产识别和溯源取证 1. 游戏中的黑灰产 1. 黑灰产简介 黑色产业&#xff1a;从事具有违法性活动且以此来牟取利润的产业&#xff1b; 灰色产业&#xff1a;不明显触犯法律和违背道德&#xff0c;游走于法律和道德边缘&#xff0c;以打擦边球的方式为“…

巧用断点设置查找bug【debug】

默认设置的断点&#xff0c;当代码运行到断点处MCU就会被挂起&#xff0c;从而停在断点处。 但在某些情况下&#xff0c;如调试FCCU时&#xff0c;如果设置断点&#xff0c;MCU停下后将会导致 FCCU 配置WDG超时。或在调试类似电机控制类的应用时&#xff0c;不适当的断点会导 致…