虚拟内存和物理内存

概念

物理内存就是指内存条,虚拟内存是逻辑概念。每个进程都有自己独立的虚拟地址空间,32位有4G大小,64位有256T大小。一个进程同一时刻不可能所有变量数据都会访问到,只需要在访问某部分数据时,把这一块虚拟内存映射到物理内存,其他没有实际访问过的虚拟地址空间并不会占用到物理内存,这样对物理内存的消耗就大大减少了 。

虚拟内存划分

32位系统

64位系统

从虚拟地址的角度来看,内存空间如下划分:

  1. 32位系统最高的1G空间保留给内核使用,多个进程共享;64位系统最高的128T空间保留给内核使用并且不被进程共享。
  2. 栈,栈向低地址方向延伸,用于存放局部变量和进程上下文。栈是一块连续的内存的区域,用来记录函数调用过程中的栈帧,在函数调用时栈的参数由右往左入栈,然后是返回地址、函数中的局部变量。(静态变量存储在静态存储区,所以不入栈)当函数调用结束后,按照栈后进先出的顺序退栈,栈顶指针指向最开始存的地址,程序由该点继续运行。
  3. MMAP区(文件映射内存,如动态库等),当malloc/free申请释放内存大于128K时(且堆内部的内存不能满足malloc调用),通过mmap系统调用分配一块虚拟地址空间;32位系统向低地址扩展,64位向高地址扩展。
  4. 堆,堆向高地址方向延伸,当malloc/free申请释放内存小于128K时(且堆内部的内存不能满足malloc调用),通过brk/sbrk系统调用,控制堆顶指针向高地址偏移(malloc)或者低地址偏移(free);操作系统是用链表来存储的空闲内存地址的,所以堆的内存是不连续的。
    参考linux下brk、mmap、malloc和new的区别
  5. BSS(未初始化的全局变量和静态变量)、数据段(初始化的全局变量和静态变量)、代码段(二进制代码和一些字符串常量)。

从C++程序占用的内存来看可以分位以下几部分:

  1. 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
  3. 全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
    未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统释放。
  4. 文字常量区 —常量字符串就是放在这里的,程序结束后由系统释放。
  5. 程序代码区—存放函数体的二进制代码。

物理内存

系统的物理内存被划分为许多相同大小的部分,也称作内存页,一般位4KB。

内核使用

内核管理内存,因此内核代码可以访问内核空间和用户空间。 当谈论“内核空间”时,通常指的是内核专用的页面。
操作系统启动时,位于/boot目录下的压缩内核文件会被加载到内存中并解压。这部分内容在系统允许期间都会常驻在内存的起始位置。

slab分配器

操作系统的运行还需要更多的空间来分配给管理进程、文件描述符、socket和加载的内和模块等内容。所以内核会通过slab分配器动态分配内存。

进程使用

除去内核使用的部分,所有的进程都需要分配物理内存页给它们的代码、数据和堆栈。进程消耗的这些物理内存被称为“驻留内存”。
驻留内存是指虚拟内存中实际映射到物理内存的那部分,也就是进程实际占用的物理内存大小。所以判断一个进程使用的内存大小,主要是看占用的物理内存,也就是驻留内存的大小,即RSS。

页缓存page cache

Page Cache由内核控制,不能由用户空间代码访问。 Read操作时内核可以将页面从内核空间拷贝到用户空间。

因为磁盘io的速度远远低于内存的访问速度,所以为了加快访问磁盘数据的速度,页缓存尽可能的保存着从磁盘读入的数据。page cache中还有一部分称为buffer,它的作用是缓存要写入到磁盘的数据。

页缓存的大小是在一直动态变化的。当系统内存充足时,页缓存会一直增大;当系统free内存不足时,这时如果有进程申请内存,操作系统会从page cache中回收内存页进行分配;如果page cache也已不足,那么系统会将当前驻留在内存中的数据置换到磁盘上,然后空出来的这部分内存就可以用来分配了,这就是页替换,页替换对性能有很大影响。

内核空间和用户空间的区别可以参考What is kernel memory and user memory? (question about the term kernel page and the page cache)
page cache可以参考02 基础篇 | Page Cache是怎样产生和释放的?

虚拟内存->物理内存的映射机制

系统内核为每个进程都维护了一份从虚拟内存到物理内存的映射表,称为页表。页表根据虚拟地址,查找出锁映射的物理页位置和数据在物理页中的偏移量,便得到了实际需要访问的物理地址。

动态内存分配

C++的动态内存分配、管理都是基于malloc和free的,malloc动态内存分配函数是通过brk(sbrk)和mmap这两个系统调用实现的,这两种实现方式的区别大致如下:

  • brk(sbrk),性能损耗少; mmap相对而言,性能损耗大
  • mmap不存在内存碎片(是物理页对齐的,整页映射和释放);
    brk(sbrk)可能存在内存碎片(由于new和delete的顺序不同,可能存在空洞,又称为碎片)
  • 无论是通过brk(sbrk)还是mmap调用分配的内存都是虚拟空间的内存,只有在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
  • delete,动态内存释放函数。如果是brk(sbrk)分配的内存,直接调用brk(sbrk)并传入负数;如果是mmap分配的内存,调用munmap归还内存。

缺页中断

软性

软性页缺失指页缺失发生时,相关的页已经被加载进内存,但是没有向MMU注册的情况。操作系统只需要在MMU中注册相关页对应的物理地址即可。

硬性

硬性页缺失是指相关的页在页缺失发生时未被加载进内存的情况,malloc的数据在第一次访问时都会发送缺页中断。操作系统寻找到一个空闲页把数据load进去,当没有空闲页时通过LRU算法把一个使用中的页写到磁盘上(malloc的数据放回swap空间,文件数据直接释放),并注销其在MMU内的记录,然后将数据读入被选定的页,重新向MMU注册该页。

无效

当程序访问的虚拟地址是不存在于虚拟地址空间内的时候,则发生无效页缺失,C++中体现位segament fault。

参考链接
如何理解虚拟内存
【春节红包系列】一次"内存泄漏"引发的血案

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值