【Linux】进程(7):地址空间

news2024/11/23 6:33:21

大家好,我是苏貝,本篇博客带大家了解Linux进程(7):地址空间,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • (A) 直接看代码,看现象
  • (B)基本理解
  • (C)细节
    • 1. 如何理解地址空间
      • a.什么是划分区域
    • 2. 为什么要有地址空间
      • a.将无序变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域
      • b.进程管理模块和内存管理模块进行解耦
      • c.拦截非法请求
    • 3. 进一步理解页表
    • 4. 进一步理解写时拷贝

(A) 直接看代码,看现象

修改.c文件
在这里插入图片描述

运行进程
在这里插入图片描述

先看黄色框,这个我们能理解,因为之前说过父子进程的数据,如果任意一个都不对数据写入,那么数据就是共享的,所以它们指向同一块数据空间,所以地址相同

再看红色框,我们也能理解。因为进程具有独立性,所以子进程对数据的修改,父进程是看不到的,所以它们打印同一个全局变量,但值不同

再看蓝色框,这就不能理解了,同一个地址的值怎么可能既是100也是300?所以这个地址绝对不可能是物理地址,是虚拟地址。引出下一个概念:虚拟地址

(B)基本理解

当我们将程序加载进内存中时,OS在物理内存中一定要给这个程序开辟一块空间,来保存进程的代码和数据。OS要创建进程的task_struct。我们之前只说task_struct能指向内存中的代码和数据,事实远比我们想的复杂
在这里插入图片描述

在OS内部还要创建地址空间(每个进程都有独立的地址空间)。地址空间在32位机器和64位机器上的大小是不同的。我们以32位机器为例(下面讲的都是在32位机器下),地址空间从低到高一共有4GB的空间。我们之前用C语言打印的地址都是地址空间范围内所对应的地址,而非物理地址

进程的PCB会指向地址空间。

进程的代码和数据都在物理内存里面,进程想访问数据时,数据并不在地址空间上保存,地址空间会给我们提供线性的连续的地址(即虚拟地址),让我们未来提供虚拟地址找到物理地址

在这里插入图片描述

如何通过虚拟地址找到物理地址呢?
在计算机体系结构中还存在页表(每个进程都有自己独立的页表)。页表主要负责将地址空间的虚拟地址和对应的物理地址之间建立映射关系。只要建立好了映射关系,未来上层使用虚拟地址访问时,OS会自动拿着虚拟地址查页表转换成物理地址,最后访问到数据

在这里插入图片描述

现在有进程中有全局变量g_val,&g_val得到的0x601054是虚拟地址,假如g_val的物理地址是0x11223344,那么在页表中就会存储这些地址
在这里插入图片描述

现在进程创建了一个子进程,OS也会为子进程创建task_struct、地址空间和页表(每个进程都有自己独立的地址空间和页表)。
子进程没有代码和数据,它会继承父进程的代码和很多属性,相当于父进程pcb里的很多属性就可以用来初始化子进程。所以子进程的task_struct除了pid,ppid等,大部分属性都和父进程的一样。
子进程的地址空间和页表都是直接拷贝父进程的,所以子进程的地址空间里也有g_val的虚拟地址0x601054,子进程的页表里也有g_val的物理地址
在这里插入图片描述

现在让子进程对数据进行写入:将g_val的值从100改为300。
由于进程具有独立性,所以子进程对g_val的修改,不能影响父进程,所以肯定不能在0x11223344对应的空间修改。OS在写入时,发现g_val不仅被子进程在使用,还同时被父进程使用,所以写入暂停,OS在物理内存中重新开辟一块空间,假设物理地址为0x22334455,然后将g_val的值100拷贝到新空间中。再用新的物理地址覆盖页表中老的物理地址,重新构建映射。
上面的工作(叫写时拷贝)做完,OS再继续执行写入操作。将新的空间的值改为300

在这里插入图片描述

至此,子进程修改g_val的值,只是修改了物理内存和页表,可是上层用到的虚拟地址依旧是0x601054,虽然虚拟地址相同,但被映射到物理内存的不同的区域,所以出现了我们在(A)直接看代码,看现象里地址一样,但值不同的情况

