深入Linux系列之进程地址空间

深入Linux系列之进程地址空间

1.引入

那么在之前的学习中,我们知道我们创建一个子进程的话,我们可以在代码层面调用fork函数来创建我们的子进程,那么fork函数的返回值根据我们当前所处进程的上下文是返回不同的值,它在父进程中返回子进程的PID,在子进程中则会返回0,那么我们可以利用返回值然后定义一个变量来接收这个返回值,然后通过if else逻辑达到让我们的父子进程有着不同的执行流的目的,那么想必刚才所说的过程对于你来说已经十分熟悉,对于fork函数如何使用也已经轻车熟路,但是在刚才的过程中,我们唯独会有一个疑问,也就是我们定义的一个变量来接收这个返回值,同一个变量在不同的进程中竟然会出现两个不同的值,那么这就和我们的理解有点冲突,因为一个变量当前赋值且没被修改的情况下不可能具有两个不同的值,就像一个人只能拥有一种性别一样。
在这里插入图片描述

那么我们之前的文章也详细介绍过我们的fork,但是对于这个情况,当时并没有提及,那么我们现在来思考一下为什么会出现这样的现象,那么我们知道我们进程是具有独立性,那么要做到独立性也就意味着我们父子进程的数据不能做到共享,为此做到该数据独立性,那么我们的fork函数会采取写时拷贝的机制,那么我们如果子进程读取我们父进程的数据,那么此时子进程会与父进程共享一个物理内存页面,但是如果我们子进程要对数据进行写入操作的话,那么这时则会触发写时拷贝机制,那么我们操作系统会为其要写入的数据专门拷贝得到一个副本,然后在这个副本中做写入,从而做到独立性,所以根据我们之前对fork函数的学习以及认识,有了这个写时拷贝机制,我们认为此时我们父子进程关于接收这个返回值的变量,那么它一定是有两份副本,一份是父进程,一份是子进程,所以也就解释了它为什么能够具备有两个值。

​ 那么接下来可以用c语言写一段简单的代码来实验,验证我们刚才的推理的成立,代码逻辑也很简单,那么在代码中定义一个int类型全局变量g_val,然后用fork返回值让父子进程有着不同的执行流,然后我们让子进程对g_val进行修改,然后接着父子进程分别打印这个全局变量的值以及对应的地址,如果打印的结果是子进程中的全局变量的值是修改后的结果,并且对应的地址与父进程不同,那么就能够验证我们之前的结论。
在这里插入图片描述
在这里插入图片描述

那么我们根据结果发现,值确实不同,但是地址是相同的,那么值不同证明父子进程确实对于g_val有各自对应的副本,所以子进程的值与父进程的不同,那么能够验证我们刚才所说的结论,那么既然g_val在父子进程中有着各自对应的一份副本,那么地址不可能是相同的,那么既然是相同的,那么只能说明一个原因,那就是这个地址不是实际的物理内存地址,那么这个地址究竟是什么呢,那么这就和本文的主题进程地址空间有关了,那么这个地址就是虚拟地址。

2.进程地址空间

那么在我们之前对于进程的理解上有得添加一个新的内容,也就是进程地址空间。

我们知道我们进程的内核数据是被加载到我们的内存中的,而当我们的进程被调度到CPU当中运行的话,那么我们CPU得从我们内存中获取我们进程的各种内核数据,我们知道我们CPU以及内存以及外部的io设备都是一个独立的工作单元,那么它们要协同工作的话,就得用“线”来连接,那么数据通过该线在各个工作单元中流通,那么其中这里的线我们可以分为数据线与地址线。

那么同理我们的CPU与我们的内存也有数据线以及地址线连接,那么顾名思义,地址线就是用来专门传输地址内容,而数据线是用来传输数据内容的,那么以32为机器为例,那么我们的CPU与我们的内存的地址线有32根,那么每一根地址线根据其高低电频的电信号分别用来表示二进制的0和1,那么我们的CPU就能根据各个地址线的高低电频信号组合得到一个二进制序列,而这个二进制序列就是我们的地址,那么内存的寻址器获取到地址后,会到内存的对应位置得到CPU要获取的数据,然后通过数据线传给CPU当中的寄存器,那么这32根地址线每一根线只能表示两种不同的信息,要么是0或者1,所以我们这32根地址线总共就能表示出2的32次方个不同的地址,那么它的范围是从0000 0000到FFFF FFFF。
在这里插入图片描述

那么我们知道我们这32根地址线只能表示0000 0000到FFFF FFFF范围上的所有地址,那么我们这0000 0000到FFFF FFFF范围上连续的地址便是我们的进程的地址空间,也就意味着,我们进程的各种数据,那么他们的地址只能在这个范围上,不能超出这个范围,因为我们CPU的32根地址线只能表示这么多,那么我们操作系统为了管理这个进程地址空间,采取的方式是我们最熟悉的先描述,再组织,那么它在我们进程所对应的task_struct结构体中,定义了一个指向mm_struct结构体。

而没错这个mm_struct结构体就是用来描述我们的进程地址空间,那么它里面会有两个字段来表示这个进程地址空间的起始位置与结束位置,那么通过这个mm_struct结构体就能在抽象建模出我们的进程地址空间。

而进程地址空间的设计还不至于此,那么我们进程地址空间还专门进行了内存的布局,将其分为了代码段以及数据段,然后再数据段中的不同范围还分为了常量区,堆区,栈区等,那么这些名词在我们的语言的学习上想必都听闻过。

那么至于为什么对我们的进程地址空间进行内存布局,那么我们可以拿生活中的一个例子来理解,就假设你是一个读者,要去图书馆借阅书籍,那么如果我们的图书馆的各种类型的数据是随意摆放,各种类型的书籍都在同一个书架上,那么我们读者要寻找到目标书籍的时间开销是很大的,但是如果图书馆将这些数据按照类型分不同楼层,按首字母分不同书架,那么我们查找以及图书馆管理起来就很方便。

根据这个例子我们便知道要为进程地址空间布局的一个重要原因,那么便是对于数据的查找以及内存管理,那么具体实现则是我们的mm_struct结构体在包含其他各个数据段的结构体指针,那么对应该数据段的结构体就包含起始位置以及结束位置的两个字段或者直接包含各个数据段的起始以及结束的字段,那么通过我们的mm_struct结构体,我们就对内存有着统一的布局和视角来看待。
在这里插入图片描述

而在我们进程地址空间上的各个地址,这些地址也就是所谓的虚拟地址,所谓虚拟,也就是说它并不是我们该数据真实在物理内存当中的地址,那么之所以我们不给我们的数据直接分配物理地址,原因也很简单,那么就是安全,那么如果我们用户直接接触到了物理地址,那么我们可以对数据的篡改以及覆盖的行为是不受阻拦的,那么这个虚拟地址就像是一个屏障,将其与物理地址隔开,那么我们对物理内存的任何操作意味着都要受到约束或者阻拦,这维护了数据以及系统的安全。

那么既然我们只能看到虚拟地址,那么我们内存器要获取数据那么肯定得是要我们的真实的物理地址,那么必然得需要将我们的虚拟地址转换为我们的物理地址,那么就有了我们页表的存在。

