[Linux] 进程地址空间

news2024/11/5 8:43:02

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁


好了,折腾了几天的直流稳压电源失败了,what can I say,果然我就是先天软件圣体,敲代码才是本职工作,现在就Linux和算法交换着学

地址空间的划分

我们在刚学指针时一定会学习地址的概念,相信不少人还记得这张图

我们学习到了了内存空间的分布,从低地址到高地址分别是

  • 代码段:用于存储可执行代码和只读常量

  • 已初始化数据区:存储已经初始化的全局变量

  • 未初始化数据区:存储未初始化的全局变量

  • 栈区:局部变量,自高地址向低地址使用内存

  • 堆区:用于动态内存管理,自低向高使用内存

  • 内核空间:给OS用的,存放环境变量env,命令行参数等等

我们也可以通过代码展示一下

#include<stdio.h>
#include<stdlib.h>
int gval=100;
int ungval;
int main(){
int a=10,b=10,c=10;
int *p=(int*)malloc(sizeof(int));
printf("code:%p\n",main);
printf("init gval:%p\n",&gval);
printf("uninit gval:%p\n",&ungval);
printf("heap:%p\n",p);
printf("stack1:%p\n",&a);
printf("stack2:%p\n",&b);
printf("stack2:%p\n",&c);
}

小知识:

  1. main本身也是一个地址,&main和main是等价的,我们可以用它的地址表示代码段存储的代码地址
  2. 已初始化的全局变量的地址要比未初始化的全局变量地址低

这些东西我相信大家还是记得的,但是接下来的fork,会打破你的认知。


虚拟地址

之前在讨论fork父子进程时谈到了这个:

子进程和父进程共享一份代码,但是子进程的数据是对父进程数据的一份拷贝

即子进程会自己开辟一片空间来存放拷贝的父进程数据,于是我们来份代码验证一下

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
    int a=10;
    pid_t p=fork();
    if(p==0)
    {
        printf("我是子进程,该数据地址是:%p\n",&a);
    }
    else
     printf("我是父进程,该数据地址是:%p\n",&a);
}

 

什么???地址是一样的!

我们知道,如果父子进程的数据地址一样,就意味着他们对该数据的修改会影响到对方,但是进程相互之间应该具备独立性,这显然是不合理的,当初设计OS的佬不可能不知道这点,于是乎,真相只有一个:此地址,非彼地址

我就不卖关子了,事实上我们之前在学c语言c++或者别的语言中,所学习的地址,那些0x123f3f3f什么的所代表的根本不是真正的计算机物理地址空间,而是虚拟地址,要想搞清楚它,必须站在操作系统的层面才可以。

在语言层面接触到的地址,都不是物理地址,而是虚拟地址


页表与fork

页表和map很像,你告诉他一个虚拟地址 ,他会告诉你所对应的真实物理地址在哪里。而不同的进程有不同的页表,所以我们就可以做到,父子进程虚拟地址相同,但是物理地址不同

这时我们就可以回头在思考这句话:

子进程和父进程共享一份代码,但是子进程的数据是对父进程数据的一份拷贝

怎么做到的?怎么就共享或者拷贝了?

其实就是所谓的“共享”就是在子进程创建时直接拷贝父进程的页表,父子的虚拟地址一样,指向的物理地址也一样。

对于代码,因为代码是只读的,不会被修改,所以直接拷贝页表即可,但对于数据还要加上一个写时拷贝

问题一:为什么要拷贝

当父进程修改某个数据,而子进程之后又要用该数据进行计算或判断时,就会被影响到,为了保持的进程之间的独立性,所以必须拷贝。

问题二:为什么不把数据全拷贝

原因一:假如父进程的数据有足足1个G,但是真正会被修改的只有0.1g,剩下0.9G都是只读,那我们的子进程就不需要拷贝这0.9G,拷贝了就是纯纯浪费空间

原因二:虽然子进程的代码和父进程共享,但别忘了子进程不会从头开始执行,而是从fork的下一条语句开始执行,(关于他是怎么做到的,不知道的朋友可以去看看进程状态切换 )

假如父进程一共1000行代码,fork是在第500行,那么前500行代码就不会被子进程使用,这其中所包含的数据也同样不会,那这些数据也自然不用拷贝

