浅析 Linux 进程地址空间

news2024/11/14 20:36:44

浅析 Linux 进程地址空间

  • 有趣的现象
  • 地址空间和虚拟地址
  • 开篇现象解释
    • 解释
    • 相关问题
  • 如何理解地址空间
  • 为什么要有地址空间
    • 将无序变为有序,让进程以统一的视角看待物理内存以及自己运行的各个区域
    • 进程管理模块和内存管理模块进行解耦
    • 拦截非法请求
  • 粗浅理解页表和写时拷贝
    • 页表
    • 写时拷贝
  • 粗浅认知虚拟地址

说到 地址,C 语言的指针一定是痛苦的回忆,这篇文章也是需要指针功底的,做好准备 ^ ^

有趣的现象

二话不说,咱先看看如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int g_val = 100;

int main()
{
    printf("It is a process, pid: %d, ppid: %d\n", getpid(), getppid());
    pid_t id = fork();
    if (id == 0)
    {
        // child
        int cnt = 0;
        while (1)
        {
            printf("It is child process, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            ++cnt;
            if (cnt == 5)
            {
                int tmp = g_val;
                g_val = 300;
                printf("It is child process, change %d -> %d\n", tmp, g_val);
            }
        }
    }
    else 
    {
        // father
        while (1)
        {
            printf("It is father process, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }
    return 0;
}

在 Linux 终端上跑跑看,会有如下现象:

在这里插入图片描述

在刚开始, g_val 都是 100,子进程修改后子进程的 g_val 已经变成了 300,但父进程依然没变

这倒也可以理解,毕竟我们之前说过 进程运行的时候,它们之间具有独立性(不是老死不相往来的关系,毕竟有些进程在管理数据上是有交集的:父进程可以拿到子进程、父进程可以杀掉子进程等等;最最主要的是,在正常运行的父子进程之间,其中一个退出是不会影响另一个进程的)

独立性如何体现呢?
之前也说过:进程 = 进程的内核数据结构(task_struct) + 代码和数据
那么首先 内核数据结构 肯定是具有独立性的,毕竟要管理不同进程,不会共用同一个 task_struct 对象,而代码是只读的,不同的进程当然可以读同一份代码呀(例:同时开启多个 QQ);但数据呢?数据是不能混为一谈的,一台机器的 QQ 程序登录不同的账号,必然开启不同的进程,数据也必然是不同的

但上面黄框子框出来的父子进程两个不同的 g_val 却是 相同的地址,怎么可能出现 相同地址的同一块空间存放两份不同的数据呢?

我们现在是可以 断定g_val 的地址绝对不是物理地址,这种地址在系统层面被称为 虚拟地址

地址空间和虚拟地址

已经知道当一个程序被加载到内存后即将成为进程时,OS 会为此程序创建一个进程 PCB 节点以管理此进程,但除了要创建 PCB 外还要创建进程对应的 地址空间,全名叫做 进程地址空间,而每个进程都有自己的 地址空间,其内线性的连续的的地址就是 虚拟地址

在 C 语言或 C++ 时学习的内存地址空间其实就是我们上面说的 地址空间(如下图所示,32位,总 4 G),而这个 并不是真正的物理内存空间

在这里插入图片描述

既然不是物理的内存空间,那它在哪?是在 OS 内部,每当为程序开启一个进程,就会为此进程创建一个 地址空间,而进程 PCB地址空间 都是在物理内存中开辟的空间存放的

既然是虚拟的就不能存放实在的数据,也就是说最终数据还是都要放物理内存里的,既如此就需要将 虚拟地址 映射到真实的物理地址上,映射方式就是使用 页表 映射,每个进程也都有一个自己的页表,而页表接收到虚拟地址就能映射出真实的物理地址

OS 内进程可太多了,而每个进程又都有自己的 地址空间,那这些 地址空间 哪个被释放,哪个被修改等等,这些东西都是需要 OS 去管理,如何管理?先描述,再组织

所以 地址空间 本质上就是内核数据结构,会有它自己的结构体对象

页表 也是如此

开篇现象解释

解释

据上面所说,一个程序跑起来变成进程后,此进程会有一个自己独立的 地址空间,自己独立的 页表,由于程序里对应的地址是 地址空间 里的 虚拟地址,而 OS 也就是拿这个 虚拟地址 去查 页表 后得到真实的物理地址进行操作(读取,写入等等)

而开篇现象的代码里 g_val 的初始值为 100,那么 在真实的物理内存中 就存在一块存放整型 100 的空间 R,其地址为 RA

进程启动后,而我们查到 g_val虚拟地址0x601054 ,那么 OS 将 0x601054 拿给 页表 一查就能映射出物理地址 RA

当创建子进程后,原进程就是父进程,子进程也一定会有自己的 PCB地址空间页表(这一堆东西都是存储在内存里的内核数据结构)

子进程也会继承父进程的很多属性,除了 pidppid 外,父进程很多属性甚至都能直接拿来初始化子进程属性,其中就包括地址空间,照搬父进程的内容和父进程保持一致,而页表也是如此,直接会给子进程拷贝一份;所以子进程会把父进程的很多内核数据结构全拷贝一份(除了个别几个属性除外)

那么子进程也就有了和父进程一样的 地址空间页表,而 页表 是和指针打交道的,直接拷贝过来后子进程也一定有虚拟地址 0x601054 ,也一定可以通过自己和父进程相同的 页表 映射到物理地址 RA

那这是 浅拷贝,也就是父子进程是共用地址为 RA 的物理内存空间,里面存放的是 g_val 的值

当子进程要执行更改 g_val 值时,那接下来的操作会是下面这样吗?
子进程要改 g_val 的值,就会拿出 g_val 的虚拟地址交给 OS,经过查询页表,发现是地址为 RA 的物理内存空间,紧接着对地址为 RA 的空间值更改为 300

这样对吗?肯定不对吧,如果这样更改,那父进程拿到的值也一定是 300,为啥还是 100 呢?况且运行的进程之间是具有 独立性 的,独享各种资源,多进程之间互不干扰;子进程要修改什么值也不应该影响到父进程

真实的情况是
子进程要修改 g_val 之前,OS 发现这个变量不是你子进程一个在用,父进程也在用,那么为了不影响父进程,OS 会提前为子进程在物理内存开辟一块空间,假设地址为 CA ,然后将地址为 RA 的空间里的值(100)拷贝到地址为 CA 的空间里,再将子进程的 页表虚拟地址0x601054 的映射改为地址为 CA 的新空间 ,到此,子进程要修改 g_val 之前的准备工作全部完成,这一切都是 OS 自主完成,这些工作被称之为 写时拷贝

接下来再按照传统步骤,经过 地址空间,经过 页表 将物理内存地址为 CA 的空间的值改为 300,至此修改 g_val 工作完成

OS 内核虽然开辟了空间,修改了 页表,虽然父子进程存放 g_val 的两个物理空间也是不一样的,但上层用户层对 g_val 使用的地址依然是 0x601054 这个 虚拟地址

以上就是我们看到开篇现象的解释

在这里插入图片描述

相关问题

  • 如果父子进程不修改共用的数据呢?就像上面如果子进程一直不修改 g_val 的值呢?
    • 未来一个全局变量,默认是父子共享的,代码也是共享的(代码只读属性),如果不修改,父子进程会一直共享同一块 g_val 的物理内存空间
  • 为什么要采用 写时拷贝,和父进程共享数据时,数据要发生改动还要 写时拷贝,怪麻烦,不能直接为子进程开辟单独的数据空间吗?
    • 正是因为数据会存在耦合现象,所以咱才要 写时拷贝;如果直接将父进程的数据拷贝给子进程一份,页表重新全部修改不说,也肯定会造成 空间浪费,因为父进程的很多数据子进程是不会修改的,就比如命令行参数和环境变量,但这些数据的占地面积却不小,势必会发生父进程和子进程在不同的内存空间里读取相同的数据,这不是个管理内存的好方法,因为内存空间本来就不够大
    • 所以 写时拷贝 本质上是一种 按需申请,通过调整拷贝的时间顺序,达到有效节省空间的目的

如何理解地址空间

通过上面的图片,可以看到 地址空间 内有很多区域,如何划分这些区域?如何对这些已划分好的区域做调整?很显然,这需要对地址空间进行描述,对地址空间内的区域进行描述

区域描述 肯定是需要地址的开头(start)和结尾(end),如此就能表示一段区域,比如栈的区域,堆的区域等等,既如此就需要一个结构体来表示,内有属性 startend

地址空间 本质上也肯定是内核的一个 struct 结构体,内部很多属性都是 startend 表示的范围,从而规定出不同的区域,在 Linux 系统里,这个结构体叫做 mm_struct

但上面的图里,一个进程对应的 地址空间 似乎是 4 G,如果每一个进程都配备一个实际 4 G 空间,我想我这 16 G 的内存早就该换了吧?根本不够用的,所以这 地址空间 一定是 OS 给进程画的 “大饼” ,进程只知道这 4 G 连续的地址空间每一寸它都可以使用,至于操作系统能不能分出这么多空间给这么多进程并不是单一进程要考虑的

为什么要有地址空间

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

试想如果没有 地址空间,那也就不需要 页表,而进程要访问内存资源,就会直接记录物理内存的地址

这样做并不是行不通,但进程的状态以及增减不是 OS 可以预料的,也就是说多个进程之间状态变化,如果在再出现增减,那么物理内存的数据存储肯定不是连续的,就算刚开始加载是连续的,但随着个别进程的空间释放,数据肯定是碎片化存储在物理内存上的

就算是一个进程的代码和数据也不一定就是连续存储的,再算上多个进程的数据交叉存储,如果再直接使用物理内存地址,这是一种极大的混乱,存在严重的存储安全问题,比如越界等行为,自己的进程可能会崩溃不说,还可能影响其他进程

有了地址空间,无论一个进程的代码和数据分散在物理内存的什么位置(乱序),进程只要关心自己地址空间的虚拟地址即可,因为虚拟地址是有序的,连续的,只有区域大或小的区别

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

有了 地址空间 就可以迷惑进程的实际物理内存空间:

  • 假如现在一个进程有 4MB 的数据,CPU 已经执行了 2MB 的数据后,如果 OS 发现物理内存的整体空间不够了,那 OS可以直接将闲置空间全部释放掉,那就有可能将这个进程被执行过的 2MB 代码唤出到外设,甚至直接释放;而地址空间里却可以不变,依然是 4MB 的数据,但实际上内存只有这个进程 2MB 的数据了
  • 再比如现在进程申请了一块物理内存空间,但 OS 不一定立马就给你开辟,不建立页表映射,会一直到你需要使用时才会为你开辟需要的空间;如果立马给你开辟,那你不使用的这段时间就相当于拿着这块空间什么都不做,但如果给 OS ,它可能会使用这块空间做很多事情,内存的使用率就会非常高

所以对进程来说,只需要知道它的地址空间可以申请就够了,其他的交给 OS 安排

拦截非法请求

比如说现在你的写的程序变成进程跑在 OS 上,此进程越界访问了一块不属于自己的空间,那接下来 OS 就会拿到这块非法地址去查 页表,这时候由于 页表 内根本没有这一条 虚拟地址 的映射,OS 就会直接对你的访问进行拦截,对物理内存进行保护

粗浅理解页表和写时拷贝

页表

页表并不简单,查页表使得虚拟到物理的转换是由 CPU 内的 MMU 单元和 CR3 寄存器实现的,这种寄存器会保存当前进程的页表虚拟地址,而 MMU 会将虚拟地址结合页表快速的转化为物理地址

而页表也会有自己的 标记位,例如:指定的物理内存是否在内存中、指定单元是否具有 rwx 权限问题等等

进程挂起就可以利用这个辅以解释:当内存资源严重不足时会将部分进程的部分代码数据 唤出 外设磁盘的 swap 分区中,此时这个进程为挂起状态;
只要把页表的指定的物理内存是否在内存中的标记位标为 0,保留 虚拟地址 且具有 rwx 权限向进程表明地址存在且可以访问,只不过不在内存里,表明 该代码数据被 唤出 到外设中了

回忆 C 语言里 char* str = "Hello World!"; 代码,字符串 "Hello World!" 究竟被存放在哪?或者是接下来添一句 *str = "H"; 能编译成功吗?
字符串 "Hello World!" 是在字符常量区的,是不能被修改的,添一句 *str = "H"; 会报错,那为什么不能被修改?
原因是每一个区都是经过页表映射,页表具有权限管理的 标记位,而你的单元只读那就只能只读,不能修改写入等等,写入操作会被直接拦截,保护了物理内存根本就没有被访问;一旦你可以进行写入了,就说明此操作就是合法的

写时拷贝

OS 层面如何支持 写时拷贝 呢?
以开篇现象为例:本来父进程的页表对于 g_val 的权限是 rw 的,父进程一旦创建了子进程,OS 就会修改父子进程页表中对该变量(g_val)的权限,修改为 r ,一旦父子进程有一个尝试写入时,系统会直接识别到错误,那么 OS 判断出错的原因:

  • 是不是数据不在物理内存:触发 缺页中断
  • 是不是数据需要写时拷贝:触发 写时拷贝
  • 不是以上情况,进行异常处理

而内存当中也会有 引用计数 来说明 g_val 被几个进程使用着,如果大于 1,就说明有别的进程也在用,触发 写时拷贝

粗浅认知虚拟地址

现在我们知道进程使用的地址都是 地址空间 里的 虚拟地址 ;在进程刚开始时,页表 需要将进程的 虚拟地址 和物理地址构建映射,那请问 页表虚拟地址 从哪里来呢?它怎么知道进程要使用什么地址呢?

当程序被编译为二进程可执行文件后,文件内还存在各种各样的函数名变量名吗?是不存在的,都被替换为了地址!!!所以 程序本身就有地址,这个地址就是 虚拟地址(逻辑地址)

所以 页表 里的 虚拟地址 直接从可执行程序读过来即可

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

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

相关文章

数据库应用

一、数据库基本概念 1、数据 &#xff08;1&#xff09;描述事物的符号记录称为数据&#xff08;Data&#xff09;。数字、文字、图形、图像、声音、档案记录等 都是数据。 &#xff08;2&#xff09;数据是以“记录”的形式按照统一的格式进行存储的&#xff0c;而不是杂乱…

OpenAI 神秘「草莓」项目;IBM 关闭中国研发部门;《黑神话:悟空》幕后演员曝光;升级 AI 搜索、AI 写作等功能|网易数智日报

IBM 关闭中国研发部门&#xff0c;涉及员工数量超过 1000 人 8 月 26 日&#xff0c;IBM 中国方面确认&#xff0c;IBM 将彻底关闭中国研发部门&#xff0c;涉及员工数量超过 1000 人。、IBM 中国在声明中称&#xff1a;「IBM 会根据需要调整运营&#xff0c;为客户提供最佳服…

我入局了!最新大模型应用场景案例,数字化转型新机遇!

前言 谈起企业数字化转型&#xff0c;听到最多的词就是&#xff1a; 踩坑、烧钱、价值体现难……导致数字化成为企业发展过程中鸡肋般的存在。离不开&#xff0c;但用不好。 相反&#xff0c;作为新风口&#xff0c;AI却逆势增长&#xff01;借助AI的数字化转型、AI场景落地…

有没有不花钱的变声器软件免费版?适用于新媒体人的8款变声器软件汇总!

随着直播及游戏行业的快速发展&#xff0c;许多人开始关注变声器软件的使用&#xff0c;尤其是那些想要在公众场合上不让别人认出的同时也能畅快地进行交流的用户。无论是为了娱乐、制作内容还是保护个人隐私&#xff0c;变声器软件为人们提供了许多便利。很多人都会问&#xf…

【计算机组成原理】五、中央处理器:1.CPU的功能与结构指令执行过程(运算器、数据通路、控制器、指令周期)

五、中央处理器 文章目录 五、中央处理器1.CPU的功能与结构1.1CPU功能1.2运算器1.2.1基本结构1.2.2 ALU和寄存器的数据通路 1.3控制器1.3.1基本结构1.3.2控制器功能 1.4CPU的基本结构 2.指令执行过程2.1指令周期2.2指令周期流程2.3数据流2.4指令执行方案&#xff1a;如何安排多…

P1-90螺栓产品特性及应用范围

P1-90螺栓是一种高强度、耐高温的特殊紧固件&#xff0c;因其优异的性能而在众多行业中得到了广泛应用。下面我们将详细介绍P1-90螺栓的产品特性及其应用范围。 产品特性 1. 材质与性能 P1-90螺栓通常采用优质合金钢材料制成&#xff0c;具有高强度、耐高温、抗腐蚀等特点。这种…

大模型为何能与人类交流?

大模型在接受到对话后&#xff0c;有4个步骤&#xff08;如下图&#xff09; ① 文本token化 ② 单词向量化、位置编码 ③ 自注意力 ④ 编码输出 我们接下来看看这几个步骤是怎么具体工作的。 1、文本token化、向量化embedding和位置编码 任何不同种族交流的前提就是&am…

【GD32 MCU 移植教程】从 STM32F10x 移植到 GD32E103的移植说明

1. 前言 对于使用微控制器进行产品开发的设计人员来说&#xff0c;因产品及功能升级&#xff0c;往往需要将一种微控制器替 换成另一种微控制器&#xff0c;在保留既有功能的情况下增加新功能。为了更快地推出新产品&#xff0c;设计人员经 常要将应用程序移植到新的微控制器…

苹果手机数据恢复方法有哪些?推荐3个快速恢复的方法

你是否遇到过这样的情况呢&#xff1a;苹果手机进水后&#xff0c;无法打开手机&#xff0c;导致手机里的数据不见了&#xff1b;苹果手机不见了&#xff0c;买新手机却没有以前的手机数据……面对这样的情况&#xff0c;如何恢复苹果手机的数据呢&#xff1f;别急&#xff0c;…

Java基础——自学习使用(抽象类)

一、抽象类的定义 在Java中&#xff0c;抽象类是一种不能被实例化的类&#xff0c;它主要用于被其他类继承。抽象类可以包含抽象方法&#xff08;没有方法体的方法&#xff0c;仅有一个方法签名&#xff0c;以abstract关键字声明&#xff09;和非抽象方法&#xff08;即具有实现…

【前沿技术】扩散模型Stable Diffusion原理与应用

Stable Diffusion是一种基于扩散模型&#xff08;Diffusion Models&#xff09;的生成技术&#xff0c;近年来在图像生成和其他生成任务中取得了显著的进展。该技术以其高质量的生成效果、稳定的训练过程和广泛的应用前景&#xff0c;迅速在学术界和工业界引起了广泛关注。 以…

C++入门基础知识38——【关于C++ 运算符——逻辑运算符】

成长路上不孤单&#x1f60a;【14后&#xff0c;C爱好者&#xff0c;持续分享所学&#xff0c;如有需要欢迎收藏转发&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

【PWN · 栈迁移 | one-read】[羊城杯 2024]pstack

从前的栈迁移&#xff0c;怎么也得泄露一个栈指针&#xff0c;或者对bss有两次及以上写的能力&#xff0c;这题过分精简&#xff0c;一时间失了分寸。。。好在信息检索到了解法&#xff0c;并动态调试了解了过程 前言 栈溢出长度不够如何利用——可以考虑栈迁移 如果从交互来…

想入门AI产品经理,你一定要明白这些!

前言 对于想要进入AI领域&#xff0c;特别是成为AI产品经理的朋友们来说&#xff0c;了解并掌握一些AI大模型的基础知识是非常重要的。接下来&#xff0c;我就用更通俗的方式&#xff0c;给大家介绍一下入行AI大模型所必备的几点知识。 一、AI大模型是啥&#xff1f; 简单来…

中秋佳节好物推荐:五款数码产品让你的节日更加精彩

中秋节是中华文化中最为重要的传统节日之一&#xff0c;它象征着团圆和丰收&#xff0c;也是我们与家人朋友欢聚一堂&#xff0c;共享美好时光的日子。除了团圆饭桌上的月饼和水果&#xff0c;我们还可以通过一些现代数码产品来为这个节日增添科技感和便利性。无论是提升个人生…

【机器学习】非线性降维、流形学习的基本概念、如何选择合适的非线性降维方法以及非线性降维的流形学习实例(含python代码)

引言 非线性降维是机器学习中用来处理高维数据的一种方法&#xff0c;特别是当数据包含复杂的非线性结构时 文章目录 引言一、非线性降维1.1 目的1.2 非线性降维方法1.2.1 核主成分分析 (Kernel PCA)1.2.2 局部线性嵌入 (LLE)1.2.3 等距映射 (Isomap)1.2.4 拉普拉斯特征映射 (L…

3秒AI写真出图,Stable Diffusion2024升级版+使用教程来了!(无需安装,解压即用)

要说今年摄影圈最大的新秀 那妥妥的就Stable Diffusion 比如下面的写真照片 你敢信这是SD绘画生成的&#xff1f; 就在刚刚它又全面升级了 新版无需安装&#xff0c;直接解压就能用 比之前推送的更加智能、快速和简单 另外还特意为大家准备了 Stable Diffusion 人工智能…

新书速览|Altium Designer 24入门与案例实践:视频教学版

本书内容 《Altium Designer 24入门与案例实践:视频教学版》以当前最新的板卡级设计软件Altium Designer 24为基础&#xff0c;全面讲述电路设计的各种基本操作方法与技巧&#xff0c;并演示两个大型综合实战案例。《Altium Designer 24入门与案例实践:视频教学版》配套示例源文…

RAG的基石:大语言模型文本向量化能力对比

什么是具象的高维向量空间&#xff1f;也许这是一个&#xff0c;在不同的平行空间里面&#xff0c;对不同的物体有各自的表示… 一、概述 大家都比较关心大语言模型的能力&#xff0c;但往往容易忽略其向量化&#xff08;Embedding&#xff09;的能力。在RAG应用中&#xff0c…

USB3202N多功能数据采集卡16位模拟量250K频率LabVIEW采集卡

品牌&#xff1a;阿尔泰科技 系列&#xff1a;多功能数据采集卡 概述&#xff1a; USB3202N多功能数据采集卡&#xff0c;LabVIEW无缝连接&#xff0c;提供图形化API函数&#xff0c;提供8通道&#xff08;RSE、NRSE&#xff09;、4通道&#xff08;DIFF&#xff09;模拟量输…