【Linux】进程的虚拟地址空间

news2024/12/28 6:12:57

文章目录

      • 现象引入
      • 进程地址空间
      • 进程地址空间的描述
      • 进程地址空间是怎么产生的
      • 进程地址空间的好处
      • 对开篇问题的解释

现象引入

我们运行下面一段代码:

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

int global_val = 100;

int main()
{
    pid_t id = fork();
    int count = 0;
    if (id == 0)
    {
        while (1)
        {
            printf("我是子进程, global_val=%d, &global_val=%p\n", global_val, &global_val);
            sleep(1);
            count++;
            if (count == 6)
            {
                global_val = 200;
                printf("global_val已修改\n");
            }
        }
    }
    else if (id < 0)
    {
        perror("fork");
        return 1;
    }
    else 
    {
        while (1)
        {
            printf("我是父进程, global_val=%d, &global_val=%p\n", global_val, &global_val);
            sleep(2);
        }
    }
}

代码设置了一个全局变量 global_val = 100,

并设置了一个子进程,

子进程运行一段时间后会修改全局变量 global_val = 200,

子进程和父进程都会打印 global_val 和它的地址,

那么修改前后父子进程的打印内容有什么变化呢?

看下面的运行结果:

image-20230204214939416

可以看到,全局变量被子进程修改后,

子进程后续打印的 global_val 就是修改后的值,

而父进程却没有发生变化!

最诡异的是,它们的地址都还一样!

所以由此可见,

这里所谓的地址并不是实实在在的物理地址,即给内存的存储地址编的地址,

而是虚拟地址!

那什么是虚拟地址,为什么要有虚拟地址,虚拟地址和物理地址有什么关系,又有所谓线性地址、逻辑地址,它们又是什么?

下面就对这几个问题进行探讨。


进程地址空间

通过上述现象可以看出,

打印出来的地址是虚拟地址,

实际上,在学习C语言的过程中我们可能有了解过内存分区,

栈区、堆区、静态区、代码区等等…

image-20230211221141535

仔细想一下,如果是采用32位编址方式,

那么能编址的大小范围就是0 ~ 232byte = 4GB,

如果是采用64位编址方式,

那么能编制的大小范围就是0 ~ 264byte = 2147483648GB,

而实际上,我们电脑的内存并不是固定的4GB或2147483648GB(太夸张了)。

同时,因为像声明定义变量、调用函数开辟栈帧、动态内存分配是在进程运行的过程中执行的,

所以像堆区和栈区实际上是在进程运行的过程中才有的,

所以这个所谓的内存分区实际上就是进程地址空间,并且是虚拟的进程地址空间。

并且每个进程都有自己一套独立的地址空间。


进程地址空间的描述

我们知道进程地址空间有代码区、初始化和未初始化数据区、以及栈区堆区什么的…

我们也知道操作系统用进程控制块(PCB)去描述和维护进程,

那进程的地址空间应该也要维护起来,

所以进程控制块中应该还有描述进程地址空间的结构。

而怎样以一种较为简单的方式描述进程地址空间呢?

小时候上学的时候书桌都是两个人用一张的那种,

调皮的同桌经常会在桌上画一个线说不能越过这根线,

所以这根线就将空间分成了两块,

一块是我的,一块是他的。

假设桌子长一米,分割线在桌子中间,

那么就可以规定[0, 50cm]是我的空间,[50, 100cm]是同桌的空间,

如此便完成了课桌空间的规划。

所以对于一块4GB的空间,

我们同样可以用begin和end来划分空间:

struct mm_struct
{
	//uint_32_t就是32位无符号整型,正好符合32位编址,本质就是unsigned_int
	uint_32_t code_start, code_end; //用来记录代码区的开始和结束
	uint_32_t data_start, data_end; //用来记录数据区的开始和结束
	uint_32_t heap_start, heap_end;
	uint_32_t stack_start, stack_end;
	//...
}

