进程地址空间

在C/C++程序员眼中,对内存有着明确的分区,例如堆区、栈区等等,
那么这些谈论的东西,跟我们在系统中的内存是一个东西吗?
今天我们来探讨这一知识。

文章目录

  • 1.对内存分区的认识
    • 1). 栈区的使用特点
    • 2). 内存区域的划分
    • 3). 环境变量和命令行参数的存储
  • 2. 进程地址空间
    • 1). 解决遗留问题
    • 2). 什么是地址空间?
      • a. 什么是地址空间?
      • b. 地址空间结构体的进一步理解
    • 3). 页表
      • a. 访问权限
      • b. 地址对应内容有无
      • a. 进程切换与页表
  • 3. 为什么要有地址空间
  • 4. 再次理解写时拷贝

1.对内存分区的认识

在C/C++程序员眼中,我们的内存应该是这个样子:
在这里插入图片描述

1). 栈区的使用特点

在关于堆区和栈区的使用应该是,堆区向上增长,栈区向下增长
栈区向下增长:

#include <stdio.h>

int main()
{
  int a = 0;
  int b = 0;
  int c = 0;
  int d = 0;
  int e = 0;
  printf("%p\n", &a);
  printf("%p\n", &b);
  printf("%p\n", &c);
  printf("%p\n", &d);
  printf("%p\n", &e);                      
  return 0;
}

在这里插入图片描述
我们看到先创建的变量的地址是要比后创建的变量的地址是大的,也说明了栈空间的使用是向下的,但是我们在面对一个数组的时候,数组的第一个元素和最后一个元素谁的地址大呢?在日常经验的使用中,明显最后一个元素的地址是大于首元素地址的,这又与栈区向下增长的说法不符了,其实栈区的使用特点是:向下增长,向上使用,当在创建一个变量的时候,进程会在栈区中开辟所需要的空间,然后再向上使用。例如一个结构体:

#include <stdio.h>

struct stu{
  int a;
  int b;
  int c;
};

int main()
{
  struct stu s;
  printf("%p\n", &s.a);
  printf("%p\n", &s.b);
  printf("%p\n", &s.c);   
}

在这里插入图片描述
可以看到结构体中的成员也遵循栈区的向下增长,向上使用。
而C语言访问变量的方式的本质也就是变量起始地址+偏移量的访问方法
所以堆区和栈区是堆栈相对而生。

2). 内存区域的划分

我们上面说了内存是那样划分的,所以现在我们用代码证明一下,上面划分的区域对不对:

 #include <stdio.h>
 #include <stdlib.h>
 
 
 int b = 0;
 
 int main()
 {
   int* a = (int*)malloc(sizeof(int) * 10);
   const char* str = "hello world";
   printf("栈区:%p\n", &a);
   printf("堆区:%p\n", a);
   printf("全局变量区:%p\n", &b);
   printf("字符常量区:%p\n", str);
   printf("代码区:%p\n", main);            
   return 0;
 }

在这里插入图片描述
可以看到,确实如我们所说,大概是这么一个分配的情况。

3). 环境变量和命令行参数的存储

环境变量和命令行参数,存储在栈区之上:
在这里插入图片描述

2. 进程地址空间

1). 解决遗留问题

接下来我们会用我在一篇博客中遗留的问题:Linux系统调用函数fork展开讨论,用它引入并讲述,我们所理解的内存分区和内存是一个东西吗?以及什么是进程地址空间。
我们在使用fork的过程中,我们发现创建子进程之后,一个函数返回了两个值,我们知道这是fork的效果,在return的时候子进程就已经被创建好,开始运行了。但是返回之后,我们发现一个变量它居然有两个值,并且我们在父子进程中分别打印这个变量地址,发现它们的地址竟然一样,这个我们无法理解。

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

int main()
{

  printf("我是一个进程,我的id是:%d,我的父进程的id是:%d\n", getpid(), getppid());
  pid_t id = fork();
  if(id < 0)
  {
    return 1;
  }
  else if(id > 0)
  {
    printf("我是一个父进程,我的id是:%d,我的父进程的id是:%d,%d:%p\n", getpid(), getppid(), id, &id);
  }
  else
  {
    printf("我是一个子进程,我的id是:%d,我的父进程的id是:%d,%d:%p\n", getpid(), getppid(), id, &id);               
  }
  return 0;
}

在这里插入图片描述
同一个地址,会存储两个变量吗?这显然是不可能的,硬件上就不支持这么做,所以我们得出一个结论:我们谈论的堆区、栈区它不是电脑当中的内存,那它是什么呢?我们在这里给出答案,它叫进程地址空间也叫线性空间,里面的地址都是虚拟地址。
那进程地址空间和内存有什么关系呢?这里一张图简略说明一下上面疑问的答案:
在这里插入图片描述

