【Linux】进程地址空间

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、程序地址空间

32位系统地址空间布局图

在这里插入图片描述

那么我们曾经学习语言时所谈的地址是真实的物理地址吗我们用代码验证

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

int g_val;

int main()
{
 pid_t id = fork();
 if (id == 0)
 {
   printf("子进程[%d], g_val = %d, &g_val = %p\n",getpid(), g_val, &g_val);
 }
 else if (id > 0)
 {
   printf("父进程[%d], g_val = %d, &g_val = %p\n", getpid(), g_val, &g_val);
 }
 else {
   perror("fork失败");
   exit(EXIT_FAILURE);
 }
 sleep(1);
 return 0;                                                                                                                                                                                                  

}

在这里插入图片描述

输出的变量值和地址是一模一样的这是很好理解的因为子进程按照父进程为模版父子并没有对变量进行任何修改。现在将代码稍作改动

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

int g_val;

int main()
{
 pid_t id = fork();
 if (id == 0)
 {
   g_val = 99;//子进程修改g_val
   printf("子进程[%d], g_val = %d, &g_val = %p\n",getpid(), g_val, &g_val);
 }
 else if (id > 0)
 {
   sleep(3);
   printf("父进程[%d], g_val = %d, &g_val = %p\n", getpid(), g_val, &g_val);
 }
 else {
   perror("fork失败");
   exit(EXIT_FAILURE);
 }
 sleep(1);
 return 0;                                                                                                                                                                                                  

}

在这里插入图片描述

子进程会先跑完也就是子进程先修改g_val完成之后父进程再读取。我们发现父子进程输出地址是一致的但是变量内容不一样
由此能得出如下结论

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的说明该地址绝对不是物理地址因为物理硬件内存只有一套物理地址是唯一的
  • 在Linux地址下这种地址叫做虚拟地址
  • 我们在用C/C++语言所看到的地址全部都是虚拟地址。物理地址用户一概看不到由OS统一管理

二、进程地址空间

所以之前说‘程序的地址空间’是不准确的准确的应该说成 进程地址空间

进程地址空间是计算机系统中一个非常重要的概念它给每一个进程提供了一个假象每个进程都在独占地使用内存。

这是操作系统这个”管理者“给每个进程画的大饼实际上每一个进程能使用的真实物理空间是有限的。每一个进程所看到的地址空间是一样的这个空间称为虚拟地址空间或进程地址空间就是文章刚开始那张图。

那么操作系统如何“画好这个大饼”先描述再组织

先将地址空间用一种数据结构描述起来再对其进行组织管理。所以地址空间的本质是内核的一种数据结构在Linux中是一个名为mm_struct的结构体而每个进程地址空间的区域划分也是通过这个结构体来完成的

在这里插入图片描述

通过这个结构体系统就可以知道这个进程各个区域的使用情况如果要对区域进行调整如在进程对应代码中定义局部变量new在堆上开辟空间导致栈区、堆区扩大free缩小堆区域本质上是修改对应区域的end或start。

在进程的内核数据结构task_struct中又有一个结构体指针指向mm_struct这样每个进程就和其进程地址空间唯一对应

在这里插入图片描述


三、页表和MMU

简单理解

页表是一个存放在内存的数据结构页表可以将虚拟地址映射到物理地址页表的内容由操作系统维护存在多级页表如一级页表映射到二级页表二级页表再映射到物理页。

MMU内存管理单元Memory Management UnitCPU上的一种硬件主要用于虚拟地址和物理地址之间的转换地址翻译每次地址翻译都会读取页表。所以CPU看到的也是虚拟地址访问数据时CPU把虚拟地址给MMUMMU再去页表中拿到物理地址。

在这里插入图片描述

在这里插入图片描述

同一个变量地址相同其实是虚拟地址相同内容不同其实是被映射到了不同的物理地址。

平常查看汇编代码时也会看到地址这个地址也是虚拟地址OS和编译器都遵守这套规则。在程序跑起来成为进程前就已经有了虚拟地址。


五、重新认识fork

既然我们了解了虚拟内存和内存映射那么我们可以知道fork函数为什么会有两个返回值即给父进程返回子进程PID给子进程返回0。

当前进程调用fork时内核会为新进程子进程做很多工作

  1. 创建PCBtask_struct
  2. 创建地址空间mm_struct
  3. 完成数据的赋值
  4. 创建并设置页表等

fork函数返回时子进程和父进程的虚拟内存相同返回的本质是写入当两个进程任一个后来返回的顺序是不确定的进行写操作时写时拷贝机制创建新的物理空间。id的地址相同内容不一样。

pid_t id = fork();

六、虚拟内存的意义

  • 安全性

    1. 如果让进程直接访问内存进程可能越界非法操作虚拟内存保护了每个进程的地址空间不被其他进程破坏。
    2. 虚拟内存降低了进程和进程数据代码的耦合性保证了进程的独立性。
  • 便捷性
    虚拟内存为每个进程提供了一致的地址空间从而简化了内存管理方便使用。


如有错误望指正。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux