Linux学习之路 -- 进程篇 -- 进程地址空间

news2024/12/27 11:15:28

目录

一、背景介绍

二、进程地址空间

1.看现象

2.先简单描述一下地址空间(地址空间全在操作系统的内部)

3.地址空间详细一点的描述

4.进程地址空间里面的内容(部分)

三、进程地址空间的转换机制

1.页表

2.进程地址空间和页表存在的意义

<1>将物理内存从无序变为有序

<2>将进程管理和内存管理进行解耦合

<3>页表+地址空间是保护物理内存的重要手段

四、解释一些问题


一、背景介绍

在了解完环境变量后,我们再来谈一谈进程地址空间这个概念

在正式介绍这个概念之前,我们先看一张图

在学习c语言阶段时,这张内存图想必大家都不陌生,接下的话题都主要围绕这张图展开。在此之前我们会验证一些东西。其次我们主要讲解内容时用户空间,不是内核空间,内核空间涉及内容过多,且复杂,暂不谈论。

这里我们先验证一下这张图的合理性,看看每个区域是否如上图一样分布。先用一段代码来实现

#include<stdio.h>
#include<stdlib.h>
int num;
int nums = 100;

int main()
{
    const char* str = "hello";
    printf("代码区:%p\n",main);
    printf("常量区:%p\n",str);
    printf("初始化区域:%p\n",&nums);
    printf("未初始化区域:%p\n",&num);
    char* heap = (char*)malloc(10);
    printf("堆区区域:%p\n",heap);
    printf("栈区区域:%p\n",&heap);
    return 0;
}

运行结果:

这里我们看见,这几个区依次增长,其中我们可以发现,堆区和栈区中间存在大量的镂空。这些现象符合上图的规则。验证完它们的分布,我们再验证一下堆区和栈区的增长方向问题。下面我们再用一段代码来对这个问题进行验证。

int main()
{
    char* heap1 = (char*)malloc(1);
    char* heap2 = (char*)malloc(1);
    char* heap3 = (char*)malloc(1);
    printf("heap1 addr:%p\n",heap1);
    printf("heap2 addr:%p\n",heap2);
    printf("heap3 addr:%p\n",heap3);
    
    printf("stack head1 addr: %p\n",&heap1);
    printf("stack head2 addr: %p\n",&heap2);
    printf("stack head3 addr: %p\n",&heap3);
}

这里我们发现堆区地址依次增长,栈区地址依次减小

我们再对命令行参数和环境变量进行验证

int main(int argc, char* argv[],char* env[])
{

   for(int i = 0; argv[i]; i++)
   {
        printf("&argv[%d] = %p\n",i,argv + i);
   }
      for(int i = 0; env[i]; i++)
   {
        printf("&env[%d] = %p\n",i,env+i);
   }
}

运行结果

我们可以看见,这里命令行参数地址比环境变量地址小,且环境变量的地址是向上增长的。

无论是命令行参数,还是命令含参数表里面的内容都是在栈的上面。

二、进程地址空间

1.看现象

在正式介绍进程地址空间之前,我们先来看一个奇怪的现象

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int num;
int main(int argc, char* argv[],char* env[])
{
    int ret = 0;
    pid_t id = fork();
    if(id == 0)
    {
        while(1)
        {
            printf("child, pid:%d, ppid: %d,num: %d,&num: %p\n",getpid(),getppid(),num,&num);
            ret++;
            if(ret == 7)
            {
                num = 100;
                printf("child change nums\n");
            }
            sleep(2);
        }
    }
    else
    {
        while(1)
        {
            printf("father, pid:%d, ppid: %d,num: %d,&num: %p\n",getpid(),getppid(),num,&num);
            sleep(2);
        }
    }
}

运行结果:

一开始的运行结果还算是正常的,父子进程有相同的代码段和数据段。到后面num改变后,num有两个值也还可以理解,发生了写时拷贝,但是为什么不同的num值却有相同的地址?

