【Linux】初识进程地址空间

news2024/11/22 16:30:13

❤️前言

        大家好!这里是好久没有营业的大懒虫lion,今天要和大家聊的内容是我最近新学习的关于进程地址空间的相关知识。

正文

        当我们使用C/C++语言进行内存管理时,经常会接触到这样的一张图片:

        它常常被我们称作程序地址空间,在我们编写自己的代码时,都是在这样的内存布局的基础上进行思考,我们访问内存中定义的变量,访问内存中存储的代码数据……在我们的眼中,这样的空间布局似乎是真实存在于物理空间中,但是事实真的如此吗?

        首先让我们来看看下面的这一段代码:

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

int g_val = 0;
int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 0;
	}
	else if (id == 0) { //child
		printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	else { //parent
		printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	sleep(1);
	return 0;
}

//输出结果:
//与环境相关,观察现象即可
//parent[2995]: 0 : 0x80497d8
//child[2996]: 0 : 0x80497d8

        我们很容易就可以推断出这段代码的结果:当我们调用fork()函数产生一个子进程,这个子进程以父进程为模版,并且子进程并没有对全局变量进行修改。

        那么让我们将这段代码稍加改动:

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

int g_val = 0;
int main()
{
	pid_t id = fork();
	if (id < 0) {
		perror("fork");
		return 0;
	}
	else if (id == 0) { //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
		g_val = 100;
		printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	else { //parent
		sleep(3);
		printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
	}
	sleep(1);
	return 0;
}

//输出结果:
//与环境相关,观察现象即可
//child[3046]: 100 : 0x80497e8
//parent[3045]: 0 : 0x80497e8

        我们发现在子进程和父进程中,全局变量g_val的值不相同,但是地址却是一样的,这就和我们当初在语言层面上看待内存空间的视角有较大出入了(既然这个变量用的是同一个地址值进行存储,那么为什么在子进程和父进程中这个变量的值却会变得不同呢?)。

        我们以之前的观念无法解释这样的问题,那么我们不妨做出这样的推论:这里的地址并不是真的物理地址,而是操作系统用某种方式将物理地址转化成的虚拟地址,事实也确实是这样,并且我们平时在使用C/C++编写代码时使用的地址也全部不是物理地址,而是操作系统所修饰出的虚拟/线性地址。

引入进程地址空间

        当我们发现了这样的一种现象,单凭现有的知识概念已经无法对虚拟/线性地址有更详细的了解,也不能对这种现象进行更加深入的解释,那么我们便可以引入新的概念——进程地址空间,通过研究进程地址空间,从而初步理解这种现象。

        我们所说的进程地址空间,其实就是上面所说的“程序地址空间”,但是相较来说,前者的描述更加准确。前者则是当我们学习了进程相关知识,在操作系统的层面上看待地址空间的描述方式,而后者是当我们在语言层面上编写代码、看待地址空间的描述方式。

        我们都知道,当我们创建出一个进程,他们都会带有自己的内核数据结构,那么今天我们研究的进程地址空间,也便是进程自带的内核数据结构中的一种。当我们要对它进行管理,就需要对应的指针,对应的指针存于哪里呢?自然是存在于描述Linux进程信息的进程控制块——task_struct中。

        当我们使用语言编写程序时是以统一的视角来看待内存空间,但是每个进程的task_struct不相同,进程地址空间也便是相互独立的,那么独立的每一个进程是如何做到以统一的视角看待内存的呢?

引入页表

        这里我们又必须要提出一个新的内核数据结构——页表,通过页表,我们便可以完成上述的设计,那么上述的问题具体是如何解决的呢?

        页表的主要作用是与对应进程的地址空间相配合,从而建立起线性地址与物理地址之间的映射关系,同时这种映射是由系统内核自己完成的。上述问题的解决方式简单来说就是:我们通过页表在线性地址与物理地址之间加上了一层映射关系,而且这种映射是由操作系统自己分配的,那么独立的每个进程在物理地址中存储的数据在正常情况下便不会发生冲突,这时每个进程便只需要看好自己的地址空间即可。

        于是我们便了解到了线性地址与物理地址之间的相互关系,每个进程需要管理自己进程地址空间中的线性地址,而操作系统通过页表进行从线性地址到物理地址的映射对应操作。

        现在我们可以反到上面去研究和解释父子进程中的全局变量问题。我们上面得出推论:全局变量g_val使用的并不是直接的物理地址,而是虚拟地址(线性地址),那么其中发生了哪些变化,最后的结果又是如何产生的呢?

我们可以画出如下的图像:

        我们从代码进行到fork()后开始分析,父进程创建了一个新的子进程,这个子进程继承了父进程在创建它之前的所有代码操作,也就是地址空间和页表中存储的数据是一致的,也就是这两者目前的代码、变量在物理空间中其实是占用着相同的空间的。这里又新引入一个页表的行为:当子进程对先前的线性地址中的数据进行修改,那么此时就会发生写时拷贝,将另一块物理地址映射给当前的线性地址,但是不会将线性地址的值进行改变(也没有必要)。这样我们便可以理解这段代码的运行结果,这两个进程中的g_val其实不是同一个变量,它们所占的相同线性地址以页表对应映射出的物理地址是不同的,不同的物理地址中存储着不同的数据,于是最后打印出的结果便是相同的线性地址中存储着不同的值。

深入进程地址空间

        进程地址空间究竟是什么?这可以通过我们早期对C语言指针的研究进行解释:以32位机器举例,简单来说就是将内存地址以32根数据总线的01信号进行二进制的编址,32根总线可以表示出2^32个不同的数字,对应着这么多单位的地址(一个单位即为一个字节),他们分别管理着对应的"内存",[0,2^32)也就是地址空间的地址范围。

        那么如何理解进程地址空间中的区域划分呢?

        地址空间中有许多不同的分区,这些区域的划分其实就是在一大块线性地址中划出一些分界线,以此就可以分出小块的具有不同数据和功能的区域。在进程地址空间对应的内核数据结构中其实就存储了许多不同的用于分界的数据,其中包括这个区域的起始地址和结尾地址等。

        当我们需要调整修改地址空间中的区域划分,也就是只要对涉及的各个结构对象中的数据进行修改。

// 各个分区的结构体对象就类似如下结构:
struct area
{
    int start;
    int end;
    // ...
};

深入页表

        进程地址空间中的不同区域往往含有不同的属性,存储着不同性质的数据(只读、读写),这样的情况显然不是简单的区域划分就可以做到的,那么我们现在来探讨操作系统如何做到这点。

        进程地址空间需要页表与物理地址相勾连,那么操作系统便可以在这中间的操作中加以限制,从而进行不同分区数据的管理。

        事实上,页表中除了线性地址和物理地址的映射关系以外,还存有对应线性地址的数据读写权限,这样当进程想要修改某个内存中的数据时便可以进行先一步的识别,如果这一块内存不能修改,那就会进行报错。也就是说物理内存本身其实是没有所谓的分区、读写权限的概念的,所有的这些都是在上层进行设计的,都是由操作系统内核限定和操作的。

使用地址空间和页表的好处

  1. 让进程以统一的视角看待内存。
  2. 使用进程地址空间和页表可以让我们在访问内存的时候增加一个过程,在这个过程中我们可以对访问内存的行为做检查,错误的访问将不会到达物理内存,这样就保护了物理内存。
  3. 有了进程地址空间和页表,我们可以做到将进程管理模块和内存管理模块进行解耦合。

🍀结语

        以上就是最近接触到的关于进程地址空间和页表的一些知识,希望能帮助到阅读此篇博客的读者。

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

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

相关文章

代码都成屎山了,还在用if-else?不如试试我的这套工厂模式+Map+自定义注解+枚举

前言 看到同事的代码&#xff0c;想到多年以前自己刚开始工作的时候写的代码&#xff0c;即使有再多的需求&#xff0c;再多的业务逻辑&#xff0c;都是满屏的if-else解决的&#xff0c;全然忘记什么叫做“面向对象编程”&#xff0c;但是写的多了都忘记了哪里是头&#xff0c;…

OAuth 2.0实现统一认证

OAuth 2.0协议概念&#xff1a; OAuth 是 Open Authorization 的简写。OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAuth 的授权不会使第三方触及到用户的帐号信息&#xff08;如用户名与密码&#xff09;&#xff0c;即第…

MIPI/DSI转eDP新选择CS5523芯片替代LT8911EXB,IT6151

ASL&#xff08;集睿致远&#xff09; CS5523 是一颗 MIPI DSI 输入&#xff0c;DP/eDP 输出转换芯片。MIPI 输入 4 lanes, 每 lane 最大支持 1.5Gbps&#xff0c; DP/eDP 输出最多支持 4 lanes, 每条 lane 最大支持 2.7Gbps。 芯片内部有一个 MCU &#xff0c;自带 flash。 …

计算机网络期末复习-Part2

1、网络应用程序体系结构 &#xff08;1&#xff09;客户端/服务器&#xff08;C/S&#xff09;体系结构&#xff1a; 客户端/服务器&#xff08;C/S&#xff09;应用程序&#xff1a; Web浏览器与Web服务器&#xff1a;当您使用Web浏览器&#xff08;客户端&#xff09;访问…

Vue3 学习笔记

vue3 官网&#xff1a;简介 | Vue.js (vuejs.org) 1. 环境搭建与启动 npm create vuelatest 这一指令将会安装并执行 create-vue&#xff0c;它是 Vue 官方的项目脚手架工具 之后&#xff0c;你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示&#xff1a; ✔ …

Flutter的专属Skia引擎解析+用法原理

Skia是一款跨平台的2D图形库&#xff0c;是Google公司开发的&#xff0c;可以用于开发各种应用程序&#xff0c;如浏览器、游戏、移动应用程序等。Skia引擎的主要特点是速度快、可移植性强、占用的内存少、稳定性佳&#xff0c;适用于多种硬件平台。 Skia的目标是提供快速、高…

Java SE 25居然有8年的Premier Support !

今天偶然间看了一下Oracle官网发布的Java SE路线图&#xff0c;发现Java 25赫然在列&#xff0c;并且Premier Support居然长达8年&#xff01;上一个有这么长支持的版本还是Java 8。Java 17&#xff08;LTS&#xff09;和Java 21&#xff08;LTS&#xff09;算上Extended Suppo…

鸿蒙开发工具DevEco Studio的下载和安装

一、DevEco Studio概述 1、简介 HUAWEI DevEco Studio&#xff08;获取工具请单击链接下载&#xff0c;以下简称DevEco Studio&#xff09;是基于IntelliJ IDEA Community开源版本打造&#xff0c;为运行在HarmonyOS和OpenHarmony系统上的应用和服务&#xff08;以下简称应用…

计算机中丢失mfc140u.dll怎么解决

mfc140u.dll是一个Microsoft Visual C库文件&#xff0c;主要用于MFC&#xff08;Microsoft Foundation Class&#xff09;应用程序的开发。它包含了MFC应用程序所需的一些常用功能&#xff0c;如对话框、窗口、菜单等。当mfc140u.dll丢失时&#xff0c;可能会导致MFC应用程序无…

轻松连接电商平台:百川 Baichuan2-53B模型在无代码开发环境中的应用

连接AI技术与系统&#xff1a;Baichuan2-53B大模型的应用 AI大模型Baichuan2-53B&#xff0c;已经在无代码开发环境中显示出了其强大的能力。它融合了意图理解、信息检索以及强化学习技术&#xff0c;结合有监督微调与人类意图对齐&#xff0c;表现突出。这款大模型可以通过集…

2023-11-07 C语言链接库编译命令

点击 <C 语言编程核心突破> 快速C语言入门 C语言链接库编译命令 前言一、引入库文件, 包括头文件和lib库二、简单示例总结 前言 要解决问题: 一般没有给新手的链接库编译命令学习资料, 然而, 不解决这个问题, 调用库就能折腾到劝退, 我近日回答一个问题, 很简单, 调用…

干货 | 网络安全入门学习笔记

安全是互联网公司的生命&#xff0c;也是每位网民的基本需求。现在越来越多的人对网络安全感兴趣&#xff0c;愿意投奔到网络安全事业之中&#xff0c;这是一个很好的现象。 很多对网络安全感兴趣的朋友&#xff0c;总是在寻找适合0基础的学习资源&#xff0c;今天就给大家分享…

Labview的分支判断

和其他的编程语言一样的。都会有switch,case, if ,else; 再combo box中实现 再后台程序中对应的写上逻辑就好了。

P5906 【模板】回滚莫队不删除莫队

这一题&#xff0c;虽说在洛谷标的是模板题&#xff0c;但可能没有“历史研究”那一题更加模板。 这一题相对于回滚莫队的模板题&#xff0c;可能在回滚的处理上稍微复杂了一点。对于回滚莫队就不多解释了&#xff0c;可以看一下 回滚莫队模板题 这一篇博客&#xff0c;稍微简单…

SpringBoot自动装配 Spring相关 常用设计模式 双亲委派 MongoDB Redis 适配器模式与策略模式

SpringBoot自动装配 阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 Spring相关 阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 常用设计模式 双亲委派 Java虚拟机定义了三个主要的类加载器: 1、启动类加载器 2、扩展类加载器 …

休闲玩具的软文营销策略

休闲玩具行业作为新兴市场&#xff0c;具有广阔的发展前景&#xff0c;生活水平的提高带来消费观念的升级&#xff0c;城市化进程加速导致人们对休闲娱乐的需求持续上涨&#xff0c;玩具作为娱乐性、放松性、互动性的产品受到广大群体喜爱&#xff0c;休闲玩具市场的竞争也愈发…

我是这样发布成绩的

作为一名老师&#xff0c;每当到了期中或者期末考试后&#xff0c;总是有那么些时刻&#xff0c;想要快点把成绩发布出去&#xff0c;让学生知道他们的努力得到了多少回报。但如何发布成绩呢&#xff1f;传统的纸质方式&#xff1f;太慢&#xff01;一个个发短信&#xff1f;太…

广东开放大学:电大搜题助力学子迎考利器

近年来&#xff0c;广东开放大学一直致力于为广大学子提供优质的教育资源和学习服务。作为一所专注于远程教育的学府&#xff0c;广东开放大学不仅拥有雄厚的师资力量和丰富的教育经验&#xff0c;还致力于创新教学手段&#xff0c;为学生提供更便捷、高效的学习体验。在这个信…

【1++的Linux】之线程(三)含生产者消费者模型

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;可重入与线程安全二&#xff0c;死锁三&#xff0c;线程同步什么是线程同步&#xff1f;怎么实现线程同步条件变量 四&#xff0c;生产者与消费者模型1&…

软件测试用例与分类

测试用例与分类 黑盒测试基于需求的设计方法等价类边界值判定表正交表场景设计法错误猜测法 FiddlerPostman测试用例测试分类按测试对象界面测试可靠性测试容错性测试文档测试兼容性测试易用性安装卸载测试安全测试性能测试内存泄漏测试 白盒测试灰盒测试开发阶段单元测试集成测…