首先,父进程开始运行之后,调用函数fork形成子进程,然后在return的时候子进程就已经存在了,至于父子进程谁先运行,这是调度的问题受多方面影响。每个进程都有自己的进程地址空间,而进程地址空间由进程pcb中的一个指针指向。
我们假设是父进程先运行,当程序执行到创建变量id并给id初始化的时候,id的虚拟地址就被创建好,并且内存中的地址对应的内存也已经开辟好,然后虚拟内存的地址和物理内存的地址就像上图一样被放在一个表里面,这个表叫做页表,虚拟地址至此和物理地址形成一种映射关系,然后fork会给父进程返回子进程的id,那么此时物理内存中存储的应当是子进程id。
然后就是子进程执行到创建变量id的这一步,子进程在被创建好的时候,它也形成了自己的pcb,以及进程地址空间,子进程会继承父进程的一些东西,这其中就有父进程的页表。所以子进程此时也有着和父进程相同的id的虚拟地址以及存储id变量物理内存的地址,但是现在fork又返回了一个值,子进程要将写如变量id中,但是同时有两个进程指向者着id的物理内存,说明不能执行修改,那就会发生写时拷贝,操作系统会再开辟一块内存把子进程fork的返回值放在这个内存中,并且把这块物理内存的地址放在紫禁城的页表中覆盖原来页表中的地址。

在这个过程中,我们发现两个进程的页表中id变量的虚拟地址并没有发生改变,改变的是物理内存的地址,所以当我们打印变量id的地址的时候,打印的地址为什么会一样了。

2). 什么是地址空间?

a. 什么是地址空间?

讲过上面的的讲述,肯定是不能讲清楚地址空间是什么的,所以我现在谈论进程地址空间是什么。
我们的电脑假设有16G的内存,而我们使用电脑的时候,肯定是有许多进程,我们也说过每个进程都有自己的进程地址空间,进程需要被管理来,那么每个进程的进程地址空间也需要被管理器来,如何管理?先描述在组织
描述:我们看到进程地址空间它的主要特点就是里面划分着许多区域,那这些区域是怎么被划分的呢?就像我们小时候两个人做同桌的时候发生矛盾就会在桌子上画线以表明自己的区域一样,进程地址空间区域的划分也是如此,它也会用若干变量来划分区域,比如stack_strat,stack_end,heap_start,heap_end这样的变量来将进程地址空间划分开来,那么管理进程地址空间,就只需要管理由这几个变量组成的结构体就可以来管理进程地址空间了,而在Linux中这个结构体叫mm_struct。
在这里插入图片描述
而组织这些结构体就是用进程pcb中的一个指针来指向它。
在这里插入图片描述
所以进程地址空间也只是一个结构体而已,在32位系统下,结构体变量的范围是从00000000到ffffffff。进程中的使用内存的行为最终都会靠页表映射到物理内存中。
,当我们在进程中创建一个变量的时候都会先有一个虚拟地址映射到物理地址这个映射关系由页表来存储,物理地址存储着变量的内容。

b. 地址空间结构体的进一步理解

我们在进行开发的过程中,肯定会遇到需要内存中一块特殊的区域来做一些特殊的任务,这就需要更加细致的对地址空间的划分,而这个划分在mm_struct中也有:
在这里插入图片描述
在这里插入图片描述

这个变量可以对地址空间有着更细致的划分
在这里插入图片描述

3). 页表

每个进程又会有自己的页表,页表中存储着虚拟地址与物理地址的映射关系,除此之外,每个页表的映射关系之后还有两个存储着其他的东西。

a. 访问权限

我们知道我们代码和字符常量区的内容是不可被修改的,原因就是因为页表中的访问权限决定的。
在这里插入图片描述
它可能会用10的方式来判断你能否对这个内存中的内容进行修改。

b. 地址对应内容有无

现在的有的游戏它的内存占用很大,有的甚至比我们的电脑内存还要大,那电脑怎么能够运行起来这个程序呢?它其实是将先将程序的一部分运行起来,然后等到需要下一部分内容的时候在释放这一部分,加载下一部分内容到内存,以实现运行整个游戏,那操作系统怎么知道,我需要的内容不在内存中呢?这就是利用了页表拥有的第四个数据,判断当前物理内存是否被分配和有无内容:
在这里插入图片描述

它大概使用01的这种办法来表示当前物理内存有无数据,如果没有操作系统就需要把下一步部分从磁盘加载到内存中,而加载的这个行为叫做缺页中断。很明显这已经是内存管理了。

a. 进程切换与页表

