【Linux 11】进程地址空间

🌈 Ⅰ 虚拟地址引入

  • 现在通过一段代码来观察一个现象,定义了一个全局变量 g_val = 100,然后使用 fork 创建一个子进程,让父子进程各自完成任务,在子进程中定义 count 来计数,当子进程的打印任务执行到第五次之后,让子进程讲 g_val 的值改成 300。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int g_val = 100;

int main()
{
    printf("father is running, pid: %d, ppid: %d\n", getpid(), getppid());

    pid_t id = fork();  // 创建子进程

    if (0 == id)        // 执行子进程部分代码
    {   
        int count = 0;

        while (1)
        {
            printf("child process, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", 
                getpid(), getppid(), g_val, &g_val);
            sleep(1);   // 每间隔一秒打印一次
            count++;

            if (5 == count)
            {
              g_val = 300;
              printf("child process, change %d -> %d\n", 100, 300);
            }
        }
    }
    else if (id > 0)   	// 执行父进程部分代码
    {
        while (1)
        {
            printf("father process, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", 
                getpid(), getppid(), g_val, &g_val);
            sleep(1);   // 每间隔一秒打印一次
        }
    }

    return 0;
}

在这里插入图片描述

  • 在子进程修改 g_val 的值时,父子进程的 g_val 的地址时一样的,然而在子进程讲 g_val 的值改成 300 时,能够发现父进程的 g_val 的值依然是 100。

    • 变量内容不一样,所以父子进程输出的变量绝不是同一个变量。
    • 但子进程和父进程的 g_val 的地址是却相同,说明输出的 g_val 地址绝不是一个物理地址。在 Linux 地址下,这种地址被称作虚拟地址
    • 用 C/C++ 所看到的地址,全都是虚拟地址,物理地址由操作系统 OS 统一管理。
  • OS 负责将 虚拟地址 转化成 物理地址

🌈 Ⅱ 虚拟地址空间

  • 操作系统 OS 会为每一个进程创建一个地址空间,这是一个虚拟的地址空间。

在这里插入图片描述

🌈 Ⅲ 页表 (解释 fork() 的返回值既 > 0 又 == 0)

不对公有变量进行修改时

  • 操作系统 OS 需要将虚拟地址转化为物理地址,OS 可以通过页表将虚拟地址和物理地址建立映射关系
  • 建立了映射关系之后,上层在使用虚拟地址访问时,OS 会自动拿着虚拟地址查页表,然后转化成物理地址从而访问到代码和数据。
  • 在上述代码的父子进程都有属于自己的虚拟地址空间以及页表,只不过公用同一个虚拟地址罢了。
  • 在没有进程对 g_val 的值进行修改时,父子进程的页表映射的都是同一个 g_val 的地址。

在这里插入图片描述

对公有变量进行修改时

  • 操作系统 OS 会先找到一块地址将原先的变量内容拷贝一份下来,然后再对该内容进行修改。这个因为要发生修改而拷贝内容的操作称为写时拷贝
    • 假设值为 100 的 g_val 的物理地址为 0x112233,值为 300 的 g_val 的物理地址为 0x123456 对应的映射关系如下图所示:

在这里插入图片描述

  • 如上图所示:同一个变量,值不同却地址相同,其实是虚拟地址相同,内容不同是被映射到的物理地址不同
  • 所以这也就能解释为什么 fork() 返回的 id 值既可以 > 0,又可以 = 0

🌈 Ⅳ 什么是地址空间

  • 地址空间本质是内核的一个 struct 结构体,内部的很多属性都是表示 start 和 end 的范围。
  • 每个进程都有一个地址空间,系统中可能存在多个进程,每个进程通过地址空间来访问内存,因此系统中存在着多个地址空间。地址空间需要被 OS 管理起来
  • 一旦涉及到了管理,那么就要先描述,再组织

1. 先描述

  • 地址空间本身是一个结构体对象,在 Linux 中被称为 struct mm_strunt
  • 先用结构体描述出地址空间,该结构体包含着区域信息,用于实现地址空间区域的划分。
struct mm_struct
{
	......
};

2. 再组织

  • 进程 PCB 里有一个指向 mm_struct 的结构体指针,来将 PCB 和 mm_struct 联系起来。

🌈 Ⅴ 为什么要有地址空间

  • 如果没有虚拟地址空间,那么进程访问的就都是物理地址,每个进程都需要记录物理地址。

  • 进程直接访问物理地址,在发生异常情况时,就有可能破坏其他空间的代码和数据

  • 以下是页表 & 虚拟地址空间存在的价值:

1. 将无序变为有序

  • 经过页表的映射,将无序的物理地址变成有序的虚拟地址,以统一的视角看待物理内存,以及自己运行的各个区域。
  • 不需要再关心正文代码、初始化数据、非初始化数据等在物理内存的哪个位置,这些东西永远在虚拟地址空间的固定位置。

在这里插入图片描述

2. 提高内存使用率

  • 如果一个进程早期在物理内存申请了一块空间,但长时间不去使用,就会造成浪费。
  • 在页表中预先弄好虚拟地址,但不实际开辟这块空间,等到需要用到这块空间时,再去物理内存中开辟,然后将开辟好的物理空间地址与预先弄好的虚拟地址 映射即可。

3. 拦截非法请求 - 保护物理内存

  • 没有地址空间:进程就能够直接访问物理内存,没人能管着你,增加了发生异常情况的概率。
  • 有了地址空间:一个进程就只会映射自己的数据 ,如果去越界访问别人的空间,那么在页表中是找不到对应的映射关系的,OS 会直接进行终止。也就是说,只要进入到访问物理内存阶段,那么一定是安全的。
  • 107
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值