那么页表是一个数据结构,那么它建立了虚拟地址到物理地址的一个映射,并且还为每个数据设置了是否具有读写权限的字段信息,那么我们可以通过页表将虚拟地址转化为物理地址。所以在我们创建进程的时候,我们进程的内核数据被加载到内存中,同时我们还会为进程创建对应的task_struct结构体,那么其中就包括了进程地址空间的创建以及页表的创建,那么也就意味着我们每一个进程都有自己的进程地址空间也就是对应的mm_struct结构体和页表,但是我们要注意的是,我们不同的进程的进程地址空间都是相同的,也就意味着我们每一个进程的mm_struct结构体的各个字段都是相同的,那么这也很好理解,因为我们让各个进程都以统一的逻辑来看待内存,但是看不到物理内存,那么我们每个进程对应的进程地址空间都是全部的从0000 0000到FFFF FFFF的4GB的空间,但是对于页表不同进程肯定是不一样的,因为虚拟到物理对应的映射各个进程是不同的,那么在我们创建我们的进程的同时,我们会初始化页表的部分数据的映射,那么之所以是部分,而不是全部数据,则是由于有以下原因:

我们知道我们操作系统会为了缓解进内存的压力会对某些进程采取挂起的手段,也就是将他的内核数据先放回到外部设备比如磁盘当中,那么此时它的内核数据都没加载到内存中,那么自然无法完成对应的映射。

其次,我们知道整个进程地址空间的大小是4GB,那么我们在不同的计算机的构造下,有可能会出现实际物理内存比虚拟内存小的情况,比如有的物理内存是2GB,那么就会导致有的数据没有加载到物理内存,所以无法完成所有数据的映射的初始化。

那么对于有些不在物理内存无法完成初始化的数据,那么页表会专门会有一个字段来标记它是否在物理内存,如果不在,那么则会被标记为缺页,那么对于缺页中断的数据的话,那么我们到时候进程被调度的时候,操作系统一定会为缺页中断的数据重新生成对应的映射。
在这里插入图片描述

所以有了虚拟地址的概念,那么我们对之前的我们语言层面上无法解释的东西,那么我们现在就能够找到源头了,比如我们知道我们c语言我们为什么不能修改字符常量,那么是因为常量区的数据的权限会在页表中标记为只读权限,如果我们要对访问字符常量并且执行写操作,那么它就会对我们该操作进行阻拦。


重新认识fork系统调用

那么有了虚拟地址空间这个概念,那么我们再来重新理解一下我们的fork函数创建子进程的一个原理,那么我们调用fork函数,那么我们会拷贝父进程的task_struct结构体,其中就包括进程地址空间以及页表,这也就是为什么说我们子进程与我们父进程是共享一份物理内存页面的,调用fork函数后,我们操作系统会将父进程的页表中的数据段的内容的权限全部改成只能读不能写,那么如果我们子进程要对数据段的内容进行写入的话,由于权限只读,从而就会触发写时拷贝机制,那么操作系统会在物理内存重新为该数据开辟一份空间,然后接着修改我们子进程所对应的物理内存地址,而虚拟内存地址则不需要修改,从而做到父子进程的数据的独立,有了虚拟地址的概念,便解决了我们开头所说的那个问题。


有了页表这个概念之后,那么我们CPU要读取我们进程在内存中的数据,那么CPU的MMU(内存管理单元)那么它会获取到该进程的页表,然后将我们进程的各种数据的虚拟地址转换成物理地址,然后通过地址线传递给我们的内存的寻址器去获取目标数据然后再传到我们的CPU的寄存器当中

3.结语

那么本篇文章介绍了我们进程地址空间的概念,那么我们知道进程地址空间的出现的意义第一是能让用户以及操作系统能够以统一的视角与布局看待我们的内存,因为我们实际上各个数据在物理内存中的位置是离散的乱许的,但是在进程空间的视角下,逻辑上认为他们是有序排列,比如栈区以及堆区在高地址处,那么这样的内存布局的好处还方便于我们对于数据的查询以及管理。

那么第二是我们的虚拟进程地址空间以及页表的存在,建立一道用户与物理内存的屏障,那么用户不能通过代码直接访问到物理内存,不让用户的各个行为不会受到阻拦,那么可能会出现数据的修改以及越界访问等等问题。

第三是我们虚拟地址以及页表的出现,那么让我们的进程管理以及内存管理相互分离开,那么进程管理我们操作系统就管理我们task_struct结构体所对应的各个数据结构,而内存管理,则是管理我们的页表结构即可,实现了进程管理与内存管理的解耦

那么这就是本篇文章对于进程地址空间的全部内容,那么希望能够让你有所收获,那么我会持续更新,那么下一篇文章我将会解析我们进程的终止与等待,那么希望你能够多多关注,多多支持!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端 CSS 动态设置样式::class、:style 等技巧详解

一、:class 动态绑定类名 v-bind:class&#xff08;缩写为 :class&#xff09;可以动态地绑定一个或多个 CSS 类名。 1. 对象语法 通过对象语法&#xff0c;可以根据条件动态切换类名。 <template><div :class"{ greenText: isActive, red-text: hasError }&…

ArgoCD实战指南:GitOps驱动下的Kubernetes自动化部署与Helm/Kustomize集成

摘要 ArgoCD 是一种 GitOps 持续交付工具,专为 Kubernetes 设计。它能够自动同步 Git 仓库中的声明性配置,并将其应用到 Kubernetes 集群中。本文将介绍 ArgoCD 的架构、安装步骤,以及如何结合 Helm 和 Kustomize 进行 Kubernetes 自动化部署。 引言 为什么选择 ArgoCD?…

go语言文件和目录

打开和关闭文件 os.Open()函数能够打开一个文件&#xff0c;返回一个*File 和一个 err。操作完成文件对象以后一定要记得关闭文件。 package mainimport ("fmt""os" )func main() {// 只读方式打开当前目录下的 main.go 文件file, err : os.Open(".…

LLM应用实践(1)- 物流状态判断

原文&#xff1a;LLM应用实践&#xff08;1&#xff09;- 物流状态判断 稳定输出 JSON 字符串 为了能够更好的贴合实际的业务场景的应用&#xff0c;我们通常期望大模型返回的数据是 JSON 格式的&#xff0c;这样能够降低对大模型返回内容处理的复杂度&#xff0c;如果返回了…

redis高级数据结构Stream

文章目录 背景stream概述消息 ID消息内容常见操作独立消费创建消费组消费 Stream弊端Stream 消息太多怎么办?消息如果忘记 ACK 会怎样?PEL 如何避免消息丢失?分区 Partition Stream 的高可用总结 背景 为了解决list作为消息队列是无法支持消息多播问题&#xff0c;Redis5.0…

SpringMVC SpringMVC拦截器 拦截器基础知识

1.什么是拦截器 SpringMVC提供了Intercepter拦截器机制&#xff0c;类似于Servlet当中的Filter过滤器&#xff0c;用于拦截用户的请求并作出相应的处理&#xff0c;比如通过拦截器来进行用户权限验证或者用来判断用户是否登录。SpringMVC拦截器是可插拔式的设计&#xff0c;需…

TAPEX:通过神经SQL执行器学习的表格预训练

摘要 近年来&#xff0c;语言模型预训练的进展通过利用大规模非结构化文本数据取得了巨大成功。然而&#xff0c;由于缺乏大规模高质量的表格数据&#xff0c;在结构化表格数据上应用预训练仍然是一个挑战。本文提出了TAPEX&#xff0c;通过在一个合成语料库上学习神经SQL执行…

Matlab机械手碰撞检测应用

本文包含三个部分&#xff1a; Matlab碰撞检测的实现URDF文件的制作机械手STL文件添加夹爪 一.Matlab碰撞检测的实现 首先上代码 %% 检测在结构环境中机器人是否与物体之间发生碰撞情况&#xff0c;如何避免&#xff1f; % https://www.mathworks.com/help/robotics/ug/che…

激活函数篇 04 —— softmax函数

