【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程序的加载

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

相关推荐
运维-大白同学18 分钟前
将django+vue项目发布部署到服务器
服务器·vue.js·django
糖豆豆今天也要努力鸭25 分钟前
torch.__version__的torch版本和conda list的torch版本不一致
linux·pytorch·python·深度学习·conda·torch
烦躁的大鼻嘎34 分钟前
【Linux】深入理解GCC/G++编译流程及库文件管理
linux·运维·服务器
乐大师34 分钟前
Deepin登录后提示“解锁登陆密钥环里的密码不匹配”
运维·服务器
ac.char41 分钟前
在 Ubuntu 上安装 Yarn 环境
linux·运维·服务器·ubuntu
敲上瘾41 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
长弓聊编程1 小时前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.1 小时前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
梅见十柒1 小时前
wsl2中kali linux下的docker使用教程(教程总结)
linux·经验分享·docker·云原生
Koi慢热2 小时前
路由基础(全)
linux·网络·网络协议·安全