【Linux】初识进程地址空间

❤️前言

        大家好!这里是好久没有营业的大懒虫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) { //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else { //parentprintf("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 { //parentsleep(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.rhkb.cn/news/186024.html

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

相关文章

Netty 是如何利用EventLoop实现千万级并发的

经过前面几篇文章的介绍&#xff0c;我们掌握了 Netty 的 5 个核心组件&#xff0c;但是有了这 5 个核心组件 Netty 这个工厂还是无法很好的运转&#xff0c;因为缺少了一个最核心的组件&#xff1a;EventLoop&#xff0c;它 是 Netty 中最最核心的组件&#xff0c;也是 Netty …

使用C++的QT框架实现五子棋

最近有点无聊正好想玩五子棋&#xff0c;那就实现一下这个游戏吧&#xff0c;网上的五子棋逻辑又长又复杂&#xff0c;我这个逻辑还是蛮简单的&#xff0c;展示如下&#xff08;检测函数在最后&#xff09; 这是一个简单的五子棋&#xff0c;今天就了解一下这个游戏的思路&…

机器学习——回归

目录 一、线性回归 1、回归的概念&#xff08;Regression、Prediction&#xff09; 2、符号约定 3、算法流程 4、最小二乘法&#xff08;LSM&#xff09; 二、梯度下降 梯度下降的三种形式 1、批量梯度下降&#xff08;Batch Gradient Descent,BGD&#xff09;&#xff…

【2023.11.6】OpenAI发布会——近期chatgpt被攻击,不能使用

OpenAI发布会 写在最前面发布会内容GPT-4 Turbo 具有 128K 上下文函数调用更新改进了指令遵循和 JSON 模式可重现的输出和对数概率更新了 GPT-3.5 Turbo 助手 API、检索和代码解释器API 中的新模式GPT-4 Turbo 带视觉DALLE 3文字转语音 &#xff08;TTS&#xff09;收听语音样本…

[unity]切换天空盒

序 unity是自带天空盒的&#xff1a; 但有的时候不想用自带的。怎么自定义&#xff1f;如何设置&#xff1f; 官方文档 Unity - Manual: The Lighting window (unity3d.com) 相关窗口的打开方法 天空盒对应的选项 实际操作 从标准材质球到天空盒材质球 新建一个材质球&…

Powerpoint不小心被覆盖?PPT误删文件如何恢复?

PowerPoint不小心删除了&#xff0c;这可能是众多学生和工作人员最头痛的事情了。PPT被覆盖或误删可能意味着几个小时的努力付之东流。那么PPT覆盖的文档要如何救回来呢&#xff1f;小编将会在本篇文章中为大家分享几个解决方案&#xff0c;使PPT文档覆盖还原操作成为可能&…

为什么有的孩子玩着玩着就成了学霸?

毫不夸张地说&#xff0c;几乎所有的父母都想养出聪明宝宝&#xff0c;孩子上学之后能成为学霸就更省心了。 可“聪明”毕竟不能量化&#xff0c;不是说让孩子上几天课就能提升的。很多家长都在促进孩子大脑发育上使足了劲&#xff0c;可到头来却发现是在做“无用功”。 事实…

Linux-命令行命令

注&#xff1a;[]的内容说明是可选的 1.ls ls [-a -l -h] [Linux路径] >如果没有参数&#xff0c;就展示当前工作目录的内容 > -a&#xff1a;all的意思&#xff0c;即列出所有文件&#xff08;包含隐藏文件/文件夹&#xff09; > -l&#xff1a;以列表形式展示内容&…

3、Dockerfile 深入与其他细节

Dockerfile 在 Docker 中创建镜像最常用的方式&#xff0c;就是使用 Dockerfile。Dockerfile 是一个 Docker 镜像 的描述文件&#xff0c;我们可以理解成火箭发射的 A、B、C、D…的步骤。Dockerfile 其内部包含了一 条条的指令&#xff0c;每一条指令构建一层&#xff0c;因此每…

代码随想录算法训练营第16天|104. 二叉树的最大深度111.二叉树的最小深度222.完全二叉树的节点个数

JAVA代码编写 104. 二叉树的最大深度 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; …

鸡尾酒学习——原谅(自制)

1、材料&#xff1a;冰块、君度、蓝橙力娇酒、雪碧、橘子。 2、口感&#xff1a;甜味为主带着一丝丝酸味&#xff0c;喝起来比较清爽&#xff0c;没有一丝酒味的小甜酒。&#xff08;喜欢喝酒的可以多加酒&#xff0c;不喜欢喝酒的可以适量减少酒&#xff09; 3、视觉效果&…

cookie 里面都包含什么属性?

结论先行&#xff1a; Cookie 中除了名称和值外&#xff0c;还有几个比较常见的&#xff0c;例如&#xff1a; Domain 域&#xff1a;指定了 cookie 可以发送到哪些域&#xff0c;只有发送到指定域或其子域的请求才会携带该cookie&#xff1b; Path 路径&#xff1a;指定哪些…

MySQL:锁机制

目录 概述三种层级的锁锁相关的 SQLMyISAM引擎下的锁InnoDB引擎下的锁InnoDB下的表锁和行锁InnoDB下的共享锁和排他锁InnoDB下的意向锁InnoDB下的记录锁&#xff0c;间隙锁&#xff0c;临键锁记录锁&#xff08;Record Locks&#xff09;间隙锁&#xff08;Gap Locks&#xff0…

彻底删除Ubuntu双系统(联想小新2022)

彻底卸载Ubuntu双系统 以里联想小新pro16 i9-12900h为例子 把开机启动项设为默认Windows启动 以联想电脑为例子&#xff0c;关机后一直点击Fn F2进入Bios把windows启动项移到最上面&#xff0c;这样可以开机默认启动windows了删除ubuntu系统分区 使用磁盘管理软件 DiskGeniu…

【手把手教你】将python程序打包成exe可执行文件

1. 安装环境 pip install pyinstaller6.0.02. 打包文件 pyinstaller -D “要启动的文件名“.py比如我的命令就是&#xff1a;pyinstaller -D eval.py 执行完后&#xff0c;会生两个文件夹dist和bulib两个文件和一个xxx.spec文件 3. 删除生成的文件 删除生成的bulid和dist文…

19、Flink 的Table API 和 SQL 中的内置函数及示例(1)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

JAVA 版小程序商城免费搭建 多商家入驻 直播带货 商城系统 B2B2C 商城源码之 B2B2C产品概述

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

4.移位计算,乘除法运算

目录 一. 移位计算 &#xff08;1&#xff09;算数移位 &#xff08;2&#xff09;逻辑移位 &#xff08;3&#xff09;循环移位 二. 乘法运算 &#xff08;1&#xff09;原码的乘法运算 &#xff08;2&#xff09;补码的乘法运算 三. 除法运算 &#xff08;1&#xf…

MySQL -- mysql connect

MySQL – mysql connect 文章目录 MySQL -- mysql connect一、Connector/C 使用1.环境安装2.尝试链接mysql client 二、MySQL接口1.初始化2.链接数据库3.下发mysql命令4.获取执行结果5.关闭mysql链接6.在C语言中连接MySQL 三、MySQL图形化界面推荐 使用C接口库来进行连接 一、…

【无代码】【VR开发】【Unity】【VRTK】4-导入VRTK Tilia Package

【导入VRTK V4】 VRTK的Tilia Package包含了一整套空间开发方案。导入后你可以在PackageManager中看到它们。 所有的Tilia包都可以在如下页面找到: https://www.vrtk.io/tilia.html Tilia包有一个安装器,可以让你仅仅安装需要的包。包的种类很多,按照适用方向分类。 点击H…