(C)细节

为什么要写时拷贝?
我们上面说了是为了保证进程的独立性。那为什么不在创建子进程的时候,就把数据全部给子进程拷贝?因为如果有数据是父子进程都不需要修改的话,那将这些数据也给子进程拷贝一份,这不就是在浪费空间吗?所以,写时拷贝的本质就是按需申请

1. 如何理解地址空间

a.什么是划分区域

在小学的时候,大家都应该和同桌在桌子上划过“三八线”吧,现在假设你和同桌2个人共用一个100cm的桌子, 你们每个人50cm,那这如何用计算机语言来描述呢?
在这里插入图片描述

只需要构建2个结构体,第二个结构体表示一个课桌分为左右两部分,第一个结构体表示每部分的开始和结束位置,再构建第二个结构体的结构体变量,最后将左右两块空间的起始和终止位置都赋值即可

如果同桌太过分了,每次都侵占了属于你的10cm区域,再用计算机语言来描述

在这里插入图片描述

事实上,地址空间本质是内核的一个struct结构体(struct mm_struct),内核的很多属性都是表示start和end的范围。如何证明呢?

让我们来查看Linux的源代码,我们看到有许多表示开始和结束的变量

在这里插入图片描述

2. 为什么要有地址空间

a.将无序变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域

如果没有地址空间,那么进程的task_struct就要能指向物理内存中对应的所有的数据和代码,这对进程来讲是比较困难的

在这里插入图片描述

现在有了地址空间和页表。实际的物理内存中,代码区、数据区、堆区……都是无序的,如果让进程的task_struct直接指向物理内存的对应的各种代码和数据区,那么可能从低地址往高地址,第一个是初始化数据区,第二个是堆区……就不像有了地址空间和页表,在进程的task_struct视角,从低地址往高地址一定是代码区、初始化数据区……
在这里插入图片描述

所以地址空间的第一个好处就是将无序变有序(对task_struct),让进程以统一的视角看待自己运行的各个区域

b.进程管理模块和内存管理模块进行解耦

比如现在进程要申请一段堆空间,那先在地址空间的堆区申请一段空间,但是进程不是立马就要用,所以暂时不在物理内存申请空间,也不在页表建立映射关系(只有虚拟地址,没有物理地址),等到进程需要用这段空间,再在物理内存申请空间并建立映射关系

换言之,如果要对进程做各种管理,那么内存管理都可以延迟处理,因此地址空间和页表的存在,能让进程管理模块和内存管理模块解耦

c.拦截非法请求

当我们写的代码在遍历时要访问地址空间的堆区(我们在上层使用的都是虚拟地址),但发生了越界,此时将这个越界的虚拟地址到页表中查,发现没有这个虚拟地址,证明没有对应的映射关系,所以OS就拦截了这次请求,不让它做任何操作,就不会有往物理内存中写入的操作,所以能拦截非法请求

3. 进一步理解页表

CPU内的寄存器(如CR3)能将当前页表的地址保存在CPU内
MMU(硬件):将虚拟地址结合页表转换成物理地址
在这里插入图片描述

页表中有许多标记位,比如用来确定当前物理地址指向的空间是否在内存中,是:标记位为1;否:标记位为0

什么情况下,当前物理地址指向的空间不在内存中呢?
进程挂起。如果操作系统内存特别吃紧,且进程处于阻塞态,那么操作系统会将内存中的代码和数据加载到磁盘的swap分区,那么此时物理地址指向的空间就不在内存中,该标记位就为0

页表中还有rwx权限标记位。我们以前见过下面的代码,你觉得这个代码能被VS运行通过吗?
在这里插入图片描述

显然不能,在语言层面上讲,因为”hello world”是常量字符串,位于字符常量区,所以不能被修改
在操作系统层面,就是字符常量区的虚拟地址在页表上的rwx权限标记位为r,没有w,所以在想将“web”写入时,系统检测到了错误,在转化成物理地址中将进程终止,所以根本没有写入物理内存