实际上mm_struct是linux内核中实实在在的一个结构类型,

是进程控制块task_struct的一个成员。


进程地址空间是怎么产生的

我们写了一份C语言代码,

然后预编译、编译、链接形成可执行程序,

可执行程序加载到内存中成为进程,

从而有了一份进程地址空间。

但是进程地址空间是凭空产生的吗?

思考一下,

C语言代码在链接的过程中会有函数调用、第三方库调用等一系列操作,

那在调库或调用函数的时候编译器是怎么找到目标对象在哪呢?

实际上,我们的代码在编译的过程中就已经有了一套地址,

而这套地址就是所谓的逻辑的地址。

所以在硬盘里存放的可执行程序已经自带了一套地址,

在可执行程序加载到内存中成为进程的时候,

这套自带的地址就变成了一套进程的虚拟地址,

在linux下经过算法处理最终在mm_struct中存放。

最终通过一种名为页表的数据结构实现由虚拟地址到物理地址的映射。


进程地址空间的好处

让每个进程拥有了相同的、独立内存空间,相互之间不会干扰。

就比如一开始的那个例子,

一个地址看似存放了两个值,

实际上是父子进程两套不同的虚拟地址,

只不过是一个相同的地址值映射到了不同的物理地址。

另外,想象一下(以下纯属个人瞎扯),

如果每个进程都有一套虚拟地址,

那么每个进程都不需要考虑占用其他进程的空间,

在进程看来,内存空间的空间全部都是自己的,

内存地址都是连续的,使用起来更加方便,

至于映射不连续的物理地址的事情就都交给页表去做就好了。

读写内存更安全。由于系统和页表的限制,使得进程无法操作到其他进程的数据。

很好理解,如果一个进程能随意去访问物理地址,

会是一件很危险的事情。

比如有一个进程会存放我们登陆时输入的账号密码信息,

这时另一个进程突然来访问,

我们的信息不就被盗走了嘛。

对于逻辑地址或虚拟地址有32位或64位的编址方式,

不止是操作系统在管理进程或进程空间的时候会遵守这个规则,

实际上编译器在编译代码的时候也遵循这个规则,

而且是通用的规则,

所以程序中已经存在的逻辑地址可以很方便地转化为进程虚拟,

这样使得整个过程更加方便统一。


对开篇问题的解释

32位编址方式下每个进程都能拿到4GB的虚拟空间,

其中0~3GB是用户空间,存放代码、数据等等,

因为要保证进程的独立性,所以各个进程的映射是独立的;

3~4GB是内核空间,由于只有一个操作系统,

内核空间主要是共用的机器指令、操作系统内核的各个模块等,

因此每个进程的映射方式一样。

当fork创建子进程时,

在创建阶段由于父子进程的代码和数据都是一样的,

所以对子进程只需要给它分配新的内存块和内核数据结构,

将父进程的部分数据拷贝给它,包括父进程的页表数据!

所以在创建完子进程之后,

它的所有代码和数据都是和父进程共享的!

这也解释了为什么同一个变量在父子进程中有相同的地址,

本质是子进程继承了父进程的虚拟地址空间。

但是一旦父子进程要对数据进行写入,

为了保证进程的独立性,

你对你的旧数据写入不能影响我的旧数据,

此时就会发生写时拷贝,

在物理内存上分配一小块空间拷贝旧数据,

然后对新空间上的旧数据进行写入,

页表修改对应的映射关系,

此时两个进程中两个相同的虚拟地址就映射到了两个不同的物理地址。

修改前:

image-20230213175756631
修改后:

image-20230213180032496

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

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

相关文章

根据 Jupyter-lab 源码实现 notebook(.ipynb)在页面中的渲染

前言 最近因为工作项目的需要&#xff0c;要在项目中尽可能的还原notebook渲染效果。由于网上没找到相关的指导文章&#xff0c;所以只能生啃JupyterLab源码&#xff0c;独自摸索实现。经过一段时间“跌跌撞撞”的摸索尝试&#xff0c;总算勉强实现了。 因此编写此文章做一下…