根据上面的现象我们可以推出一个结论,我们打印出来的地址肯定不是存东西的地址(物理地址)。一个地址是不可能存两个值的,所以这个打印出来的地址就是虚拟地址/线性地址。我们现在用的地址全部不是物理地址。

上面我们第一张图,也不是物理空间分布图,而叫进程地址空间,每一个进程都有一个这样的空间。

2.先简单描述一下地址空间(地址空间全在操作系统的内部)

这里我们使用虚拟地址时,需要通过一张表,讲虚拟地址上一一映射到物理内存上,所以我们在使用虚拟地址时,是要在表上查找对应的物理内存。这也就能解释前面的现象。子进程拷贝父进程的进程地址空间和对应的映射表,所以父子继承就可以指向一段代码和数据。如果对子进程的一个变量进行修改,系统会重新开一段物理内存,也就是写时拷贝。这里会改变映射关系,但是不会修改虚拟地址,也就是说父子进程的虚拟地址是没变的,所以这里也就能解释上面的现象了,同时,这也是我们前面通过fork返回值进行分流的原因。

3.地址空间详细一点的描述

这个地址空间全程叫进程地址空间,每一个进程,都会存在一个进程地址空间,在32位机器下,这个地址空间的大小是[0,4GB]。我们都知道,普通机器上的物理内存也就是8G或4G,如果每一个进程地址空间上的所有虚拟地址都得到映射,肯定是不现实的。所以进程地址空间的大小其实就是操作系统对进程画的一张“饼”,让进程误以为自己拥有那么多的内存空间(实际上一个进程也用不到那么多的空间)。当然,操作系统也需要对这些”饼“进行管理,以防真出现内存不够用的情况。操作系统该如何管理这些空间呢?

根据"先描述,再组织",我们可以推出,我们可以用一个数据结构对其进行描述,具体到进程,就是特定数据结构的对象。

以上图为例,我们可以用上图所示结构对进程地址空间进行管理,对于进程地址空间的管理就变成了对链表的管理,每个进程PCB里面又存在属于自己的进程地址空间指针(也就是”struct 进程地址空间* “的指针,在内核中,这个描述进程地址空间的结构叫" struct mm_struct ")指向自己的地址空间,进程就可以通过这个指针找到自己的进程地址空间。

本质上来说,进程地址空间就是一个数据结构。

4.进程地址空间里面的内容(部分)

前面说了一大堆,我们大概的了解了一下进程地址空间是啥,接下来,我将介绍一下,进程地址空间里面存了啥东西,也就是进程地址空间里面的属性到底是啥?

要解释这个问题,其实我们可以通过本文的第一张图来解释,

我们可以看到,进程地址空间被分成了很多块,进程地址空间的主要属性也就是对区块的管理信息,我们可以把这个东西形象地比喻成对地盘划分,而进程地址空间的属性就像是对各个国家国界的标注信息,比如相邻国家的国界线是从哪里到哪里。所以实质上进程地址空间的属性就是每个区域的范围,从一段区域的开始,到一段区域的结束。而进程地址空间实际上是一段有界区间,所以我们可以通过下图方式,表示每个区域。

我们可以使用上图一样的方法就能把一段区间用表示出来,不过需要注意的是,在系统中,表示区间类型的是无符号长整型,这里为了方便演示,所以使用int类型。依次类推,我们可以把所有的区间表示出来,这些区间范围就是进程地址空间的主要属性。当然,肯定还有其他的属性,只不过这里不做介绍。

mm_struct参数(看看就行),下面两张图均取自《内核设计与实现》

我们对进程地址空间进行区域划分的本质是让区域内的地址都可以使用。

三、进程地址空间的转换机制

1.页表

