【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.mzph.cn/news/139285.shtml

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

相关文章

druid连接池异常GetConnectionTimeoutException(原创)

问题描述 有天&#xff0c;测试同学突然反馈系统页面查询缓慢&#xff0c;影响使用&#xff0c;我查了日志报&#xff1a; druid 连接池异常 GetConnectionTimeoutException wait millis 9120, active 20, maxActive 20 creating 0 结论先行 经一系列排查&#xff0c;得出数…

面试10000次依然会问的【ThreadLocal】,你还不会?

ThreadLocal简介与基本概念 ThreadLocal&#xff0c;即线程局部变量&#xff0c;是Java语言中用于实现线程数据隔离的一个重要类。这种机制允许在多线程环境中&#xff0c;每个线程都有自己的变量副本&#xff0c;从而使得每个线程都可以独立地改变自己的副本&#xff0c;而不…

css实现div倾斜效果

效果如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head> <style> *{margin:0;padding: 0;} .box1{margin:30px 100px;width:100px;height:200px;background:blueviolet;} …

【信息安全原理】——传输层安全(学习笔记)

&#x1f4d6; 前言&#xff1a;为保证网络应用&#xff0c;特别是应用广泛的Web应用数据传输的安全性&#xff08;机密性、完整性和真实性&#xff09;&#xff0c;可以在多个网络层次上采取安全措施。本篇主要介绍传输层提供应用数据安全传输服务的协议&#xff0c;包括&…

使用 HTTP Client 轻松进行 API 测试

在开发过程中&#xff0c;我们经常需要测试 API 接口以确保其正常工作。JetBrains 的集成开发环境&#xff08;IDE&#xff09;如 CLion、IntelliJ IDEA、PyCharm 等&#xff0c;默认内置了 HTTP Client 插件&#xff0c;可以方便地进行API测试。本文将介绍如何使用HTTP Client…

2023年【汽车驾驶员(高级)】找解析及汽车驾驶员(高级)复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车驾驶员&#xff08;高级&#xff09;找解析是安全生产模拟考试一点通总题库中生成的一套汽车驾驶员&#xff08;高级&#xff09;复审考试&#xff0c;安全生产模拟考试一点通上汽车驾驶员&#xff08;高级&#…

Xilinx FPGA平台DDR3设计详解(一):DDR SDRAM系统框架

DDR SDRAM&#xff08;双倍速率同步动态随机存储器&#xff09;是一种内存技术&#xff0c;它可以在时钟信号的上升沿和下降沿都传输数据&#xff0c;从而提高数据传输的速率。DDR SDRAM已经发展了多代&#xff0c;包括DDR、DDR2、DDR3、DDR4和DDR5&#xff0c;每一代都有不同的…

长虹智能电视使用123

1、开机 在接通电源的情况下&#xff0c;长虹智能电视开机有两种方式。 方式1&#xff1a; 按电视右下角开机按钮 方式2&#xff1a; 按电视遥控器开机按钮 长虹智能电视开机后会进入其操作系统&#xff08;安卓&#xff09;。 屏幕左右双箭头图表&#xff0c;手指点击会…

SpringMvc 常见面试题

1、SpringMvc概述 1.1、什么是Spring MVC &#xff1f;简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&am…

短视频矩阵seo系统源码搭建----技术定制化开发

一、需要遵循一下技术开发步骤&#xff1a; 1. 确定需求和功能&#xff1a;明确系统的主要目标和需要实现的功能&#xff0c;包括关键词研究、短视频制作、外链建设、数据分析、账号设置优化等方面。 2. 设计系统架构&#xff1a;根据需求和功能确定系统的架构&#xff0c;包…

模板初阶 C++

目录 泛型编程 函数模板 概念 格式 原理 函数模板的实例化 类模板 格式 类模板的实例化 泛型编程 当我们要实现一个交换函数&#xff0c;我们可以利用函数重载实现&#xff0c;但是有几个不好的地方 1.函数重载仅仅是类型不同&#xff0c;代码复用率较低&#xff0c;只…

小米智能电视投屏方法

小米智能电视也提供了投屏功能。 使用遥控器&#xff0c;在应用中找到它&#xff0c;点击进入。 小米电视支持windows笔记本&#xff0c;macbook笔记本&#xff0c;iphone手机&#xff0c;安卓手机投屏。 windows笔记本投屏 在投屏应用中找到windows投屏&#xff0c;选中开…

论文阅读[121]使用CAE+XGBoost从荧光光谱中检测和识别饮用水中的有机污染物

【论文基本信息】 标题&#xff1a;Detection and Identification of Organic Pollutants in Drinking Water from Fluorescence Spectra Based on Deep Learning Using Convolutional Autoencoder 标题译名&#xff1a;基于使用卷积自动编码器的深度学习&#xff0c;从荧光光谱…

用volta管理不同项目node版本

1 什么是volta volta是一个node.js的版本管理工具&#xff0c;你的电脑上安装了很多个node版本&#xff0c;volta可以让你在不同的项目中使用不同版本的node.js,并且可以切换node.js版本 Volta会自动将安装的Node.js版本与该项目绑定&#xff0c;使得您在该项目中执行 node、np…

Flutter 实战:构建跨平台应用

文章目录 一、简介二、开发环境搭建三、实战案例&#xff1a;开发一个简单的天气应用1. 项目创建2. 界面设计3. 数据获取4. 实现数据获取和处理5. 界面展示6. 添加动态效果和交互7. 添加网络错误处理8. 添加刷新功能9. 添加定位功能10. 添加通知功能11. 添加数据持久化功能 《F…

Springboot+vue的企业资产管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的企业资产管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的企业资产管理系统&#xff0c;采用M&#xff08;model&a…

python实现全向轮EKF_SLAM

python实现全向轮EKF_SLAM 代码地址及效果运动预测观测修正参考算法 代码地址及效果 代码地址 运动预测 简化控制量 u t u_t ut​ 分别定义为 v x Δ t v_x \Delta t vx​Δt&#xff0c; v y Δ t v_y \Delta t vy​Δt&#xff0c;和 ω z Δ t \omega_z \Delta t ωz…

asp.net 在线音乐网站系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 在线音乐网站系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言 开发 asp.net 在线音乐网站系统1 应用…

EXCEL中将UTC时间戳转为日期格式(精确到秒)

UTC时间戳的格式通常是一个整数&#xff0c;表示从1970年1月1日00:00:00 UTC到当前时间的总秒数。它可以以秒或毫秒为单位表示。例如&#xff0c;如果当前时间是2023年3月17日 12:34:56 UTC&#xff0c;则对应的UTC时间戳为1679839496&#xff08;以秒为单位&#xff09;或1679…

【树与二叉树的转换,哈夫曼树的基本概念】

文章目录 树与二叉树的转换将二叉树转化为树森林与二叉树的转化&#xff08;二叉树与多棵树之间的关系&#xff09;二叉树转换为森林森林的先序遍历1&#xff09;先序遍历2&#xff09;后序遍历 哈夫曼树的基本概念森林转换成二叉树&#xff08;二叉树与多棵树的关系&#xff0…