转转微服务容量管理实践

1 背景2 容量管理的目标3 发展阶段4 容量管理4.1 容量水位4.2 资源容量优化4.3 集群容量4.4 压测指标4.5 压测标准5 扩容、缩容6 总结1 背景 随着转转业务的不断发展和用户不断增长&#xff0c;公司持续增加对硬件和基础设施的投入&#xff0c;用于满足业务发展的需要&#xff…

计算机网络8-在浏览器中输入URL后会发生什么

参考&#xff1a; 在浏览器中输入URL并按下回车后会发生什么&#xff1f; DNS域名详细解析过程 1.URL解析拿到域名 当用户输入URL并回车后&#xff0c;浏览器对拿到的URL进行识别&#xff0c;抽取出域名字段&#xff0c;比如https://www.baidu.com,它的域名就是www.baidu.com…

SQL数据库根据需求发送邮件

一、启用数据库邮件 手动启用数据库邮件功能&#xff0c;需执行以下脚本&#xff1a; exec sp_configure show advanced options,1 RECONFIGURE exec sp_configure Database Mail XPs,1 RECONFIGURE With Override 二、邮件服务器设置 1.邮箱启用设置-POP3/IMAP/SMTP/Exch…

DAMA数据管理知识体系指南之数据质量管理

第12章 数据质量管理 12.1 简介 数据质量管理是组织变革管理中一项关键的支撑流程。业务重点的变化、公司的业务整合战略&#xff0c;以及并购与合作&#xff0c;都对IT职能提出了更高要求&#xff0c;包括整合数据源、创建一致的数据副本、交互提供数据或整合数据。与遗留系…

SpringAOP理解实现方式

Aop 什么是Aop&#xff1f; AOP就是面向切面编程&#xff0c;通过预编译方式以及运行期间的动态代理技术来实现程序的统一维护功能。 什么是切面&#xff0c;我理解的切面就是两个方法之间&#xff0c;两个对象之间&#xff0c;两个模块之间就是一个切面。假设在两个模块之间…

9.手动部署Java应用

Jenkins部署Java应用什么java应用手动部署java环境、手动进行代码发布过程1.环境准备配制负载均衡配制webserver&#xff08;tomcat&#xff09;集群本地做域名劫持查看效果2.模拟开发提交Java代码-->推送至gitlab上传代码至gitlab3.运维克隆代码&#xff0c;然后通过maven手…

Yolo-fastestv2训练自己的数据集记录

Yolo-fastestv2训练自己的数据集记录 第一节&#xff1a;代码来源 本机环境&#xff1a;ubuntu20&#xff0c;cuda,cudnn,pytorch1.11.0 代码来源&#xff1a;https://github.com/dog-qiuqiu/Yolo-FastestV2 配置环境后先测试一下环境 终端输入&#xff1a; python3 test.py…

Vue入门介绍

一、背景 目前前端主流框架有Vue、react、Angular等&#xff0c;其中Vue简单易学&#xff0c;只要稍微会点HTML、CSS、JavaScript基础就能很快上手Vue&#xff0c;其门槛低&#xff0c;上手速度快的特点&#xff0c;深受测试开发同学喜爱&#xff0c;已逐渐成为测开必备的前端…

spring回显方式在代码层面的复现(内存马系列篇十四)

前言 在前面的一章中&#xff0c;主要在理论上进行了各种内存马的实现&#xff0c;这里就做为上一篇的补充&#xff0c;自己搭建反序列化的漏洞环境来进行上文中理论上内存马的注入实践。 这是内存马系列文章的第十四篇。 环境搭建 可以使用我用的漏洞环境 https://github…

一款基于java的超级棒的开源支付系统(用来毕设也不错),国内首款开源的互联网支付系统