进程地址空间,本质是线性/虚拟地址,它是不能存东西的,所以我们必需通过映射来实现对虚拟地址的转换,从而获取到对应的物理内存中的真实数据。这里我们就需要通过名为页表的映射表来实现上述的功能。下面用一张图简单描述一下系统通过页表找到物理内存地址的过程。

cpu从进程地址空间取得虚拟地址,通过CR3这个寄存器(这个寄存器存放的是页表的物理内存地址)可以找到页表(页表存在于物理地址中,每个进程也有对应的页表),通过页表上的映射关系,就可以找到虚拟地址所对应的数据。这些过程,包括虚拟地址的转换,查找,增加等等,都是由CPU上的MMU(内存管理单元) 完成的。

2.进程地址空间和页表存在的意义

<1>将物理内存从无序变为有序

通常,我们物理内存可能是不连续的,系统有时候也提供不了一整块连续的物理内存,所以系统会用一些零散的物理内存存放信息,通过页表,我们能把这些零散的物理地址存放的信息连到一起,让无序的物理内存信息变为有序,这样进程就能以统一视角看待内存。

<2>将进程管理和内存管理进行解耦合

因为有页表的存在,所以我们可以把物理内存的管理操作独立于进程管理,也就是说,我物理内存怎么分配,进程是不需要关心的,跟你进程也没有半毛钱关系。操作系统管物理内存时,就不需要考虑进程相关信息,而系统进行进程管理时,也不用关心物理内存的分配和使用。这样就降低了进程管理和内存管理的耦合性(耦合性:简单看成关联度)

<3>页表+地址空间是保护物理内存的重要手段

当用户进行非法的用户访问时,页表没法将虚拟地址转化成合理的物理内存地址,此时系统就会拒绝用户访问物理内存,这也就是平时在写C语言时,我们访问野指针时,系统并没有崩溃的原因。

四、解释一些问题

在上述知识的支撑下,我们可以解释一些问题

1.new / malloc的内存开辟相关问题

首先我们要思考一个问题,我们申请了内存后,我们会直接使用吗?显然是不一定的。

对于系统来说,系统并不知道用户开辟内存后是否会直接使用这些内存,如果用户没有使用这些内存,系统也得不到这些内存的使用权,这就会造成一定浪费。但这对于以操作系统来说是不可接受的,操作系统是必需追求高效的,所以为了保证系统的高效性,系统不会先给用户在物理内存上开空间,当用户malloc和new的时候是从进程地址空间中申请虚拟地址,此时的虚拟地址没有建立对应的映射关系。只有当用户真的要使用物理内存空间时(这个过程中还要检查请求是否合理),系统才会开辟对应的物理内存。如果用户真要使用物理内存时,系统发现该用户提供的虚拟地址没有对应的映射关系,系统中断用户的下一步操作,去建立新的映射关系并申请物理内存,这个过程称为缺页中断(这里不详谈),执行完该操作后,继续用户的下一步操作。

上述这样操作的好处什么呢?

<1>充分的保证了内存的使用率,不会导致空转

<2>提升了new和malloc的速度

我们在申请内存时,只需要从进程地址空间中申请虚拟地址即可,真要使用物理内存时,再进行物理内存的申请和页表映射关系的构建,这个过程并不会降低操作系统的速度,因为这些工作本来就是要做,只不过分成了两个阶段去执行罢了,时间成本上并无差异。


如果向具体了解进程地址空间的相关内容和操作,参看《Linux内核设计与实现》一书,本文仅仅是简单介绍了进程地址空间。

文中如有不对之处,还望各位大佬指正,谢谢!!!

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

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

相关文章

牛客NC162 二叉树中和为某一值的路径(三)【中等 dfs C++、Java、Go、PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/965fef32cae14a17a8e86c76ffe3131f 思路 既然要找所有路径上节点和等于目标值的路径个数&#xff0c;那我们肯定先找这样的路径起点啊&#xff0c; 但是我们不知道起点究竟在哪里&#xff0c; 而且任意节点都有…