现在我们来拿fork返回值分析,当fork进行return返回值时,子进程已经建立好了,父子进程都会分别执行该return语句,这个返回值会被写入到某个变量中,此时OS发现父子进程对数据进行了修改于是进行写时拷贝,给子进程分配了一块物理内存空间

小结

写时拷贝是指当父子进程有一方尝试修改变量时,操作系统会为修改方分配新的物理内存并拷贝数据,以确保独立性。


地址空间的管理

我们刚才也说了对于一个进程,他所使用的的内存被划分为了不同的区域,来存储不同的数据,这些做的目的是为了将同种类的数据放在一起,方便OS管理,但是这个划分功能是如何实现的呢?我们可以想想,计算机一启动就会开启几十甚至上百的进程,每一个进程都需要维护自己的各种空间区域(堆、栈、代码段等等),于是我么就会想到-“先描述,再组织”,是的,这个六字真言又出现了。根据我们学习task_struct的经验,不难猜出,每个进程都有个对应的结构体来描述他的空间使用状况,这个结构体叫做mm_struct。它里面维护的是每个区域的开始地址和结束地址,而task_struct中有个mm_struct指针,可以找到该结构体。

这个结构体对区域的维护很简单,例如我们malloc空间,那么堆区就会变大,那我们只要改一下他的start(向上增长)就可以了。

struct mm_struct {
    long code_start;
    long code_end;
    
    long init_start;
    long init_end;
    
    long uninit_start;
    long uninit_end;
    
    long heap_start;
    long heap_end;
    
    long stack_start;
    long stack_end;
    ...
}

mm_struct的初始化,我们以linux为例,在命令行输入指令readelf -S +文件名

就可以看到该可执行文件中的各个区域划分,这个结果是在编译中产生的

在我们运行程序时,那个mm_struct结构体就会读取这些属性,来初始化各个区域的起始位置


 OS对页表的管理

事实上,页表不只是从虚拟地址映射到物理地址。

他还存储了很多标记位。

操作系统 (OS) 负责设置页表并定义每个页面的标志位

我们好好想想,对于一片物理内存空间,真的有所谓的只读、只写、无法访问之类的吗?

当然没有!它就是一片空间,你想放什么放什么,但事实是我们的一些操作(例如向只读文件写入)会被拒接。这绝不是内存空间本身可以做到的,事实上这就是靠标记位做到的。我们先讲两个标记位。

rwx--读写执行权限

只需要三个比特位即可存储,从虚拟地址映射到物理地址时OS会检查该标记位,看你的操作是否合法。

我们在执行指令时,如果要向一个只读文件进行写入,那么代码就会挂掉,如果对空指针、野指针进行解引用操作,代码也会挂掉,就是这么实现的。因为他们的标记位显示无法写入权限,所以OS就拒绝了你的操作。

我们现在来看这份代码,是不是就明白为什么编译器没有报错,但是发生了运行时错误

因为这份代码的问题是他试图修改的数据所对应的标记位是只读,无法写入的,因此当代码运行到这一步进行从虚拟地址到物理地址的访问时,MMU一检查发现你这操作不合法,所以不会进行地址映射,而会给OS打报告,OS则可能直接kill进程,而这些,自然不是编译器在编译时可以发现的错误。

isexist

表示该虚拟地址有无对应物理地址

我们知道在加载进程时,先创建的task_struct,然后再加载进代码和数据,那么加入有个代码有足足10w行,我们是需要一口气把它都加载进来吗,显然不是的,因为后面的代码即是加载进来了也要等一段时间才可以运行到,数据也是同样的道理,因此我们对于大文件是采用分批加载的,

同样的道理,对于最开始加载进来的代码和部分数据,我们之后就用不上了,那他们继续留在内存里显然就是浪费空间而已,因此对于靠前的代码和数据我们会进行换出,对于靠后的代码数据我们暂时先不加载进来。这些分批操作、换入换出的实现就是依靠isexist这个标识符,isexist是真,说明该虚拟地址有对应的物理地址,否则说明没有。

以加载大文件为例,当进程要加载进内存时,调用malloc,其实就是让mm_struct里的堆区扩大一些,容纳更多的虚拟地址供你使用,所以你当你malloc成功时,有可能只是申请到了虚拟地址,但是OS并没有给您分配物理地址,这是因为当OS认为你申请后一段时间里如果没有立刻使用这些空间,那么这些空间就等同于处于闲置状态,这是对资源的浪费,所以他就先不给你分配,此时isexist标志位就是0(表示没有),当你后面真的用到了的时候,可以通过缺页中断给你分配。