我们暂且理解为进程pcb中也会有一个指针来指向自己的页表,然后当自己的进程被调度的时候,上一个进程被切走,我们把硬件上下文在放到寄存器中,而这个过程中有一个寄存器CR3这个寄存器中就会存储页表的地址,这个地址自然也是物理地址。

3. 为什么要有地址空间

1.让进程以统一的视角看待内存,任意一个进程可以通过地址空间+页表的方式
访问内存将乱序的内存数据变成有序
2.使用进程地址空间可以有效防止进程访问内存的安全检查
通过页表的内存访问权限可以达到目的
3.通过页表让进程映射到物理内存中,可以保证进程的独立性以及进程管理和内
存管理之间互不相干

4. 再次理解写时拷贝

我们知道使用fork之后,父子进程共享代码数据,原本进程运行在栈区堆区等的数据是可以修改的,页表的权限是可读可写的,而在使用fork创建子进程的时候,父进程的页表中的数据权限都被改成了只读,子进程会拷贝父进程的页表,当然子进程的页表也会是只读的,很明显我们是不知道此时,父子进程的页表都变成了只读的,所以当我们要对数据进行修改时,会发生报错,而这个错误会由操作系统来判别这到底是怎么回事,而此时操作系统会有两种判断一个是真的出错了,一个就是不是出错,而是触发了我们进行重新申请内存拷贝内容的机制,而这才是操作系统内存的写时拷贝。之所以为什么申请新空间后是先拷贝原数据再修改,而不是直接申请新空间之后直接填写新的数据,是因为,有的数据可能具有整体性,它不是单一的。

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

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

相关文章

Arcmap制图绘制显著性区域

类似于下图这种&#xff0c;为分析结果添加显著性区域&#xff0c;该如何实现呢&#xff1f; 实现方式多种多样&#xff0c;比如&#xff1a; 1、代码。Python、R、Matlab都有实现方式&#xff0c;但是绘制一幅优美的地图&#xff0c;用代码绘制&#xff0c;需要添加很多控制语…

进阶JAVA篇-了解 File 文件的常用API

&#x1f525;博客主页&#xff1a; 小扳_-CSDN博客 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 1.0 File 文件的说明 2.0 如何创建 File 类的对象 2.1 需要注意的事项 3.0 File 类的常用 API 3.1 如何创建文件或文件夹 3.2 如何查询文件和文件夹的信息 3.3 如何删除文件和…

微信定时发圈,快人一步不落索

现在的社交媒体运营已经成为了私域流量获取的重要手段&#xff0c;而微信作为最大的社交平台之一&#xff0c;更是吸引了众多使用者。但是&#xff0c;你是否曾经感叹过每天手动发朋友圈的繁琐&#xff1f;是否希望能够事先设置好定时发送的功能&#xff0c;让你的朋友圈自动更…

检查Python中的变量是否为字符串

我们将通过示例介绍两种不同的方法来检查 Python 中的变量是否为字符串。 检查Python中的变量是否为字符串 在 Python 中&#xff0c;每个变量都有一个数据类型。 数据类型表示变量内部存储的数据类型。 数据类型是编程语言最重要的特征&#xff0c;用于区分我们可以存储的不…

本科生学深度学习-Attention机制

很久没有写了,今天想学习下Bert ,发现其中一个很重要的机制是self-Attention,在查self-attention的时候又回归到Attention机制,记录下。 1、Attention 是什么 Attention(注意力)机制核心逻辑就是「从关注全部到关注重点」。 attention机制是模仿人类注意力而提出的一种…

Mybatis-Plus通用枚举功能 [MyBatis-Plus系列] - 第493篇

历史文章&#xff08;文章累计490&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 S…

AS/400-对象管理-01

对象管理 对象对象构图 AS/400中的库命令Display Library List (DSPLIBL)Create Library (CRTLIB)Display library (DSPLIB)Edit Library List (EDTLIBL) Source physical file 物理文件创建物理文件的命令 &#xff1a; CRTSRCPF 物理文件查看所有物理文件的源文件创建源文件…

2023年中国冷风机分类、销量及市场规模分析[图]

冷风机通常是指一种设备&#xff0c;用于通过冷却空气来调节室内或工业环境的温度。这些设备通过循环空气并通过冷却元件&#xff08;如冷却盘或冷凝器&#xff09;来降低空气的温度&#xff0c;从而实现温度控制。冷风机在家庭、商业和工业领域都有广泛的应用&#xff0c;可以…

geoserver去除tif影像黑色的背景的方法

geoserver加载某些tif文件的时候,tif文件本身有黑色的背景,怎么去掉呢? 只要在geoserver中设置就行。 处理方法: 1.新建数据源时要选择ImageMosaic数据源 2,设置"Output Transparent Color" 设置"Output Transparent Color"为黑色(000000),在…