最近就快要到年末了&#xff0c;小编想着应该会有很多公司开始冲年度的业绩了&#xff0c;既然是冲业绩&#xff0c;就离不开我们的支付系统&#xff0c;所以小编就去网上给大家找到了一款超级棒的开源支付系统&#xff01;帮助大家从头到尾了解清楚这其中的逻辑&#xff01;所…

蓝牙 - 芯片制造商的代号编制以及在Windows上查看

在蓝牙技术的规范中&#xff0c;对很多信息都进行了整理和代号分配&#xff0c;比如生产蓝牙芯片的厂商&#xff0c;也进行了数字编号。 有一个专门的“Assigned Numbers”的PDF文档&#xff0c;记录了蓝牙规范中的各种类型数字所表示的含义。 本文介绍的数字类型&#xff0c…

JavaScript Window - 浏览器对象模型

JavaScript Window - 浏览器对象模型 浏览器对象模型 (BOM) BOM&#xff1a;Browser Object Model 是浏览器对象模型&#xff0c;BOM由多个对象构成&#xff0c;其中代表浏览器窗口的window对象是BOM的顶层对象也是核心对象&#xff0c;其他对象都是该对象的子对象。 BOM对象…

IB-PYP幼儿十大素质培养目标

作为IB候选学校&#xff0c;一直秉承IB教育的核心目标&#xff0c;贯彻在幼儿的学习生活中。IB教育之所以成为当今国际教育的领跑者&#xff0c;最主要的原因是IB教育是切切实实的“全人”教育&#xff0c;“素质”教育&#xff0c;拥有一套完整的教学服务体系。当我们走进IB“…

【机器学习实战】七、梯度下降

梯度下降 一、线性回归 线性回归算法推导过程可以基于最小二乘法直接求解&#xff0c;但这并不是机器学习的思想&#xff0c;由此引入了梯度下降方法。本文讲解其中每一步流程与实验对比分析。 1.初始化 import numpy as np import os %matplotlib inline import matplotli…

C语言(结构和指针)

目录 1.声明结构指针 2.用指针访问成员 3.传递结构成员 4.传递结构的地址 5.传递结构 6.机构的其他特性 7.结构中的字符数组和字符指针 关于为什么要使用指向结构的指针。 第一&#xff0c;就像指向数组的指针比数组本身更容易操作一样&#xff0c;指向结构的指针通常比…

5年自动化测试,终于进字节了,年薪30w其实也并非触不可及

我的职业生涯开始和大多数测试人一样&#xff0c;开始接触都是纯功能界面测试&#xff0c;第一份测试工作就是在电商公司做功能测试&#xff0c;工作忙忙碌碌&#xff0c;每天在各种业务需求学习和点点中度过&#xff0c;过了好几年发现自己还只是一个功能测试工程师&#xff0…

第十二章 Ambari二次开发之集成Alluxio

1、Alluxio高可用部署 生产环境&#xff1a;使用具有高可用性的模式来运行Alluxio masters。 1.1、Alluxio架构 ​ Alluxio可以被分为三个部分&#xff1a;**masters、workers以及clients。**一个典型的设置由一个主服务器、多个备用服务器和多个worker组成。客户端用于通过S…

机器学习实战--梯度下降法进行波士顿房价预测

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下如何使用机器学习梯度下降法进行波士顿房价预测&#xff0c;这是简单的一个demo&#xff0c;主要展示的是一些小小的思路~ 本文目录&#xff1a;一、波士顿房价预测1.全部的数据可视化2.地理数据可视化3.房…

基于”PLUS模型+“生态系统服务多情景模拟预测实践

工业革命以来&#xff0c;社会生产力迅速提高&#xff0c;人类活动频繁&#xff0c;此外人口与日俱增对土地的需求与改造更加强烈&#xff0c;人-地关系日益紧张。此外&#xff0c;土地资源的不合理开发利用更是造成了水土流失、植被退化、水资源短缺、区域气候变化、生物多样性…