Liunx 进程地址空间

进程地址空间

因为 window 像是 VS 会对这些空间进行一系列的操作,导致我们设置的变量看上去不遵循以下的规则,所以使用 Linux 做演示

在这里插入图片描述

进程地址空间不是我们常说的内存

地址空间

引子

有这样一段代码:

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

int g_val = 100;  //设置一个全局变量
int main(){
    pid_t ret_id = fork(); //创建子进程
    if(ret_id == 0){
        int count = 0;
        while(1){
            printf("I'm child progress   id:%d  g_val: %d  &g_val:%p\n",getpid(), g_val, &g_val);
            sleep(1);
            count++;
            if(count == 2){
                g_val = 200;
                printf("child change g_val 100 -> 200\n");
            } 
        }
    }
    else{
        while(1){
            printf("I'm father progress  id:%d  g_val: %d  &g_val:%p\n",getpid(), g_val, &g_val); 
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述
输出结果显示,父子进程 g_val 变量共用一块地址,但在子进程修改 g_val 后父进程打印的 g_val 却未发生改变(同一个地址在同时读取时竟读到了不同的值)

  1. 同时读取到的变量内容不同,所以父子进程输出的变量绝对不是同一个变量
  2. 但地址值是一样的,说明,该地址绝对不是物理地址(不是硬件上具体的某个位置)

● 由此,我们引出虚拟地址(线性地址)的概念

虚拟地址(线性地址)

● 我们学习的语言中所谓 “地址” 的概念,基本指的都是虚拟地址

● 不止CPU 有寄存器,磁盘、网卡等外设一样有寄存器,但他们结构各不同,实际各部件摆放的位置(物理地址)散布各处,显然不是线性结构。
而现在,计算机将各硬件的寄存器也好,内存也好,全都看作内存,通过虚拟地址去映射它们的位置。那么之后要写入读取时就不需要用散布各处的物理地址,而是用线性排布的虚拟地址

● 没有虚拟地址(以前确实如此),有什么坏处
其一:用户直接访问物理地址,那就是想访问哪里就访问哪里(毕竟物理内存本身可以被随时读写),如果出现野指针问题……不安全

验证地址空间(虚拟地址空间)分布

写完才发现前面的图里写的差不多了……烦内

#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 global addr: %p\n", &g_val); 
	printf("uninit global addr: %p\n", &g_unval); 
	
	char* heap_mem1 = (char*)malloc(10);
	char* heap_mem2 = (char*)malloc(10);
	char* heap_mem3 = (char*)malloc(10);
	
	printf("heap addr1: %p\n", heap_mem1);
	printf("heap addr2: %p\n", heap_mem2);
	printf("heap addr3: %p\n", heap_mem3);
	
	printf("stack addr1: %p\n", &heap_mem1);
	printf("stack addr2: %p\n", &heap_mem2);
	printf("stack addr3: %p\n", &heap_mem3);

    int i = 0;
	for(i=0;i < argc; i++){
		printf("argv[%d]:%p\n", i, argv[i]);
	}

	for (i = 0; env[i]; i++) {
		printf("env[%d]: %p\n", i, env[i]);
	}
    return 0;
}


在这里插入图片描述

  1. 为什么动态内存开辟 10 字节,堆中的变量却相隔了 32 字节:
    多出来的空间用于记录本次申请的属性(像是开空间时可以直接从命令中获取 开10字节 的信息,释放空间时则需要依靠这些属性去释放相应大小的空间)

  2. 关于常量

如果我们再加入一段代码:

    const char* str = "12345678";
    printf("string constant: %p\n",str);

在这里插入图片描述
可以看到该字符串常量的位置和 main 函数地址相近
● 常量所处位置也在代码区(而且代码、常量均具有只读性)

虚拟地址与物理地址

● 进程地址空间(虚拟地址空间)如何管理才能便于进程使用?—— 先描述后组织
即进程地址空间本质是一种数据结构 mm_struct:
在这里插入图片描述
框出的部分为对其中区域的划分

进程的虚拟地址与物理地址的关系

在这里插入图片描述

虚拟地址空间存在的意义

1. 禁止非法访问
● 有了地址空间,非法的访问或映射会被 OS 识别到并终止该进程(虚拟地址空间、页表是由 OS 创建并维护的,所以想要使用地址空间和页表就一定在 OS 的监管之下)
2. 解耦合
● 因为有地址空间与页表映射关系的存在,数据可以加载到物理内存的任意位置(操作系统只管访问虚拟地址空间,具体的物理地址是什么不重要,这是页表的事),这就使得进程管理模块和内存管理模块之间没有关系,完成了解耦合(各模块之间关联越少,耦合度越低,维护成本越低)

进而让进程管理、内存管理能够独立设计
在C、C++语言上new、malloc空间时,本质是在哪里申请 —— 虚拟地址空间
如果直接在物理内存上申请空间,如果不马上使用,则造成空间浪费
但有了地址空间后,我们在地址空间上申请,直到我们真正使用(访问物理地址空间)时,才执行内存相关算法,申请内存空间,构建页表映射关系
——延迟分配的策略,提高了整机的效率(通过 “缺页中断” 实现)

3. 实现进程独立性
● 物理内存理论上能进行任意位置的加载,所以物理内存中的代码、数据基本都是乱序
但因为有页表的存在,从进程视角看,内存(在虚拟地址上)分布是有序的
一方面,这实现了上面的解耦合(内存乱关我进程地址空间什么事)
另一方面,不同进程访问内存时:
在这里插入图片描述
因为地址空间的存在,每个进程都认为自己有 4GB 大小空间(32位),且各区域有序

只要保证每一个进程的页表,映射的是物理内存的不同区域,就能实现进程间互不干扰

相关问题

1. 为何之前父子进程在同一地址读取到了不同的值
子进程会继承父进程的大部分属性(包括地址空间和页表),所以刚开始父子进程访问的是同一个值。但当子进程想要修改该变量时,为保证进程的独立性,页表的映射关系会发生改变(映射到了另一块物理内存),而原本的虚拟地址不会变化,于是出现了上述现象

而这种直到修改变量时才去修改映射关系以改变数据的操作称为:写时拷贝

2. fork 如何返回给一个变量两个不同的值

pid_t id = fork();

在 return 之前父子进程就已创建完毕,所以 return 的本质,即是对 id 进行写入
发生写时拷贝,父子进程各自在物理内存中有属于自己的变量空间,只是在用户层用了同一个变量(虚拟地址)来标识

3. 可执行程序的虚拟地址

程序内部有地址码?
有,程序内部需要地址标定代码中的逻辑关系、函数入口

所以不仅是操作系统,编译器同样遵循地址空间的分布。在编译代码时,编译器就已经形成了各区域,给每一个变量、每一行代码进行了编址(程序在编译时,每一个字段就已具有了一个虚拟地址)

程序加载到内存时,这些虚拟地址则会映射到物理内存上

我们写的程序调用函数时需要通过函数名(函数地址)跳转到相应函数去执行,那么程序运行时当 CPU 读到该位置时,这是个物理地址还是虚拟地址?
—— 虚拟地址,原本编译好的虚拟地址不会变化,CPU调用无非是通过该地址跳转到相应的位置(虚拟地址),再通过映射寻找物理地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值