缺页中断

在虚拟地址通过页表映射到物理地址时,如果该地址不存在,那么就会向OS报错,此时OS会分析,这个不存在的地址如果是用户之前malloc过了,但是OS还没给他分配的,那么就会给他分配;如果是野指针导致该地址不存在,那OS就会杀掉进程,因为这是一种错误。

硬件层次对页表的管理

CPU在执行代码时,如果需要访问地址,就离不开页表的映射,而这一步不是OS做的(划重点!!!)而是CPU中的MMU(内存管理单元)处理的,如下图

为什么要有虚拟地址

使得OS保护了内存,在有野指针、访问只读文件等等操作时,因为有了rwx这些标识位可以进行甄别,看操作是否合法。

通过虚拟地址,可以malloc后先不开辟物理空间,之后通过缺页中断解决,使得空间利用率更高

降低了内存管理和进程管理的耦合度,在加载进程时,开辟空间时,不需要考虑内存是否够用,因为只需要开辟虚拟地址而已,而虚拟地址是完全够的,这样进程管理中就不需要考虑内存不够怎么办的事,

为了提高缓存命中率我们会尽量让同种类型的数据放一起,因此有了栈区,堆区等等,但是直接在物理地址的层面实现这点很难,有了虚拟地址就只需要读取编译后的文件信息即可,这样就把数据的分布从(在物理空间的)无序变得(在虚拟地址的)有序

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

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

相关文章

Vue3 + Element Plus简单使用案例及【eslint】报错处理

本电脑Vue环境已安装正常使用 博主使用npm 包管理器安装 Element Plus.有问题评论区帮忙指正,感谢阅读. 在完成的过程中如果遇到eslint报错 Parsing error &#xff1a;Unexpected token { eslint 这个报错&#xff0c;也可以尝试第7部分报错处理解决。 目录 1.新建项目 2…

【云原生】Docker搭建开源翻译组件Deepl使用详解

目录 一、前言 二、微服务项目使用翻译组件的场景 2.1 多语言用户界面 2.2 业务逻辑中的翻译需求 2.3 满足实时通信的要求 2.4 内容管理系统 2.5 个性化推荐系统 2.6 日志和监控 三、开源类翻译组件解决方案 3.1 国内翻译组件方案汇总 3.1.1 百度翻译 3.1.2 腾讯翻…

DFA算法实现敏感词过滤

DFA算法实现敏感词过滤 需求&#xff1a;检测一段文本中是否含有敏感词。 比如检测一段文本中是否含有&#xff1a;“滚蛋”&#xff0c;“滚蛋吧你”&#xff0c;“有病”&#xff0c; 可使用的方法有&#xff1a; 遍历敏感词&#xff0c;判断文本中是否含有这个敏感词。 …

如何在Linux系统中使用Netcat进行网络调试

文章目录 Netcat简介安装Netcat在Debian/Ubuntu系统中安装在CentOS/RHEL系统中安装 Netcat基本命令Netcat基本用法示例1&#xff1a;监听端口示例2&#xff1a;连接到远程主机 Netcat选项-l选项-p选项-v选项 Netcat模式监听模式连接模式 Netcat排除和包含排除端口包含端口 Netc…

【ClickHouse 探秘】你知道 ClickHouse MergeTree 引擎吗?

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

推荐一款高效的内存清理工具:MemoryCleaner

MemoryCleaner是一款高效的内存清理工具&#xff0c;旨在优化您的计算机性能。它利用Windows内置的多种功能&#xff0c;能够在不影响系统运行的情况下&#xff0c;自动释放内存。用户可以通过系统托盘直接访问MemoryCleaner的功能&#xff0c;无需打开程序&#xff0c;使得内存…

MySQL分区表(二)

说明&#xff1a;之前有写过一篇博客&#xff0c;介绍MySQL如何建立分区表&#xff0c;本文介绍如何建立子分区表。子分区&#xff0c;就是在原来分区的基础上&#xff0c;再嵌套一个分区。 例如&#xff0c;按照记录的创建时间分区&#xff0c;在此基础上&#xff0c;再按照租…

ssm043基于JavaEE的龙腾公司员工信息管理系统的设计与实现+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;龙腾公司员工信息管理系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本龙腾公司…

