Linux:程序地址空间的详解


程序地址空间回顾

在学习C/C++的时候,我们对程序地址空间的认识基本上是酱紫的!

在这里插入图片描述

程序地址空间是什么?

  • 程序地址空间:进程的虚拟地址空间

  • 程序中访问的地址都是虚地址

  • 地址:内存区域的编号 (程序是不占用内存的,运行起来的程序被加载到内存,才会占用内存)

示例代码:

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

int val =100;
int main() {
    int ret = fork();
    if(ret < 0){
        perror("fork");
		return 1; 
	}
    else if(ret == 0){
        printf("I am child : %d!, ret: %d\n", getpid(), ret);
        val = 1;
        printf("child:val = %d, &val = %p\n",val, &val);
    }
    else{
        printf("I am father : %d!, ret: %d\n", getpid(), ret);
		printf("father:val = %d, &val = %p\n", val, &val);

    }
    sleep(1);
    return 0;
}

代码运行图:
在这里插入图片描述

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

OS必须负责将 虚拟地址 转化成 物理地址

扫描二维码关注公众号,回复: 11337961 查看本文章

进程地址空间

所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?

  • 我们在进程中所访问到的地址实际都是一个虚拟地址,而我们所说的程序地址空间实际上是一个进程的虚拟地址空间

如何虚拟一个内存空间?为什么要虚拟一个内存空间

  • 程序地址空间->进程地址空间->虚拟地址空间

  • 为了能够实现不让进程直接访问物理内存

  • 直接使用物理内存会导致:(内存利用率低,缺乏内存访问控制

    1. 进程中代码数据的使用都是连续的地址,若直接使用连续的物理内存会造成内存的浪费
    2. 直接访问物理内存会因为缺乏内存访问控制导致进程的不安全
      在这里插入图片描述
  • 进程使用虚拟地址空间,通过页表映射物理内存,可以实现进程中数据在物理内存上的离散式存储(提高内存利用率),并且使用 页表 映射虚拟地址与物理地址的映射关系,并且在页表中可以实现内存访问控制(标志位表示内存的访问权限)
    在这里插入图片描述

虚拟地址空间是什么:

本质: 虚拟地址空间其实就是一个结构体—mm_struct,操作系统通过这个mm_struct结构体向进程描述了一个连续的,线性的,完整的地址空间(空有地址编号,但是不具备存储)

目的: 为了告诉进程,拥有所有的内存,可以随意使用;

如何通过虚拟地址访问物理内存?

操作系统为进程创建虚拟地址空间(mm_struct)的同时,也创建了页表用于映射虚拟地址与物理地址的关系(如下图)

分页 & 虚拟空间地址

在这里插入图片描述

说明:

  • 同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!
  • 每个进程都有一个自己的进程地址空间
  • 创建子进程 - 父进程代码共享,数据独有

附:

  • 写实拷贝技术:两个进程一开始指向同一块空间,等待发生改变的时候,再给子进程重新开辟空间—目的是提高子进程创建效率

如何通过虚拟地址得到物理地址

  1. 分页式内存管理

将虚拟地址进行分页管理,通过页表映射虚拟地址页与物理内存块之间的关系,用于实现数据离散式存储,提高内存利用率

虚拟地址的组成: 页号+页内偏移

  • 页号:页表中页表项的编号,
  • 页内偏移:具体一个变量首地址相较于内存页起始位置的偏移量

优点:将物理内存进行分块管理,通过页表映射实现数据在物理内存上的离散式存储,提高内存利用率。

物理内存块号 * 物理内存块大小+虚拟地址中的页内偏移 = 物理地址

32位操作系统—寻址空间大小总共就是32位

假设内存大小 4G,页大小 4096 字节,意味着页号所占的位是 2^20,也就是说虚拟地址的高 20 位都是页号,低 12 位就是页内偏移。

4096字节为 1页,4G / 4096Byte = 1024*1024,也就是 2^20.

  1. 分段式内存管理

对虚拟地址进行分段管理,对于编译器的内存管理非常方便

虚拟地址的组成: 段号+段内偏移

优点:使程序员对内存的管理更加方便,将内存空间分为了代码段、初始全局段…; 什么变量就在什么位置申请地址

  1. 段页式内存管理

每个分段都有一个页表,通过地址中的段号,找到段表项,通过段表项中段内页表起始地址找到自己的页表,通过地址中的段内页号,在这个页表中找到页表项,通过页表项中的物理块号 + 页内偏移得到最终的物理地址

虚拟地址的组成:段号+段内页号+页内偏移(先分段,在每个段内采取分页)

优点: 集合了分页和分段式的优点

编译器在编译程序的时候就会为每一个指令以及数据进行地址的分配(一个程序使用哪些地址在编译连接完成后就已经定下了)

缺页中断:

内存只有8G,运行的程序的数据和代码都是在内存中,意味着运行的程序多了,有可能内存就不够用了;

磁盘分区有两种:

  • 交换分区
  • 文件系统分区

交换分区

作为交换内存使用

当内存不够用的时候,这时候,其实内存并不是所有的数据都是活跃数据;这时候操作系统会根据一定的算法,将某块内存中的数据保存到磁盘的交换分区中。腾出这块内存加载新数据

LRU算法: 最久未使用 – 内存中最久没有被访问过的数据放到交换分区

操作步骤 :

  1. 每个内存的页表中记录了每一个虚拟内存对应的物理地址
  2. 如果某个虚拟页面的物理内存中的数据被交换了出去,保存到交换分区,则将这个页表项置为缺页中断
  3. 等待下次这个进程要访问这个被交换出去的数据的时候(这个数据当前不在内存中,保存在交换分区中)
  4. 触发缺页中断,重新从交换分区将数据交换回来

如有不同见解,欢迎留言讨论~~

猜你喜欢

转载自blog.csdn.net/AngelDg/article/details/106678693