Postgresql在jdbc处理bit字段的解决方案

问题&#xff1a; bit如果长度为1&#xff0c;则会默认为布尔型&#xff08;1-true 0-false&#xff09;&#xff1b; bit如果长度大于1&#xff0c;则会默认为bit类型&#xff0c;但是代码中以前常用的两种set方式&#xff0c;会报错 第一种方式&#xff1a; ps.setObject(i1,…

【工具】FreePic2PDF+PdgCntEditor|PDF批量添加书签(Windows)

这俩软件都不大&#xff0c;比较便携。 FreePic2PDF&#xff1a; 我下载的来源&#xff1a;https://www.52pojie.cn/thread-1317140-1-1.html&#xff08;包含下载链接https://www.lanzoui.com/it4x6j4hbvc&#xff09;下载的结果&#xff1a;https://pan.baidu.com/s/1r8n5G42…

win 下安装 nvm 的使用与配置

nvm 全名 node.js version management&#xff0c;是一个 nodejs 的版本管理工具。通过它可以安装和切换不同版本的 nodejs。 注&#xff1a;如果已经安装了 nodejs 需先卸载后再安装 nvm 为了确保 nodejs 已彻底删除&#xff0c;可以看看安装目录中是否有 node 文件夹&#x…

解密人工智能:决策树 | 随机森林 | 朴素贝叶斯

文章目录 一、机器学习算法简介1.1 机器学习算法包含的两个步骤1.2 机器学习算法的分类 二、决策树2.1 优点2.2 缺点 三、随机森林四、Naive Bayes&#xff08;朴素贝叶斯&#xff09;五、结语 一、机器学习算法简介 机器学习算法是一种基于数据和经验的算法&#xff0c;通过对…

js的小题

//闭包实例代码 function fn1() {let a 1;function fn2() {a;console.log(a);}console.log(a,a) } fn1(); 执行结果: 1 a 现在思考怎么调用里面的fn2函数呢? 答案是: //闭包实例代码 function fn1() {let a 1;function fn2() {a;console.log(a);}console.log(a,a)return f…

安卓核心板_天玑700、天玑720、天玑900_5G模块规格参数

5G安卓核心板是采用新一代蜂窝移动通信技术的重要设备。它支持万物互联、生活云端化和智能交互的特性。5G技术使得各类智能硬件始终处于联网状态&#xff0c;而物联网则成为5G发展的主要动力。物联网通过传感器、无线网络和射频识别等技术&#xff0c;实现了物体之间的互联。而…

正点原子嵌入式linux驱动开发——Linux RTC驱动

RTC也就是实时时钟&#xff0c;用于记录当前系统时间&#xff0c;对于Linux系统而言时间是非常重要的&#xff0c;就和使用Windows电脑或手机查看时间一样&#xff0c;在使用Linux设备的时候也需要查看时间。本章就来学习一下如何编写Linux下的RTC驱动程序。 Linux内核RTC驱动…

算法笔记【8】-合并排序算法

文章目录 一、前言二、合并排序算法基本原理三、实现步骤四、优缺点分析 一、前言 合并排序算法通过采用分治策略和递归思想&#xff0c;实现了高效、稳定的排序功能。本文将深入探讨合并排序算法的原理、实现步骤&#xff0c;并讨论其优缺点。 二、合并排序算法基本原理 合…

一文看懂完整的研究生生活规划

很多人在刚从本科步入研究生生活的时候,总是对于自己三年的研究生生活没有清晰的规划,总是在各种浪费时间,没有拿到想要的东西,也没有学到想学的东西,亦或是没有找到理想的工作,最后草草的毕业。这个时候我们就应该对于自己的研究生生活有个清晰的规划,帮助我们不留遗憾…

人大与加拿大女王大学金融管理硕士项目:开启国际视野,成就金融领袖

生活中&#xff0c;我们总会遇到各种各样的困难和挑战。有时候&#xff0c;我们会感到沮丧、迷茫甚至绝望。但是&#xff0c;正是这些困难和挑战&#xff0c;让我们变得更加坚强、勇敢和成熟。在这个职场竞争愈发激烈的时代&#xff0c;不断地充实自己是非常重要的。如果你从事…

IP代理被低估的作用,你知道吗?

IP说简单不简单&#xff0c;说复杂也不复杂&#xff0c;打个比方&#xff0c;IP就好比我们上网的一个门牌号&#xff0c;每家每户都会有一个门牌号&#xff0c;而且是唯一的地址。而代理IP&#xff08;代理服务器&#xff09;是一个位于中间的服务器&#xff0c;充当客户端和目…