进程地址空间


前言

我们在之前学习过一张表,里面存放着栈,堆,常量区等,我们认为这就是内存。
其实,更具体来讲,应该是进程地址空间,我们一起来了解一下吧!!!

一、程序地址空间

我们在之前学习过这张图
在这里插入图片描述
我们可以写一段代码验证一下

#include <stdio.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main(int argc, char *argv[], char *env[])
{
    printf("code addr: %p\n", main);
    printf("init data addr: %p\n", &g_val);
    printf("uninit data addr: %p\n", &g_unval);

    char *heap = (char*)malloc(20);
    char *heap1 = (char*)malloc(20);
    char *heap2 = (char*)malloc(20);
    char *heap3 = (char*)malloc(20);
    static int c;
    printf("heap addr: %p\n", heap);
    printf("heap1 addr: %p\n", heap1);
    printf("heap2 addr: %p\n", heap2);
    printf("heap3 addr: %p\n", heap3);

    printf("stack addr: %p\n", &heap);
    printf("stack addr: %p\n", &heap1);
    printf("stack addr: %p\n", &heap2);
    printf("stack addr: %p\n", &heap3);
    printf("c addr: %p, c: %d\n", &c, c);

    for(int i = 0; argv[i]; i++)
    {
        printf("argv[%d]=%p\n", i, argv[i]); //?
    }
    for(int i = 0; env[i]; i++)
    {
        printf("env[%d]=%p\n", i, env[i]);
    }

    return 0;
}

在这里插入图片描述

我们可以观察到这样的现象

命令函参数环境变量,无论是表,还是表里的内容,都是在栈区上面的。
static静态变量,不初始化默认是0.

我们再来看一下下面这段代码

#include <unistd.h>
#include <sys/types.h> 
int g_val = 100;
int main()
{
       pid_t id = fork();
       if (id < 0)
       {
             perror("fork");
        
               return 0;
        }
       //子进程
       else if (id == 0)
        {
             printf("child:id:%d,pid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);

        }
       else
       {
             printf("parent:id:%d,pid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); 
        }
       return 0;
}

在这里插入图片描述

运行的结果我们很好理解,子进程创建是以父进程为模板进行创建的,父子并没有对变量进行修改。打印的内容和地址都是相同的,父子代码共享。
发生写时拷贝数据各自开辟一份,私有一份。

如果我们对数据进行修改了呢??

#include <unistd.h>
#include <sys/types.h> 
int g_val = 100;
int main()
{
       pid_t id = fork();
       if (id < 0)
       {
             perror("fork");
        
               return 0;
        }
       //子进程
       else if (id == 0)
        {
             g_val=200;
             printf("child:id:%d,pid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);

        }
       else
       {
             printf("parent:id:%d,pid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); 
        }
       return 0;
}

在这里插入图片描述
子进程对数据进行了修改,发生了写时拷贝,数据被修改成了200,打印的结果我们很好理解。
但是地址为什么还是相同的呢??

变量内容不一样,父子进程输出的变量绝对不是一个变量。
地址值是一样的,这绝对不是物理地址。
我们在Linux下,这种地址称为虚拟地址。
我们在C/C++看到的地址,都是虚拟地址。物理地址是由操作系统进行管理的,用户看不到。

虚拟地址和物理地址之间的转化由操作系统完成。

二、进程地址空间

我们之前学到那张图应该叫做进程地址空间,
本质上是内存中的一种内核数据结构,在Linux中进程地址空间具体由结构体mm_struct实现

在这里插入图片描述
每个进程被创建,对应的PCB和mm_struct.也随之被创建。操作系统可以通过进程的PCB找到mm_struct.。因为PCB中有一个结构体指针指向这个mm_struct.

folk之后,父进程创建子进程,子进程也要拥有自己的PCB和地址空间,子进程和父进程代码数据共享。
地址空间和物理内存之间存在一个页表,里面存放的是物理地址和地址空间的映射关系。
我们就可以通过地址空间里面的地址,借助页表的映射关系,在物理内存中找到对应的数据和代码。

当子进程对数据进行修改本质是在物理内存中发生写时拷贝,开辟一块同样大的空间,把数据拷贝过去,再进行修改,同时修改子进程与物理内存的映射关系。

我们为什么要拷贝呢??直接修改不就行了??
我们的对数据的修改可能完成这样的动作:g_val++。

经过上面的解释,我们也可以进一步理解为什么folk之后两个返回值不同了。

操作系统要不要对地址空间组管理呢??
先描述,在组织。
进程地址空间是数据结构,具体到进程中,就是特定数据结构的对象。

我们为什么要进行写时拷贝??

进程之间具有独立性,多进程运行,需要独享各种资源,多进程运行互不干扰,不能让子进程的修改影响父进程。

为什么不在创建子进程时就将数据拷贝

子进程不一定使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要读数据进行拷贝,我们应该按需分配,提高内存资源的利用率。

我们划分为不同的区域,堆,栈等等。
可以判断是否越界,可以扩大或者缩小范围。

进程地址空间就像一把尺子,尺子刻度由0x00000000到0xffffffff,尺子按照刻度被划分在不同区域。而结构体mm——struct中,便记录了各个边界的刻度
在这里插入图片描述
每个刻度都代表一个虚拟地址,这些地址通过页表映射与物理内存建立联系。

堆向上增长和栈向下增长就是为了改变mm_struct中堆和栈的边界刻度
区域划分的本质:区域内每个地址都可以使用

CPU中存在一个叫做CR3的寄存器,这个寄存器保存到就是页表的起始地址。

我们为什么要有进程地址空间??

1.有了进程地址空间,就不会有任何系统级别的越界问题存在了。例如进程1不会错误的访问到进程2的物理内存空间,因为你对地址空间进行操作之前需要先通过页表映射到物理内存,而页表只会映射到属于你的物理内存。
2.有了进程地址空间后,每个进程都被认为自己独占内存,这样能更好地完成进程的独立性以及合理使用内存空间,将进程调度和内存管理进行解耦和(两边互不干扰,各干各的)
3.用于保护内存安全
4.有了进程地址空间后,每个进程都认为看的是相同的空间范围。我们在编写程序时只需要关注虚拟地址,而不用关系数据在物理内存中的实际存储位置。

我们之前学过的malloc和new本质是在进程的虚拟地址空间中申请,等用的时候,再到物理内存中开辟相应的空间。
操作系统,一定要为效率和资源利用率负责

总结

以上就是今天要讲的内容,本文仅仅详细介绍了Linux进程地址空间的内容。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lim 鹏哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值