进程地址空间详解

1.地址空间

如下图地址空间排布图( 实际上字符常量去和代码区统一在正文代码区内,只不过字符常量区在代码区上面)

进程地址空间详解_c语言_小赵小赵福星高照~-DevPress官方社区

1.1 验证地址空间的分布

下面给出打印各个区域地址的代码

#include <stdio.h>    
#include <malloc.h>    
int g_val = 10;    
int ung_val;    
    
    
    
int main(int argc, char* argv[], char* env[] )    
{    
  int i =0;    
  int j = 0;    
  for (; env[i]; i++)    
  {    
    printf("env[%d]:%p\n", i, env[i]);//环境变量    
  }    
  for (; j < argc; j++)    
  {    
    printf("argcv[%d]:%p\n", j, argv[j]);//命令参数                                                                                                                                                                                                                                     
  }    
  int stack = 10;    
  int * heap = (int*)malloc(10);    
  const char * ch = "abcd";    
  printf("stack addre:%p\n", &stack);//栈区    
  printf("heap addre:%p\n", heap);//堆区    
  printf("ungval addre:%p\n", &ung_val);//未初始化变量    
  printf("g_val addre:%p\n", &g_val);//已初始化变量    
  printf("const char addre:%p\n", ch);//字符常量    
  printf("code addre:%p\n", main);//代码区    
    
    
  return 0;    
}

下面是代码运行结果可以很明显的看出地址是由高到低的也验证了上面图中地址的排布

env[0]:                 0x7ffeb50417fa
argcv[0]:              0x7ffeb50417f0
stack addre:        0x7ffeb5040b74
heap addre:         0x1242010
ungval addre:       0x601044
g_val addre:         0x60103c
const char addre: 0x40077a
code addre:          0x40057d

1.2什么是地址空间

实际上我们这里所说的地址空间都是虚拟的地址空间,而不是真正的物理内存,为什么呢下面给出一个实例。

这个进程使用fork()函数创建了一个子进程,当创建子进程成功时frok函数返回子进程的pid号给父进程,返回0给子进程。你没听错这里fork返回了两个值给ret(后面会解释)。

  #include <stdio.h>    
  #include <unistd.h>    
  #include <sys/types.h>    
  #include <stdlib.h>    
      
  int g_val = 100;    
      
  int main()    
  {    
    pid_t pid = getpid();    
    pid_t ret = fork();    
    if (ret < 0)    
    {    
      printf("子进程创建失败\n");    
      return 1;    
    }    
    else if(ret == 0)    
    {    
      g_val = 200;    
      printf("g_val change to 200\n");    
      printf("Im child pid:%d ppid:%d g_val:%d %p\n", getpid(), getppid(), g_val, &g_val);    
    }    
    else{    
      printf("Im father pid:%d ppid:%d g_val:%d %p\n", getpid(), getppid(), g_val, &g_val);                                                                                                                                                                                   
      sleep(2);    
    }    
      
      
    return 0;    
  }

这段代码运行结果如下

Im father pid:16533 ppid:13382 g_val:100 0x60105c
g_val change to 200
Im child pid:16534 ppid:16533 g_val:200 0x60105c

可以看到同一个地址空间的变量g_value居然有不同的值。如果说我们所说的地址空间是真实的物理内存的地址那么,怎么可能会出现同一个地址出现两个值呢,所以可以得出结论我们这里所讨论的是虚拟地址。

父进程和子进程共享代码与数据,当数据被修改时,发生写时拷贝,也就是在物理内存上为子进程重新开辟一个空间存储这个要被修改的数据,然后将这个数据修改,这样一个变量也就有了两个值。同理fork的返回时,也是发生了写时拷贝,frok的return函数调用两次,分别给子进程父进程返回不同的值。

1.3现代地址空间设计

进程的创建,首先将代码加载到物理内存中,系统创建进程地址空间结构体TCB(tast_struct),并开辟内存块资源mm_struct,其中task_struct结构体内有一个指针指向地址空间,虚拟内存内的数据通过页表的映射关系就能找到对应的物理内存内存储的代码数据。页表是怎么生成的呢,当进程数据加载到物理内存,系统创建了虚拟内存地址,通过物理内存与虚拟地址的对应关系创建页表。

1.4为什么要这么设计地址空间

历史上,曾是可以直接访问物理内存,但是这么做是特别不安全的,内存本身是随时可以被读写的,假设我们有两个进程A,B,A进程修改了物理内存内的某个可以影响B进程的数据,那么就可能导致B进程异常。(这样可不行进程是具有独立性的)

这样设计的优点

  • 非法访问或者映射可以被系统识别到,并终止进程。(因为地址空间和页表是由操作系统创建并维护的)
  • 数据可以在物理内存中的任意位置加载,我们可以通过页表这个关系映射图将,在物理内存中乱序的数据,有序化。
  • 物理内存的管理与进程管理做到了解耦合,也就是两者的管理分开,不会互相影响。
  • 申请内存空间(new,malloc空间)时可以在虚拟内存上申请,只需要在用户对物理空间访问时我们再执行内存相关的管理算法,申请物理内存,构建页表映射关系。这样就达成了延时申请,使得内存不被浪费。

1.5 补充

当我们程序在编译形成可执行程序,没有被加载到内存中时,已经形成了虚拟内存地址。

cpu中读取的地址也是虚拟地址。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值