Linux进程地址空间

目录

前言引入:

一、进程地址空间

1.1 OS分配进程地址空间

1.1.1 感性理解进程地址空间

1.1.2 OS管理进程地址空间

1.2 进程地址空间区域

1.2.1 地址空间概念

1.2.2 地址区域划分与调整

 二、内存与地址空间

2.1 内存的地址空间

2.2 页表映射

2.3 写时拷贝 (非常重要)

三、进程地址空间与代码执行

3.1 进程地址空间与编译器

3.2 逻辑地址贯穿代码执行

四、进程地址空间存在意义


前言引入:

一直以来我们学习C/C++离不开地址这个概念, 地址空间排布划分了很多不同的区域,今天我们来升深度探讨一下进程的地址空间,下面是C/C++地址空间。



 这里的C/C++地址空间是内存吗? 答案:不是!下面我们用代码来证明一下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int global_val=100;
int main()
{
  pid_t id=fork();
  if(id<0)
  {
    printf("create child process error!\n");
  }
  else if(id==0)
  {
    int cnt=0;
    while(1)
    {
     printf("我是子进程,我的id=%d | global_val=%d, &global_val=%p\n",getpid(),global_val,&global_val);
     cnt++;
     if(cnt==5)
     {
      global_val=300;
      printf("我是子进程,global_val的值改变啦!!!\n");
     }
     sleep(1);
    }
  }
  else
  {
    while(1)
    {
      printf("我是父进程,我的id=%d | global_val=%d, &global_val=%p\n",getpid(),global_val,&global_val);
      sleep(1);
    }
  }
  return 0;
}

 我们可以发现,之前全局变量都一样,但是当我们在子进程中修改了全局变量,子进程的全局变量的值确实变了,但是父进程的变量没变!但是global_val的地址却是一样的!

这就说明父子进程的地址空间并不是内存!内存空间是物理地址!物理空间存储的变量值是唯一的!

结论是这里打印出来的地址不是内存地址而是叫作虚拟地址!下面我们来讲讲这个虚拟地址空间--进程地址空间!


一、进程地址空间

OS给每个进程独立的进程地址空间!每个进程自己认为自己独立占有整个内存

1.1 OS分配进程地址空间

1.1.1 感性理解进程地址空间

一个大富翁有十个亿个人资产(内存)!它有三个儿子(三个进程),其中一个是名正言顺的儿子,另外两个是私生子!三个儿子都不知道对方的存在!大富翁希望自己三个儿子都能努力,他首先给每个儿子一部分自己的钱(创建进程地址空间),然后给每个儿子一个叮嘱,如果X儿子你能...,我就把自己的遗产全部给你!(给进程画饼,让其认为独自占有内存)  三个儿子都认为自己独占未来父亲的资产!每个儿子如果想要<亿级别的钱,大富翁都会答应给(手动开辟空间 malloc/new)  

1.1.2 OS管理进程地址空间

操作系统对每个进程的地址空间做了管理!

OS给每一个进程创建进程地址空间的同时,都会记录开辟给这个进程的内存空间大小!这样OS就会知道内存还剩多少空间!也能为后来的进程空间分配有效调节!

1.2 进程地址空间区域

1.2.1 地址空间概念

1.地址空间的基本空间大小是字节

2.32位下,地址空间最多形成的地址为2的32次方,空间大小=4GB!

3.每个地址具有唯一性!我们用数据描述地址,用32个bit位0/1来编址

4.地址范围为0x0000 ~ 0xFFFF

1.2.2 地址区域划分与调整

我们只要知道了起始地址,就能划分一个地址区域!

进程PCB内有一个结构体struct mm_struct描述区域数据!区域的集合是连续的地址,也就是线性地址的集合!操作系统对每一个进程的mm_struct管理从而对地址空间管理!

调整区域只要更改起始地址大小即可!堆和栈的开辟空间相当于对区域的调整!

下面我们来看看mm_struct结构体:


 二、内存与地址空间

2.1 内存的地址空间

通过上文我们知道地址空间不是内存!下面我们浅谈一下内存:

内存如何划分区域?32位下,他的存储是4GB连续空间,称为page(页)。内存可以看作一个连续数组,32位下数组元素个数为page mem[4GB/4KB];

2.2 页表映射

页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表!地址空间的虚拟地址与内存的物理地址通过页表进行映射!

有了页表映射,我们就能理解父子进程的虚拟地址虽然一样,但是它们的映射关系不同,所以映射对应的物理内存不同!


2.3 写时拷贝 (非常重要)

父进程创建子进程,子进程会拷贝父进程的代码和数据!所以父子进程会共享全局变量global_val!父子进程全局变量的虚拟地址相同,映射关系也相同,所以父子进程全局变量的值相同!

进程具有独立性!子进程想要修改共享数据OS会修改子进程页表的全局变量的映射关系,然后在内存另开一块空间给子进程的全局变量!这就是写时拷贝!这就是为什么父子进程虽然地址(虚拟地址)相同,但它们页表映射不同,全局变量物理地址不同!所以两者值不同!

所以 进程独立性 = 进程内核数据结构  + 进程对应的代码和数据的独立性!


三、进程地址空间与代码执行

3.1 进程地址空间与编译器

当我们编译代码的时候,地址空间已经存在了!程序经过编译,编译器会给代码进行编址(这里地址称之为逻辑地址,也是虚拟地址)!汇编指令中函数调用Call命令通过地址找到对应函数!

3.2 逻辑地址贯穿代码执行

代码本身也是数据,代码在进程地址空间的代码区!代码编译链接形成可执行程序前,编译器已经给代码编址!也就是说代码指令内就存在了逻辑地址!

当程序运行变成进程后,程序代码被加载到内存中,OS建立页表映射关系!程序的代码区起始代码(main函数)的逻辑地址放入CPU寄存器,CPU拿到了代码开始的逻辑地址,然后通过页表映射找到物理地址,然后将下一个代码指令(指令本身存在逻辑地址)放入CPU寄存器!一直到程序代码结束,就这样我们的CPU就执行了一条条代码!

CPU整个过程中看到了物理地址了吗? 没有!CPU全部拿到的是逻辑地址,逻辑地址贯穿了我们CPU处理代码的全过程!



四、进程地址空间存在意义

①.进程地址空间保证了进程的独立性

②.进程地址空间保障了内存数据安全性!

我们通过页表映射,通过虚拟地址间接访问物理地址!如果我们直接访问内存数据,一步到位不是更好? 如果进程直接访问内存,假如一个恶意进程通过扫描的方式想要窃取或者篡改内存数据,造成的后果很严重!而进程地址空间的存在让进程不直接与内存打交道,页表的存在会拦截虚拟地址的非法访问!这样我们就能保障内存数据的安全性!

③.进程地址空间的存在让所有进程以统一视角来看待进程代码和数据的各个区域!

④.进程地址空间的存在让编译器以统一视角来进行编译代码!


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值