Navicat 干货 | 掌握 PostgreSQL 规则语法

PostgreSQL 规则提供了一种强大的机制&#xff0c;控制查询执行并在数据库内部实施数据操作。理解规则的语法和用法对于有效利用其功能至关重要。在上周的文章中&#xff0c;我们探讨了 PostgreSQL 规则的工作原理及其与触发器的区别。今天的文章将使用免费的 “dvdrental”示例…

笔记本电脑坏了硬盘数据会丢失吗 笔记本电脑坏了如何取出硬盘的资料 数据恢复软件

笔记本电脑对我们真的非常重要了&#xff0c;是实现无纸化办公和学习的重要工具&#xff0c;但是如果笔记本电脑坏了我们存储在电脑里的资料该怎么办&#xff1f;笔记本电脑坏了硬盘数据会丢失吗&#xff1f;相信有许多朋友都会有这样的担忧。本文今天就为大家解决笔记本电脑坏…

3月衣物清洁行业数据概况和趋势分析:总销额环比上涨超60%!

人们日常生活离不开衣物清洁产品&#xff0c;同时随着生活品质得提高和消费者健康意识得增强&#xff0c;对于衣物清洁行业的需求量与日俱增。作为日常必备的消耗品&#xff0c;衣物清洁产品备受消费者关注。借此&#xff0c;衣物清洁行业在3月份表现出稳定的发展态势。 根据鲸…

【软件测试】关于Web自动化测试

文章目录 &#x1f343;前言&#x1f332;如何实现Web自动化&#x1f6a9;安装驱动管理&#x1f6a9;Selenium库的安装 &#x1f333;自动化常用函数&#x1f6a9;元素的定位&#x1f388;cssSelector&#x1f388;xpath &#x1f6a9;操作测试对象&#x1f388;点击/提交对象—…

洗地机选哪个牌子好?过来人建议买这四款:性价比高质量好

随着生活节奏的加快&#xff0c;洗地机逐渐成为了人们生活中的清洁小能手&#xff0c;但市面上品牌众多&#xff0c;性能参差不齐&#xff0c;消费者往往难以抉择。在选择洗地机时&#xff0c;我们不仅要考虑其清洁效果&#xff0c;还应着重考察性价比。那么&#xff0c;面对如…

3.AlexNet--CNN经典网络模型详解(pytorch实现)

看博客AlexNet--CNN经典网络模型详解&#xff08;pytorch实现&#xff09;_alex的cnn-CSDN博客&#xff0c;该博客的作者写的很详细&#xff0c;是一个简单的目标分类的代码&#xff0c;可以通过该代码深入了解目标检测的简单框架。在这里不作详细的赘述&#xff0c;如果想更深…

[Meachines][Easy]Devvortex

Main $ nmap -p- 10.10.11.242 --min-rate 1000 # echo 10.10.11.242 devvortex.htb>>/etc/hosts 子域名爆破 $ apt install seclists $ wfuzz -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://devvortex.htb/" -H &…

【Transformer】detr梳理

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 detr detr 1. 引言 论文&#xff1a; https://arxiv.org/pdf/2005.12872v3.pdf 时间&#xff1a; 2020.5.26 作者&#xff1a; Nicolas Carion?, Fra…

陈奂仁联手 The Sandbox 推出“Hamsterz Doodles”人物化身系列

全新人物化身系列结合艺术与实用性 开创元宇宙新篇章 著名亚洲唱作歌手兼香港电影金像奖得主陈奂仁携手 The Sandbox&#xff0c;兴奋地宣布推出新的元宇宙人物化身系列 —— Hamsterz Doodles 仓鼠涂鸦。 陈奂仁在 The Sandbox 推出 Hamsterz Doodles 系列&#xff0c;将艺术与…

智能家居—ESP32开发环境搭建

