【Linux杂货铺】3.程序地址空间

1.程序地址空间的引入

fork()函数在调用的时候子如果是子进程则返回0,如果是父进程则返回子进程的pid,在代码中我们分别在子进程和父进程读取全局变量g_val的时候居然出现了俩个不同的值。如下:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int g_val = 0;
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    g_val = 100
    int i = 5;
    while(i--)
    {
      printf("我是子进程,g_val值为:%d,g_val的地址:%p\n",g_val,&g_val);
      sleep(1);
    }
  }
  else
  {
    int i=5;
    while(i--)
    {
      printf("我是父进程,g_val值为:%d,g_val的地址为:%p\n",g_val,&g_val);
      sleep(1);
    }
  }
  return 0;                                                                                                                        }

执行结果:

xvjiyi@hecs-399428 20240712_pragramAddressSpace$ ./mytest

我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964

我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964

我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964

我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964

我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964

我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

这个时候我们发现明明读取的地址相同为什么会读出俩个不同的值呢?既然读出了俩个值,说明这俩个值存在不同空间里,定然不可能地址相同,唯一的解释就是这个地址不是真实的地址。事实也正是这样,这里读取到的地址是**虚拟地址,**虚拟地址空间也就是程序地址空间的产物,接下来我们来看程序地址空间,以及虚拟地址的实现机制。

2.程序地址空间

2.1虚拟地址空间介绍

程序地址空间本质是一个结构体(我们认为是 struct mm_struct),这个结构体记录着不同内存区间的在这个程序地址空间的相对地址(不是真正的物理地址),映射关系如下:

2.2页表

当进程访问地址的时候访问的是虚拟地址,那我们最终是怎样访问到物理地址的呢?当进程地址空间创立时,操作系统会建立一张页表完成虚拟地址到物理地址的映射,如下:

2.3 子进程父进程访问全局变量出现俩个不同值

接下来我们回到开头的问题,为什么我们会在同一个地址上访问到俩个不同的值。这是因为子进程继承了父进程的地址空间,父子进程在访问这个全局变量时访问的虚拟地址是一致的,而这俩个虚拟地址映射的物理地址不同,而我们的数据是存放在物理内存中的,也就造成了同一个地址出现了俩个不同值的假象。(至于为什么会存放在不同的物理内存中在下一节进程控制中给大家讲解,为了确保进程的独立性相互之间不影响,发生了写时拷贝)。

3.扩展

3.1为什么要有地址空间

如果没有进程地址空间,物理内存就可以被随意访问,可以随意改动不属于自己的空间,系统安全性大大降低。我们有了地址空间有以下优点:

  1. 1.当进程访问到不属于该进程的空间时,操作系统可在页表映射时拒绝进程的访问,有效杜绝了物理地址被随意访问,大大保证了物理内存和其他进程的安全。
  2. 2.将进程管理与内存管理解耦合。
  3. 3.让进程以统一的视角看待自己的代码和数据。

3.2 malloc申请空间的实质

当进程使用malloc申请空间时,在访问页表的时候操作系统不会给你立马分配物理地址,而会发生缺页中断,在进程需要访问这块空间时才给你分配空间。

3.3程序的加载

程序编译在加载的时候并不是一股脑全部加载到物理内存中,而是执行多少加载多少,一边执行一边加载。(源代码在被编译时早就将源代码和数据按照程序地址空间的方式完成了编址)

相关推荐
weixin_6042366734 分钟前
华三 路由器 极简核心配置
运维·服务器·网络·h3c·h3c路由器
鹤落晴春1 小时前
【Linux复习】管理SELinux安全性
linux·运维·服务器
yz_aiks1 小时前
Linux Jar包配置Systemd自启动实战:从排查到配置全流程
linux·python·jar·自启动·systemd
AI智图坊1 小时前
多件装组合SKU图的批量生产效率分析:从PS手工到AI自动化的工作流改造
大数据·运维·人工智能·gpt·ai作画·自动化·aigc
bjzhang753 小时前
CentOS下安装MySQL详解
linux·mysql·centos
Jason_chen4 小时前
Linux 6.2 音频机制深度解析:AI驱动的低延迟音频与零信任音频安全架构
linux
下午写HelloWorld4 小时前
Linux系统及Ubuntu常用指令
linux·ubuntu·操作系统
lizhihai_995 小时前
股市学习心得-AI 产业链核心标的梳理清单
大数据·服务器·人工智能·科技·学习
云计算磊哥@6 小时前
运维开发宝典026-MySQL02数据库表操作
运维·数据库·运维开发
weixin_523185326 小时前
Collections.unmodifiableMap详解:真的不可修改吗?
java·linux·前端