使用 PyCharm 构建 FastAPI 项目:零基础入门 Web API 开发

使用 PyCharm 构建 FastAPI 项目&#xff1a;零基础入门 Web API 开发 本文提供了一份完整的 FastAPI 入门指南&#xff0c;涵盖从环境搭建、依赖安装到创建并运行一个简单的 FastAPI 应用的各个步骤。通过 FastAPI 和 Uvicorn&#xff0c;开发者可以快速构建现代化的 Web API…

<项目代码>YOLOv8 夜间车辆识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

centos7.X zabbix监控参数以及邮件报警和钉钉报警

1&#xff1a;zabbix安装 1.1 zabbix 环境要求 硬件配置: 2个CPU核心, 4G 内存, 50G 硬盘&#xff08;最低&#xff09; 操作系统: Linux centos7.2 x86_64 Python 2.7.x Mariadb Server ≥ 5.5.56 httpd-2.4.6-93.el7.centos.x86_64 PHP 5.4.161.2 zabbix安装版本 [rootnod…

类(4)

1.拷贝构造函数 我们在创建对象得的时候&#xff0c;可否创造一个与已存在对象一摸一样的对象呢&#xff1f; 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用&#xff08;一般常用const修饰&#xff09; 用在已存在的类类型对象创建新对象时…

‌【元素周期表】氢

化学式&#xff1a;H₂ 外观&#xff1a;无色透明 分子量&#xff1a;2.01588 吸入少量氢气对人体没有危害&#xff0c;甚至还可能对人体有益。但是不能吸入大量氢气&#xff0c;否则可能会对身体造成影响。 氢在生活中的主要用途包括以下几个方面‌&#xff1a; ‌医疗保健…

【06】A-Maven项目SVN设置忽略文件

做Web项目开发时&#xff0c;运用的是Maven管理工具对项目进行管理&#xff0c;在项目构建的过程中自动生成了很多不需要SVN进行管理的文件&#xff0c;SVN在对源码进行版本管理时&#xff0c;需要将其忽略&#xff0c;本文给出了具体解决方案。 SVN设置忽略Maven项目中自动生成…

【Windows】X-DOC:无需NAS使用Windows也能安装Jellyfin玩私人影音媒体平台

【Windows】X-DOC&#xff1a;无需NAS使用Windows也能安装Jellyfin玩私人影音媒体平台 1、前言2、Jellyfin服务搭建2.1 Jellyfin简介2.2 Jellyfin下载2.3 Jellyfin安装2.4 Jellyfin设置2.5 Jellyfin使用 3、终端访问3.1 浏览器访问 4、内网穿透 1、前言 下载收藏高清电影、电视…

海的记忆篇章:海滨学院班级回忆录项目

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了海滨学院班级回忆录的开发全过程。通过分析海滨学院班级回忆录管理的不足&#xff0c;创建了一个计算机管理海滨学院班级回忆录的方案。文章介绍了海滨学院班级回…

Unity 使用Netcode实现用户登录和登出

Unity之NetCode for GameObjets 基本使用 说明思路相关API代码实现Tips 说明 最近项目需要联机&#xff0c;项目方案选用Unity提供的NetCode for GameObjets&#xff08;以下简称NGO&#xff09;&#xff0c;踩了不少坑&#xff0c;本文不介绍基础使用&#xff0c;围绕双端&…

C++(类和对象-运算符重载)

运算符重载概念&#xff1a; 对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型 运算符重载的同时也可以发生函数重载 1.加号运算符重载 1.1加号运算符重载的本质 1.2运算符重载也可以发生函数重载 总结1&#xff1a;对于内置的数据类型…

Flink CDC 同步 Mysql 数据

文章目录 一、Flink CDC、Flink、CDC各有啥关系1.1 概述1.2 和 jdbc Connectors 对比 二、使用2.1 Mysql 打开 bin-log 功能2.2 在 Mysql 中建库建表准备2.3 遇到的坑2.4 测试 三、番外 一、Flink CDC、Flink、CDC各有啥关系 Flink&#xff1a;流式计算框架&#xff0c;不包含 …

Sigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导

SSigrity Power SI VR noise Metrics check模式如何进行电源噪声耦合分析操作指导 Sigrity Power SI的VR noise Metrics check模式本质上是用来评估和观测器件的电源网络的耦合对于信号的影响,输出S参数以及列出具体的贡献值。 以下图为例