关于进程虚拟内存

举报
仙士可 发表于 2023/06/26 17:12:08 2023/06/26
【摘要】 内存和系统大家都知道,内存在计算机中是有限资源,它大概是一个这样的东西:在计算机中,根据内存条容量,从而转换成了一个以8位为1字节的大数组:系统通过访问具体的内存地址,获取具体存储的二进制值,从而实现读写内存数据为什么需要虚拟内存由于内存数据是固定的一个大数组,而操作系统往往是运行多个程序,如果这些程序都直接访问内存数组的话,就出现了以下问题:1:每个进程需要的内存都是变动的,可能需要1G,...

内存和系统

大家都知道,内存在计算机中是有限资源,它大概是一个这样的东西:

在计算机中,根据内存条容量,从而转换成了一个以8位为1字节的大数组:

系统通过访问具体的内存地址,获取具体存储的二进制值,从而实现读写内存数据

为什么需要虚拟内存

由于内存数据是固定的一个大数组,而操作系统往往是运行多个程序,如果这些程序都直接访问内存数组的话,就出现了以下问题:

1:每个进程需要的内存都是变动的,可能需要1G,可能需要2G,也可能需要10k,如果预先申请的过多,就会导致其他进程无法申请内存,但是如果申请的过少,又会需要频繁申请

2:在频繁申请时,内存地址不固定,每个进程都得管理自身不连续的内存段,非常麻烦

3:如果所有进程在同一时间都需要申请内存,就会造成读写冲突

这个时候,就需要用到虚拟内存了 

虚拟内存

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

虚拟内存做了以下事情:

1:每个进程拥有自己的独立虚拟内存空间,在进程看来,整个地址是连续的

2:在实际内存不足时,进程依旧可以申请内存(将使用磁盘空间存储)

3:在进程克隆后,将通过 "写时复制" 技术,只复制虚拟空间,不复制实际内存(只有写的时候复制一份),实现内存利用最大化

4:可以将共享对象映射到实际内存空间,多个进程读取自身的虚拟空间,映射相同的共享内存空间

5:进程在申请虚拟空间时,并没有实际分配内存空间,而是只有在实际使用时才会进行分配

内存管理单元 MMU(Memory Management Unit).

现在由于进程使用的是虚拟内存,所以操作系统需要将虚拟内存地址映射到物理内存中,通过MMU进行内存映射.

MMU内存管理由cpu实现,cpu如果为32位,则只支持2^32=4GB内存的映射,而64位这是8TB的内存映射

页表

内存页 (Memory Page):操作系统定义的进程申请内存的最小单位,根据页进行管理内存映射,一般默认为 4kb 大小

页表(Page Table):操作系统给每个进程存储了一个页表,用于存储虚拟内存和物理内存的关联,页表存储的对应关系叫:页表条目(Page Table Entry,简称PTE)

在创建进程后,操作系统将把页表存储进物理内存,使得MMU可以直接读取物理内存获取PTE 大页表: 操作系统可提供4kb,1Mb,1GB的页进行分配,而不是只能分配多个4k页

分级页表:当进程持续性申请4GB内存时,会发现4kb的页有100万条,这时候寻找起来会十分复杂,操作系统将页表分级存储,1级存储2级的页表范围,2级存储3级的页表范围,3级页表存储实际的页表,这样就加快了查询速度

虚拟内存转换过程

1:操作系统创建进程,初始化进程信息,分配进程虚拟地址页表

2:当进程需要存储变量数据时,虚拟空间分配虚拟地址

3:CPU获取虚拟地址访问

4:通过虚拟地址发送给MMU 5:MMU获取到一个PTE信息

6:如果PTE中存在地址,则从物理地址/CPU内存地址缓存Translation Lookaside Buffer (TLB)  读取数据

7:如果PTE中不存在地址,则触发  缺页异常

8:缺页异常后,cpu尝试给虚拟地址绑定一个物理地址,并且更新页表

9:如果内存空间占满,则确定一个不常使用的地址页,将其存储更新到物理硬盘中,该地址页重新绑定虚拟地址并更新

10:重新回到第6步,读取数据

进程虚拟内存空间分布

在64位系统中,虚拟内存可以达到好几TB,不好做演示,这边按32位系统来说

在32位4G内存中,linux内核默认会真实占用1G空间,剩余3GB用于存储用户进程数据

同样在虚拟内存中,1GB内核空间也会存在,不允许用户态访问:

在创建运行进程后,高位->低位的1GB作为内核空间,

.text编译代码段 低位->高位固定

.data,.bss 静态代码段 低位->高位固定

启动成功后,环境变量 高位->低位 固定

命令行参数 高位->低位固定

同时 栈空间 高位->低位增长

堆空间 低位->高位增长 

中间存放进程运行时候的共享库数据

c语言实际操作

#include<stdio.h>
#include <stdlib.h>
int main(){
    int a = 1;
    printf("栈内存变量地址a:%p \n",&a);

    int a1 = 1;
    printf("栈内存变量地址a1:%p \n",&a1);

    int *b = (int *)malloc(8);
    if (b==NULL){
        printf("申请内存失败\n");
        return  0;
    }

    printf("动态变量申请的堆地址b:%p\n",b);

    int *b1 = (int *)malloc(8);
    if (b1==NULL){
        printf("申请内存失败\n");
        return  0;
    }

    printf("动态变量申请的堆地址b1:%p\n",b1);
    return 0;
}
复制

运行结果

/Users/tioncico/CLionProjects/cTest/cmake-build-debug/cTest
栈内存变量地址a:0x7ffee2096928 
栈内存变量地址a1:0x7ffee2096924 
动态变量申请的堆地址b:0x7fbaf1c059e0
动态变量申请的堆地址b1:0x7fbaf1c059f0

Process finished with exit code 0
复制

可看到,在函数内一开始确定好类型,

声明的变量,将分配栈空间,栈的地址从高位到低位 (6928->6924,4个字节)

而动态分配的变量则是在堆空间,堆的地址从低位到高位 (59e0->59f0  ,16个字节,可能是页大小为16b)

如果在申请b1之前增加free,则会看到b1的内存地址跟b一样,因为b的内存地址已经被释放了,可以继续存储b1:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。