当前位置: 首页 > news >正文

【linux】程序地址空间

目录

一、前言

二、进程地址空间

三、扩展

四、总结


一、前言

        我们在学习C/C++的时候或多或少都会提到关于内存分布的相关知识,例如静态成员变量存放在静态区,局部变量存放在栈区等等,那么这些空间是怎么进行排布的呢?我们在运行程序时是直接根据数据在内存的地址来调用的吗?

二、进程地址空间

程序地址空间也可以叫做进程地址空间,因为程序运行起来就是一个进程。

先来看一张空间布局图

这是我们平时为了更好理解而虚构出来的空间布局,实际上这些数据和代码并不是按照这种排布在内存中的,下面会说明为什么。

我们先来看一段代码,并理解运行结果为什么是这样。

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
int val = 100;    
int main()    
{    pid_t id = fork();    if(id==0)    {  //子进程  sleep(1);    val+=5; //修改val                                                                                                                                                                     printf("child:pid %d val=%d addr=%p\n",getpid(),val,&val);    }    else    {    //父进程sleep(1);    printf("father:pid %d val=%d addr=%p\n",getpid(),val,&val);    }    return 0;    
}    

1.还可以发现他们之间val的地址是相同的,为什么会有这种现象呢?

首先这个地址肯定不是物理地址,如果是物理地址的话,指向相同空间,一方改变另一方也会发生改变。

在linux下这种地址被称为虚拟地址,并且我们写的C/C++程序中的地址也是虚拟地址,也就是说系统会根据虚拟地址进而转换位物理地址对数据进行访问,有一张对应虚拟地址与物理地址一一对应的表,在操作系统中这个表被称为页表。

为什么父进程与子进程的val的地址相同,因为创建的子进程会将父进程的代码和数据进行拷贝,当然也就把地址啥的都拷贝下来了,类似于浅拷贝,所以我们看到的地址是虚拟地址并且相同。

2.通过观察可以发现变量val在子进程中已经被改变了但是父进程中val的值未被改变。

每个进程都会有自己独立的虚拟地址空间,也有自己对应的页表。

我们上面讲的进程地址空间其实就是虚拟地址空间,是操作系统为了更好的管理并排列好每个区域的数据(例如常量在常量区等等)。使用虚拟地址就能够更好的进行区域划分,那么操作系统是怎么进行区域划分的呢?

在操作系统的task_struct中会维护着指向mm_struct的指针,而mm_struct就是进行虚拟地址空间区域划分(管理)的。

在mm_struct中定义的各个区域的范围,通过更改这些变量就可以对区域进行划分和修改。

    unsigned long total_vm, locked_vm, shared_vm, exec_vm;unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;

在mm_struct中还存在着 

这个是用来管理每个区域的,例如管理栈区的地址范围,相当于是一个别墅的管家。

这个时候我们再来解释为什么子进程与父进程的的val互不影响。

我们知道进程之间是相互独立的,当某个进程对数据进行改变时就会发生写实拷贝,写时拷贝的触发条件就是当某个进程对数据进行修改时。

写实拷贝的过程:

所以我们看到的现象就是这样形成的。

三、扩展

系统怎么对页表进行管理:先描述,再组织

系统会维护一个关于页表信息的结构体,类似于对进程的管理一样的操作。

页表是用来做虚拟地址与物理地址的映射的。

我们可以不先加载代码和数据,只有task_struct,mm_struct等等,因为系统是通过查页表来进行访问数据和代码的,可以先有虚拟地址,然后再将物理地址一个一个填入页表中。

为什么我们对常量进行修改,程序缺崩溃了?因为在页表中还会维护着对每个数据的权限信息,如果我们对该数据进行的非权限访问或修改,系统就会做出相应的操作。

四、总结

为什么系统要使用虚拟地址空间?直接对物理内存的数据进行操作不行吗?

如果直接对内存进行操作,我们创建子进程时,就必须对父进程的数据和代码进行拷贝到内存,这样就会造成空间浪费。

使用虚拟地址空间的话,创建子进程只对会发生修改的数据进行拷贝到内存进行了。

可以让进程管理与内存管理,进行一定的解耦合(使两边的关联变浅)。

地址空间和页表是OS创建并维护的,凡是想使用地址空间和叶表进行映射, 也⼀定要在OS的监管之下来进行访问!!也顺便保护了物理内存中的所有的合法数据 ,包括各个 进程以及内核的相关有效数据!

因为有了虚拟地址空间,那么数据在内存中的存储位置也就没那么重要了。

相关文章:

  • AI聊天多分支对话的纯前端实现
  • 19、RocketMQ核⼼编程模型
  • Nestjs框架: nestjs-schedule模块中的三类定时任务
  • 同样是synthesis(综合) HLS和Vivado里面是有什么区别
  • 商品中心—15.库存分桶扣减的技术文档
  • Hyperledger Fabric 入门笔记(二十)Fabric V2.5 测试网络进阶之Tape性能测试
  • 3.Stable Diffusion WebUI本地部署和实践
  • 论分布式设计
  • 基于Redis分布式的限流
  • springboot整合微信App支付以及支付宝App支付
  • opensuse安装rabbitmq
  • 使用 Azure 创建虚拟机所涉及的各项资源和设置
  • 探秘 VR 逃生救援技术的奇妙世界​
  • 鸿蒙HarmonyOS 5小游戏实践:记忆翻牌(附:源代码)
  • docker stats和/proc/pid/status内存统计的差异问题
  • 生成式人工智能实战 | WGAN(Wasserstein Generative Adversarial Network, GAN)
  • GO 语言学习 之 变量和常量
  • 【git学习】学习目标及课程安排
  • React Native 如何实现拉起App
  • Spring Boot 3.2.11 Swagger版本推荐