相关文章 毕业设计——基于ESP32的智能家居系统(语音识别、APP控制) 智能家居—ESP32开发环境搭建 一、下载安装二、验证三、资料获取 一、下载安装 下载安装 vscode 安装插件 创建工程 二、验证 写一个简单的函数来验证一下功能 void setup() {// put your setup c…

类和对象(2)——封装(封装的概念、包、staic)

前言 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主要研究的就是封装特性。何为封装呢&#xff1f;简单来说就是套壳屏蔽细节。 一、什么是封装 1.1 概念 将数据和操作数据的方法进行有机结合&#xff0c;隐藏对象的属性和实现细节&…

【码农圈子】想加免费的程序员微信群的看过来

群名&#xff1a;码农圈子 很多人后台反应&#xff0c;最近有没有免费的微信技术交流社群 。今天特意写一篇文章来创建一些只有程序猿的微信群。&#xff08;广告党慎入&#xff01;&#xff09; 这些微信技术群都是完全免费&#xff0c;后续也不会收取任何费用 。 群规则 …

Paragon NTFS如何手动更新? Paragon NTFS格式化硬盘会损失数据吗?

Paragon NTFS for Mac常被用于实现在Mac上读写NTFS格式硬盘&#xff0c;然而&#xff0c;有时用户可能会遇到软件无法自动更新的情况&#xff0c;需要进行手动更新操作。下面我们来看看Paragon NTFS如何手动更新&#xff0c;Paragon NTFS格式化硬盘会损失数据吗的相关内容。 一…

Python 使用 pip 安装 matplotlib 模块(精华版)

pip 安装 matplotlib 模块 1.使用pip安装matplotlib(五步实现):2.使用下载的matplotlib画图: 1.使用pip安装matplotlib(五步实现): 长话短说&#xff1a;本人下载 matplotlib 花了大概三个半小时屡屡碰壁&#xff0c;险些暴走。为了不让新来的小伙伴走我的弯路&#xff0c;特意…

【matlab 代码的python复现】 Matlab实现的滤波器设计实现与Python 的库函数相同实现Scipy

实现一个IIR滤波器的设计 背景 Matlab 设计的滤波器通常封装过于完整,虽然在DSP中能够实现更多功能的滤波器设计但是很难实现Python端口的实现。 我们以一段原始的生物电信号EEG信号进行处理。 EEG信号 1.信号获取 EEG信号通常通过头皮电极,经过多通道采样芯片采样,将获…

mysql面试题八(SQL语句)

目录 1.SQL 基本组成部分 常用操作示例 创建表 插入数据 查询数据 更新数据 删除数据 创建索引 授予用户权限 2.常见的聚合查询 1. 计数&#xff08;COUNT&#xff09; 2. 求和&#xff08;SUM&#xff09; 3. 平均值&#xff08;AVG&#xff09; 4. 最大值&…

使用FPGA实现超前进位加法器

介绍 前面已经向大家介绍过8位逐位进位加法器了&#xff0c;今天向大家介绍4位超前进位加法器。 对于逐位进位加法器来说&#xff0c;计算任意一位的加法运算时&#xff0c;必须等到低位的加法运算结束送来进位才能运行。这种加法器结构简单&#xff0c;但是运算慢。 对于超…

WSL安装-问题解决

WslRegisterDistribution failed with error: 0x8004032d WslRegisterDistribution failed with error: 0x80080005 Error: 0x80080005 ??????? 解决&#xff1a; 1、 winr输入&#xff1a;optionalfeatures.exe 2、打开这两项

钉钉报警的优势在哪里?如何配置钉钉机器人进行报警信息推送?

一、常见的报警方式 1、短信或者电话报警 这样的报警方式更适合高级别的报警提醒&#xff0c;用于处理紧急情况。出现级别不高而又频繁地发送短信会让人产生排斥感&#xff0c;而且电话或者短信的报警方式也存在一定的成本。 2、邮件报警 邮件报警更适用于工作时的提醒&…