4. 进一步理解写时拷贝

在进程没有创建子进程时,全局变量g_val的虚拟地址被记录在页表中,对应的权限标记位为rw,可读可写

在这里插入图片描述

进程创建子进程后,全局变量g_val的虚拟地址被记录在页表中,对应的权限标记位被OS设为r。子进程会拷贝父进程的地址空间和页表,所以子进程的g_val在页表对应权限标记位也是r

在这里插入图片描述

当父子进程任意一个想对g_val进行写入时,将g_val的虚拟地址到页表中查,发现其权限标记位为r,OS识别到错误,开始判断

  1. 是不是数据不在物理内存(进程挂起了,页表中对应标记位为0):如果是,触发缺页中断(让OS重新在物理内存中开辟空间,重新建立映射,把标记位置为1,然后再继续访问)。这属于正常情况
  2. 是不是数据需要写时拷贝(OS如何知道需要写时拷贝,这个以后再讲),如果是,就发生写时拷贝
  3. 上面2种都不是,进行异常处理

好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

Leetcode3169. 无需开会的工作日

Every day a Leetcode 题目来源:3169. 无需开会的工作日 解法1:排序 遍历 按 LeetCode56.合并区间 的做法,把 meetings 数组中所有重叠的区间合并起来,再统计其中无需开会的工作日个数。 代码: /** lc appleetco…

学习笔记——路由网络基础——缺省(默认)路由

3、缺省(默认)路由 1、定义 缺省路由(默认路由):是目的地址和掩码都为全0的特殊路由。全0代表任意网络。缺省路由在路由表中的形式为:0.0.0.0/0缺省路由也被叫默认路由。缺省路由优先级比直连路由低 缺省路由是一种特殊的路由,当报文没有在…

cv2.imwrite路径中存在中文时出现乱码问题

cv2.imwrite(path, img) 在写入包含中文的路径的时候,保存的文件名称为乱码。 解决办法: cv2.imwrite(path,image)将上面的代码修改为以下代码,可以避免出现中文乱码。 cv2.imencode(.jpg, image)[1].tofile(path)

嵌入式Linux系统编程 — 2.2 标准I/O库:检查或复位状态

目录 1 检查或复位状态简介 2 feof()函数 2.1 feof()函数简介 2.2 示例程序 3 ferror()函数 4 clearerr()函数 4.1 clearerr()函数简介 4.2 示例程序 1 检查或复位状态简介 调用 fread() 函数读取数据时,如果返回值小于参数 nmemb 所指定的值,这…

python常见数据分析函数

apply DataFrame.apply(func, axis0, broadcastFalse, rawFalse, reduceNone, args(), **kwds) 第一个参数是函数 可以在Series或DataFrame上执行一个函数 支持对行、列或单个值进行处理 import numpy as np import pandas as pdf lambda x: x.max()-x.min()df pd.DataFrame(…

【数据结构】详解堆的基本结构及其实现

文章目录 前言1.堆的相关概念1.1堆的概念1.2堆的分类1.2.1小根堆1.2.2大根堆 1.3堆的特点堆的实用场景 2.堆的实现2.1初始化2.2插入2.3堆的向上调整2.4删除2.5堆的向下调整2.6判空2.7获取堆顶元素2.8销毁 3.堆排序3.1实现3.2堆排序的时间复杂度问题 前言 在上一篇文章中&#…

【数据库】SQL零基础入门学习

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

如何通过 4 种方式备份和恢复Android联系人

毫无疑问,联系人是Android手机上存储的最重要的信息之一。为了保护这些重要数据,明智的做法是对Android手机进行联系人备份。如果您的手机发生任何情况导致数据丢失,例如被盗、系统崩溃或物理损坏,您可以再次将备份中的联系人恢复…

Typecho:简约而强大的开源PHP博客平台

Typecho:让博客写作回归本质- 精选真开源,释放新价值。 概览 Typecho是一个开源的PHP博客平台,以其简洁的界面和强大的功能,为博客作者提供了一个高效、易于管理的写作环境。它是一个轻量级、高性能的解决方案,适用于…

主流数据库的大数据插入对比(mssql[sql server]、oracle、postgresql、mysql、sqlite)

首先申明,做这个对比不代表数据库性能,纯属好奇。勿喷,感谢。 测试连续11次插入数据库,每次100万行数据。 测试环境:单机测试,就是所有数据库都装在本机上。操作系统:windows server 2016,使用…

【YOLOV8】1.开发环境搭建

Yolo8出来一段时间了,包含了目标检测、实例分割、人体姿态预测、旋转目标检测、图像分类等功能,所以想花点时间总结记录一下这几个功能的使用方法和自定义数据集需要注意的一些问题,本篇是第一篇,开发环境的配置。 YOLO(You Only Look Once)是一种流行的物体检测和图像分割…

工控主板分类详解

1.ATX系列 尺寸305*244mm;接口扩展丰富,更多的内存和PCIE插槽; 进一步略小的有MATX,尺寸244*244cm;扩展插槽缩减,但兼容ATX接口,依旧是按照ATX标准 2.ITX系列 尺寸170*170mm;相较于ATX主板更加迷你,功能接口也少一些; 常用于小型计算机或者嵌入式系统 高能计算机推…

【Pycharm】功能介绍

1.Code Reformat Code 格式化代码,可以帮助我们去自动调整空格等,根据python语法规范自动调整 2.Settings 1.创建py文件默认填充模版 3.读写py文件编码格式一致性 顶部代码指定的编码方式作用: 可以保证python2/3解释器在读取文件的时候按…

将HTML页面中的table表格元素转换为矩形,计算出每个单元格的宽高以及左上角坐标点,输出为json数据

export function huoQuTableElement() {const tableData []; // 存储表格数据的数组let res [];// 获取到包含表格的foreignObject元素const foreignObject document.getElementById(mydctable);if (!foreignObject){return ;}// 获取到表格元素let oldTable foreignObject…

python tk实现标签切换页面

import tkinter as tk from tkinter import ttk# 初始化主窗口 root tk.Tk() root.title("标签页示例")# 设置窗口大小 root.geometry("400x300")# 创建 Notebook 小部件 notebook ttk.Notebook(root) notebook.pack(expandTrue, fill"both")#…

【leetcode面试经典150题】-45. 跳跃游戏 II

【leetcode面试经典150题】-45. 跳跃游戏 II 1 题目介绍2 个人解题思路2.1 代码 3 官方题解3.1 代码 1 题目介绍 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] …

[职场] 研究生面试自我介绍_1 #经验分享#知识分享

研究生面试自我介绍 想要进入职场,面试是必不可少的。然而想要面试成功,就需要一个让人印象深刻的自我介绍,好的自我介绍可以让面试官,快速了解自己,快速记住自己。 一、范文1 我是一名硕士研究生,即将毕业…

如何查询公网IP?

在互联网中,每个设备都有一个唯一的公网IP地址,用于标识设备在全球范围内的位置。查询公网IP是一个常见的需求,无论是用于远程访问、网络配置还是其他目的,了解自己的公网IP地址都是很有必要的。本文将介绍几种常见的方法来查询公…

【ArcGISProSDK】 读取多面体信息并导出XML

结果展示 代码 using ArcGIS.Core.CIM; using ArcGIS.Core.Data; using ArcGIS.Core.Data.DDL; using ArcGIS.Core.Geometry; using ArcGIS.Core.Internal.CIM; using ArcGIS.Desktop.Catalog; using ArcGIS.Desktop.Core; using ArcGIS.Desktop.Editing; using ArcGIS.Deskto…

DSP问题:CCS更改工程名导入报错

1、问题现象 复制一个工程出来后,修改版本号,重新导入工程后报错。 显示项目描述无效。 2、问题原因 由于CCS无法通过工程描述中找到指定名字文件夹。使用记事本打开.project文件,里面的描述还是以前的文件夹名,所以导入时报…