Linux—程序地址空间详解!

17 篇文章 1 订阅

1.程序地址空间概念

  • 程序:只是一段代码,保存在文件中。
  • 编译器在编译程序生成可执行文件时,会对每一条指令和数据,进行地址排号。
  • 程序运行时,就会将指令和数据放到指定的内存当中去。而程序只有在运行的时候才会占据内存,因此程序地址空间又被叫做进程地址空间

2.程序地址空间详谈

  • 1.为什么要有程序地址空间?
  • 2.程序地址空间有什么作用?

搞懂以上两个问题就可以清晰得了解到程序地址空间存在的意义

先来对比早期直接物理内存分配方式

2.1早期内存分配方式

  • 最早的时候,计算机还没有虚拟机制,程序指令所访问的内存地址就是物理内存地址,所以就要将所有程序都加载到内存中,但是我们实际的物理内存只有4G。所以就会出现一些问题
  • 内存使用效率低:内存空间不足,就需要将其他程序展示拷贝到硬盘当中,然后将新的程序装入内存。然而由于大量的数据装入装出,内存的使用效率会非常低
  • 进程地址空间不隔离:由于程序是直接访问物理内存的,所以每一个进程都可以修改其他进程的内存数据,设置修改内核地址空间中的数据,所以有些恶意程序可以随意修改别的进程,就会造成一些破坏
  • 程序运行的地址不确定:因为内存地址是随机分配的,所以程序运行的地址也是不正确的

2.1.1为什么要有程序地址空间?

正是因为早期这些问题得所在,所以诞生出程序地址空间来避免这些问题。
解决问题方法

  • 增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。

2.2程序地址空间分布

程序地址空间分布图
在这里插入图片描述

2.2.1程序地址空间有什么作用?

  • 通过虚拟地址空间映射到物理内存上。而使用C语言/C++时,变量或函数的地址,都是虚拟空间地址,物理内存地址用户一概看不到,由OS统一管理。而OS负责将虚拟地址映射到对应的物理地址
  • 每运行一段程序,就会开辟连续的地址空间,若是每个程序占据的空间比较大,很多程序共同运行,就会导致有的程序在内存中无法运行。而连续开辟的内存地址空间的空间使用率是很低的。而进程使用了虚拟内存之后,每个进程都拥有自己的虚拟地址空间,都会有一块连续的空间使用。

3.代码示例详谈进程地址空间

3.1代码一:

   1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 
  5 int g_val = 0;
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id<0)
 11     {
 12         perror("fork");
 13         return 1;
 14     }
 15     else if(id > 0)
 16     {
 17         printf("father[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
 18     }
 19     else
 20     {
 21         printf("child[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
 
 22     }
 23     sleep(1);
 24     return 0;
 25 }

输出结果:
在这里插入图片描述
结论:
父子进程里面的变量的值和地址是完全一样的,这很好理解,因为子进程是按照父进程为模板,子进程并没有对变量进行任何操作。

3.2代码二:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 
  5 int g_val = 0;
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id<0)
 11     {
 12         perror("fork");
 13         return 1;
 14     }
 15     else if(id > 0)//father
 16     {
 17         printf("father[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
 18     }
 19     else//child
 20     {
            g_val=100;//修改了g_val的值
 21         printf("child[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
 22     }
 23     sleep(1);
 24     return 0;
 25 }

输出结果:
在这里插入图片描述
结论:

  • 变量内容不一样。所以父子进程输出的变量绝对不是同一个变量
  • 但是地址是一样的,所以该地址一定不是物理地址
  • 在Linux下,这种地址叫做虚拟地址
  • 我们在c/c++语言所看到的地址,全都是虚拟地址,物理地址用户一概看不到,由OS统一管理
  • OS负责将虚拟地址转化为物理地址

3.3代码一和代码二得结论

为什么子进程变量改变了,而父进程的变量没有改变?

  • 子进程是父进程的一份拷贝,子进程拷贝了父进程所有的信息。在子进程中数据未发生改变的时候,子进程使用父进程的所有信息。
  • 在第一份代码中,子进程中变量没有改变,父进程中变量没有改变。所以第一份代码中,地址相等,变量也相等。
  • 第二份代码中,子进程中变量发生了改变,父进程中变量没有发生改变。相同的虚拟地址映射到了不同的物理地址。所以第二份代码,地址相同,变量不同。

虚拟地址空间带了什么好处?

  • 提高物理内存的使用率。
  • 保证进程之间的独立性

4.操作系统中管理内存的不同方法:

分段;分页;段页式

在这里插入图片描述

4.1分段

分段式:段号+段内偏移
在这里插入图片描述

  • 段表:操作系统记录内存分了多少块。通过段号寻找对应的物理内存起始地址,再加上段内偏移量,就找到了物理地址。

4.2分页

分页式:页号+页内偏移
在这里插入图片描述

  • 通过找到对应的页号,其物理地址和页内偏移就可找到变量的物理地址。

4.3段页式

  • [ ]段页式:内存通过分段式进行管理,而每个段内使用分页式。
    首先取得段号,在段表中进行查找;在段表中,存放着对应段号的页表起始地址,再通过段内页表起始地址找到页表。
  • 当前计算机使用的段页式管理

5.结论

  • 上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值