11 进程地址空间

研究背景:kernel 2.6.32 32位平台


进程地址空间

在这里插入图片描述
在之前进程地址空间我们学习的是用户空间
在这里插入图片描述

可以通过下面的代码验证空间分布:

#include<stdio.h>
#include<stdlib.h>        
int g_val=100;    
int g_unval;        
int main(int argc,char*argv[],char*env[])    
{
 printf("代码区:%p\n",main);    
  printf("全局初始化区:%p\n",&g_val);    
  printf("全局未初始化区:%p\n",&g_unval);    
    
  char*mem=(char*)malloc(10); 
  printf("堆区:%p\n",mem);    
 
  printf("栈区:%p\n",&mem);    
 
  printf("命令行参数首地址:%p\n",argv[0]);
  printf("命令行参数尾地址:%p\n",argv[argc-1]);
  printf("环境变量地址:%p\n",env[0]); 
}

在这里插入图片描述

对于父子进程,它们的变量地址是相同的:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 0;
 }
 else if(id == 0){ //child
  printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }else{ //parent
 printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }
 sleep(1);
 return 0;
}

在这里插入图片描述

但是如果此时通过子进程对变量进行修改:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 0;
 }
 else if(id == 0){ //child
  g_val=100;
  while(1)
  {
     printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
     sleep(1);
  }
 
 }
 else{ //parent
   while(1)
  {
     printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
     sleep(1);
  }
 }
 return 0;
}

在这里插入图片描述

可以看到这里的全局变量只有子进程被修改了,而父进程的全局变量并没有被修改,这也就说明了虽然父子进程的全局变量地址是相同的,但并不是同一块物理地址

综上,我们可以得到下面的结论:

  • 进程地址空间并不是真实的内存,如果是内存那子进程就可以修改父进程的全局变量。
  • 既然不是真实的物理内存地址,那么我们在语言层面上打印出来的地址,都叫做“虚拟地址”。
    而真实的物理地址,用户是看不到的,由操作系统统一管理
  • 子进程继承父进程的虚拟地址,所以父子进程访问的数据虽然虚拟地址的地址号相同,但是都被保存到了不同的物理内存中
  • 只有物理内存有保存数据的能力。

虚拟地址

操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来

如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。

所以对于上面的程序,一开始父子进程的虚拟地址映射的是同一块物理内存地址,但是当子进程尝试写入的时候,操作系统就会将子进程映射到另一块物理内存地址上,这样即使子进程对变量进行了修改也无法影响父进程。在这个过程中,子进程的虚拟地址是没有发生改变的,变的只是子进程虚拟地址和物理内存地址的映射关系。

在这里插入图片描述

为什么要有虚拟地址

如果没有虚拟地址,那么我们进行访问的地址都是物理地址。

  1. 由于进程地址空间不隔离,程序都是直接访问物理内存,所以恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。
  2. 存在很多情况,一个进程的数据存放的内存位置是不连续的,所以访问会不方便,并且增加了异常越界的概率。
  3. 程序运行的地址不确定。当内存中的剩余空间可以满足程序的要求后,操作系统会在剩余空间中随机分配一段的空间给程序使用,因为是随机分配的,所以程序运行的地址是不确定的。

至于如何映射,则是通过页表来完成:
一开始父子进程映射的都是同一块物理内存:
在这里插入图片描述

当父进程或子进程需要修改数据时,会将父进程的数据在内存当中拷贝一份,然后再进行修改,再映射到另一块物理内存,这种方式叫做写时拷贝
在这里插入图片描述

通过这种方式,一个程序就没法越界访问另一个程序的地址空间了,因为页表中没有这种映射关系。
另外,之所以有的数据是只读的,有的数据是可读可写的,是因为页表当中对每个地址都有相关的权限管理

补充内容

虚拟地址空间存的价值:1.通过虚拟内存将空间连续化处理,2.保护内存

为什么要使用写时拷贝:进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程。

为什么不在创建子进程时就进行拷贝:子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。

什么是地址空间:地址空间的本质是一个数据结构,它里面包含的是区域信息,从而实现区域划分。
申请空间的本质:向内存索要空间,得到物理地址,然后在特定区域申请没有被使用的虚拟地址,建立映射关系,返回虚拟地址。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

今天也要写bug、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值