将模型的输出转换为概率分布&#xff0c;使得模型能够输出每个类别的概率值。 Softmax ( a i ) e a i ∑ j 1 n e a j \text{Softmax}(a_i)\frac{e^{a_i}}{\sum_{j1}^n e^{a_j}} Softmax(ai​)∑j1n​eaj​eai​​ 其中&#xff0c; a i a_i ai​ 是输入向量中的第 i i i 个…

软件工程的熵减:AI如何降低系统复杂度

软件开发的世界&#xff0c;如同一个不断膨胀的宇宙。随着功能的增加和时间的推移&#xff0c;代码库越来越庞大&#xff0c;系统复杂度也随之水涨船高。代码膨胀、维护困难、开发效率低下等问题困扰着无数开发者。这不禁让人联想到物理学中的“熵增”原理——一个孤立系统的熵…

springboot008房屋租赁系统

版权声明 所有作品均为本人原创&#xff0c;提供参考学习使用&#xff0c;如需要源码数据库配套文档请移步 www.taobysj.com 搜索获取 技术实现 开发语言&#xff1a;Javavue。 框架&#xff1a;后端spingboot前端vue。 模式&#xff1a;B/S。 数据库&#xff1a;mysql。 开…

DeepSeek训练成本与技术揭秘

引言&#xff1a;在当今人工智能蓬勃发展的时代&#xff0c;DeepSeek 宛如一颗耀眼的新星&#xff0c;突然闯入大众视野&#xff0c;引发了全球范围内的热烈讨论。从其惊人的低成本训练模式&#xff0c;到高性能的模型表现&#xff0c;无一不让业界为之侧目。它打破了传统认知&…

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas) 文章目录 计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)摘要Abstract一、Attention U-Net1. 基本思想2. Attention Gate模块3. 软注意力与硬注意力4. 实验…

<论文>DeepSeek-R1:通过强化学习激励大语言模型的推理能力(深度思考)

一、摘要 本文跟大家来一起阅读DeepSeek团队发表于2025年1月的一篇论文《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning | Papers With Code》&#xff0c;新鲜的DeepSeek-R1推理模型&#xff0c;作者规模属实庞大。如果你正在使用Deep…

【PDF提取内容】如何批量提取PDF里面的文字内容,把内容到处表格或者批量给PDF文件改名,基于C++的实现方案和步骤

以下分别介绍基于 C 批量提取 PDF 里文字内容并导出到表格&#xff0c;以及批量给 PDF 文件改名的实现方案、步骤和应用场景。 批量提取 PDF 文字内容并导出到表格 应用场景 文档数据整理&#xff1a;在处理大量学术论文、报告等 PDF 文档时&#xff0c;需要提取其中的关键信…

collabora online+nextcloud+mariadb在线文档协助

1、环境 龙蜥os 8.9 docker 2、安装docker dnf -y install dnf-plugins-core dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sed -i shttps://download.docker.comhttps://mirrors.tuna.tsinghua.edu.cn/docker-ce /etc/yum.repos.…

Meta AI 最近推出了一款全新的机器学习框架ParetoQ,专门用于大型语言模型的4-bit 以下量化

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

w198基于Springboot的智能家居系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

电脑连接wifi但是浏览器打开不了网页,使用手机热点能正常使用

电脑连接wifi但是浏览器打开不了网页&#xff0c;使用手机热点能正常使用 打开控制面板 打开网络和Internet&#xff08;查看网络状态和任务&#xff09; 点击更改适配器设置 双击WLAN 点击属性并双击打开Internet 协议版本4&#xff08;TCP/IPv4&#xff09; 将自动…

蓝桥杯K倍区间(前缀和与差分,取模化简)

输入 5 2 1 2 3 4 5 输出 6 思路&#xff1a;首先由连续子串和可以想用前缀和&#xff0c;由于加减法总和取模和分别取模结果不受影响&#xff0c;所以我们前缀和之后直接取模方便观察性质&#xff0c;本题前缀和&#xff1a;1&#xff0c;3&#xff0c;6&#xff0c;10&#…