问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

内存管理基础知识

创作时间:
作者:
@小白创作中心

内存管理基础知识

引用
CSDN
1.
https://blog.csdn.net/Villiam_AY/article/details/145963320

内存管理是计算机系统中的核心概念之一,它涉及到如何有效地分配和使用内存资源。本文将从存储器与内存的基本概念出发,深入探讨虚拟内存、页表、CPU访问内存过程、局部性以及栈和堆等关键概念,帮助读者建立对内存管理的全面理解。

1. 存储器与内存

在计算机的组成结构中有一个很重要的部分是存储器。它是用来存储程序和数据的部件。对于计算机来说,有了存储器,才有记忆功能,才能保证正常工作。存储器的种类很多。按其用途可分为主存储器(也称为内存储器,简称内存)和辅助存储器(也称为外存储器)。

外存储器主要是指除计算机内存及CPU缓存以外的储存器,此类储存器一般断电后仍然能保存数据。常见的外存储器有硬盘、软盘、光盘、U盘等。

内存一般采用半导体存储单元,包括随机存储器(RAM),只读存储器(ROM),以及高速缓存(CACHE)。

  • 只读存储器 ROM(Read Only Memory)
    只能读出,一般不能写入,即使机器停电,这些数据也不会丢失。一般用于存放计算机的基本程序和数据,如BIOS ROM。

  • 随机存储器 RAM(Random Access Memory)
    既可以从中读取数据,也可以写入数据。当机器电源关闭时,存于其中的数据就会丢失。

RAM分为两种:动态存储芯片(DRAM)和静态存储芯片(SRAM)。

  1. DRAM:DRAM结构较简单且集成度高,通常用于制造内存条中的存储芯片。
  2. SRAM:SRAM速度快且不需要刷新操作,但集成度差和功耗较大,通常用于制造容量小但效率高的CPU缓存。
  • 高速缓存 Cache
    高速缓冲存储器是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。由于从1980年开始CPU和内存速率差距在不断拉大,为了弥补这2个硬件之间的速率差异,所以在CPU跟内存之间增加了比内存更快的Cache,Cache是内存数据的缓存,可以降低CPU访问内存的时间。

三级Cache分别是L1、L2、L3,它们的速率是三个不同的层级,L1速率最快,与CPU速率最接近,是RAM速率的100倍,L2速率就降到了RAM的25倍,L3的速率更靠近RAM的速率。

寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。

那么当CPU要去读取来自远程网络服务器上的磁盘文件时,就是由CPU直接和远程服务器磁盘交互吗?事实当然不是这样的。由于CPU的执行速率远远高于外部存储的读写速率,所以当CPU去读取磁盘中数据时,通常会先查看离自己最近的寄存器是否有缓存对应的数据,如果存在想要的数据就会直接获取。而寄存器的读写速率十分接近CPU,将数据缓存在寄存其中可以极大地提升执行效率,避免低效的磁盘读写降低性能。

由于计算机的存储体系中,存储量越大越低廉的存储设备往往读写越慢,存储量越小越昂贵的存储设备往往读写越快。而为了存储更多的数据,大量数据往往存储在读写慢的存储设备上。为了让CPU在执行读写操作时,执行效率尽可能地不被读写慢的存储设备影响,于是下图中的存储器层次结构便孕育而生了。

存储器层次结构

存储器层次结构的主要思想,就是让读写更快的存储设备作为读写慢但容量更大的存储器的高速缓存,让CPU每次优先访问上层读写更快的设备,尽量减少与低效存储设备的读写交互,以保证计算机的整体性能。

2. 虚拟内存

2.1 为什么使用虚拟内存

计算机对于内存真正的载体是物理内存条,这个是实打实的物理硬件容量,所以在操作系统中定义这部份的容量叫物理内存(主存)。物理内存的布局实际上就是一个内存大数组,如图所示。

每一个元素都会对应一个地址,称之为物理内存地址。那么CPU在运算的过程中,如果需要从内存中取1个字节的数据,就需要基于这个数据的物理内存地址去运算即可,而且物理内存地址是连续的,可以根据一个基准地址进行偏移来取得相应的一块连续内存数据。

一个操作系统是不可能只运行一个程序的,当N个程序共同使用同一个物理内存时,就会存在以下问题:

  1. 内存资源是稀缺的,每个进程为了保证自己能够运行,会为自己申请额外大的内存,导致空闲内存被浪费
  2. 物理内存对所有进程是共享的,多进程同时访问同一个物理内存会存在并发问题

为了解决以上问题,操作系统便引入了虚拟内存。通过虚拟内存作为物理内存和进程之间的中间层,让进程通过虚拟内存来访问物理内存。引入了虚拟内存后的操作系统如图所示。

用户程序(进程)只能使用虚拟的内存地址来获取数据,进程会通过页表中的虚拟内存地址查看Memory Map,判断当前要访问的虚拟内存地址,是否已经加载到了物理内存。如果已经在物理内存,则取物理内存数据,如果没有对应的物理内存,则从磁盘加载数据到物理内存,并把物理内存地址和虚拟内存地址更新到页表。

引入虚拟内存后,每个进程都有各自的虚拟内存,内存的并发访问问题的粒度从多进程级别,可以降低到多线程级别。从程序的角度来看,它觉得自己独享了一整块内存,且不用考虑访问冲突的问题。系统会将虚拟地址翻译成物理地址,从内存上加载数据。但如果仅仅把虚拟内存直接理解为地址的映射关系,那就是过于低估虚拟内存的作用了。

虚拟内存的目的是为了解决以下几件事:

  1. 物理内存无法被最大化利用。
  2. 程序逻辑内存空间使用独立。
  3. 内存不够,继续虚拟磁盘空间。

2.2 读时共享,写时复制

其中针对(1)的最大化,虚拟内存还实现了“读时共享,写时复制”的机制,可以在物理层同一个字节的内存地址被多个虚拟内存空间映射,表现方式下图所示。

上图所示如果一个进程需要进行写操作,则这个内存将会被复制一份,成为当前进程的独享内存。如果是读操作,可能多个进程访问的物理空间是相同的空间

如果一个内存几乎大量都是被读取的,则可能会多个进程共享同一块物理内存,但是他们的各自虚拟内存是不同的。当然这个共享并不是永久的,当其中有一个进程对这个内存发生写,就会复制一份,执行写操作的进程就会将虚拟内存地址映射到新的物理内存地址上。

2.3 虚拟内存映射磁盘空间

对于第(3)点,是虚拟内存为了最大化利用物理内存,如果进程使用的内存足够大,则导致物理内存短暂的供不应求,那么虚拟内存也会“开疆拓土”从磁盘(硬盘)上虚拟出一定量的空间,挂在虚拟地址上,而且这个动作进程本身是不知道的,因为进程只能够看见自己的虚拟内存空间,如下图所示。

综上可见虚拟内存的重要性,不仅提高了利用率而且整条内存调度的链路完全是对用户态物理内存透明,用户可以安心的使用自身进程独立的虚拟内存空间进行开发。

3. 页、页表、页表条目


  • 页是1次内存读取的大小,操作系统中用来描述内存大小的一个单位名称。一个页的含义是大小为4K(1024*4=4096字节,可以配置,不同操作系统不一样)的内存空间。操作系统对虚拟内存空间是按照这个单位来管理的。

  • 页表
    页表实际上就是页表条目(PTE)的集合,就是基于PTE的一个数组,页表的大小是以页(4K)为单位的。

虚拟内存的实现方式,大多数都是通过页表来实现的。操作系统虚拟内存空间分成一页一页的来管理,每页的大小为 4K(当然这是可以配置的,不同操作系统不一样)。4K 算是通过实践折中出来的通用值,太小了会出现频繁的置换,太大了又浪费内存。

  • 页表条目(PTE)
    页表条目(PTE)是页表中的一个元素,PTE是真正起到虚拟内存寻址作用的元素。PTE的内部结构如下图所示。

PTE是由一个有效位和一个包含物理页号或者磁盘地址组成,有效位表示当前虚拟页是否已经被缓存在主内存中(或者CPU的高速缓存Cache中)。

  1. 有效位为1,表示虚拟页已经被缓存在内存(或者CPU高速缓存TLB-Cache)中。
  2. 有效位为0,表示虚拟页未被创建且没有占用内存(或者CPU高速缓存TLB-Cache),或者表示已经创建虚拟页但是并没有存储到内存(或者CPU高速缓存TLB-Cache)中。

通过上述的标识位,可以将虚拟页集合分成三个子集,下表所示。

有效位
集合特征
1
虚拟内存已创建和分配页,已缓存在物理内存中。
0
虚拟内存还未分配或创建。
0
虚拟内存已创建和分配页,但未缓存在物理内存中。

4. CPU访问内存过程

当某个进程进行一次内存访问指令请求,将触发上图的内存访问,具体的访问流程如下:

  1. 进程将访问内存相关的指令请求发送给CPU,CPU接受到指令请求
  2. CPU找到数据的虚拟地址(可能存放在寄存器内,所以这一步就已经包括寄存器的全部工作了。)
  3. 将虚拟地址(Virtual Page Number及offset仅是其中一部分,我们这里只展示这两部分的作用)送往内存管理单元(MMU)
  4. MMU先判断TLB(Translation Look-aside Buffer)中是否缓存了该虚拟地址的物理地址,如果命中,MMU直接获取物理地址
  5. 如果TLB未命中,则将虚拟地址发送给Table Walk Unit
  6. Table Walk Unit根据虚拟地址的VPN获取到一级页表(页目录),再从一级页表中获取到二级页表,从二级页表中获取到对应的物理内存页地址,结合虚拟地址中的物理内存页偏移量offset,拿到物理内存页中其中1项的物理地址
  7. 如果MMU未能查到物理地址,则会触发缺页异常;缺页异常被捕获后,操作系统会根据缺页异常类型,做出不同的处理。
  8. 如果MMU获取到了物理地址,则根据物理地址到Cache中查看是否已缓存了对应的内存数据,如果缓存了则返回内存数据
  9. 如果Cache未命中,则直接拿物理地址到主存中查看是否存在内存数据,如果缓存了则返回内存数据

5. 局部性

一个优秀的程序通常具有良好的局部性,它们通常会重复使用已用过的数据,或者使用已用过数据的邻近数据,也就是说,程序常常会使用集中在一起的局部数据。局部性分为:时间局部性和空间局部性。

  • 空间局部性:一个内存位置被引用过一次,在短时间内,其附近的内存位置也将被引用。(内存都是按页读取,读取1个内存位置后,其所在页的内存数据会被缓存,所以再次读取其附近的内存位置效率更高)
  • 时间局部性:被引用过一次的内存位置,在短时间内将被多次引用。(执行效率越高的缓存,容量越小。读取1个内存位置后,长时间不再读取此内存位置,会有新的内存位置被缓存,该内存位置可能不再存在缓存中)

6. 栈和堆

Linux为每个进程维护了一个单独的虚拟地址空间,并且这个地址空间是连续的,进程就可以很方便地访问内存,也就是我们常说的虚拟内存。虚拟内存形式如下图所示。

Linux进程的虚拟内存

一个进程的地址空间通常包括代码段、数据段、堆、栈等,地址从低到高。代码中使用的内存地址都是虚拟内存地址,而不是实际的物理内存地址。栈和堆只是虚拟内存上2块不同功能的内存区域。在x64架构中,使用rsp寄存器指向栈顶;在x86架构中,使用esp寄存器指向栈顶的内存地址。一般可以简称为sp。

  1. 由编译器自动分配和释放,速度快
    栈中存储着函数的入参以及局部变量,这些参数(如函数参数、函数返回地址,局部变量、临时变量等)会随着函数的创建而创建,函数的返回而销毁。(通过 CPU push & release)
  2. 栈的特性:后入先出LIFO
    栈需要存储函数中的局部变量和参数,函数又是最后调用的最先销毁,栈的后进先出正好满足这一点。
  3. 栈由高地址向低地址扩展,栈内是连续分配内存的
    如果给一个数组或对象分配内存,栈会选择还没分配的最小的内存地址给数组,在这个内存块中,数组中的元素从低地址到高地址依次分配(不要和栈的从高到低弄混了)。所以数组中第一个元素的其实地址对应于已分配栈的最低地址。
  4. 栈只能获取栈顶的内存地址
    栈是从高地址往低地址扩展的,栈顶正好指向数组的起始地址,即数组的指针。

栈桢

  1. 栈帧本质上是一种栈
    栈帧本质上是一种栈。栈帧是指函数在被调用时,所拥有的一块独立的用于存放函数所使用的状态和变量的栈空间。
  2. 函数的每次进入,都对应1个栈桢
    每个函数都对应有一个栈帧。同一个函数多次调用,每次可能会分配到不同的栈帧。整个栈的内容在同一个时刻可以看作是由许多栈帧依序“堆叠”组成的。栈桢的结构详见下图。
    对于一个运行中的函数,其使用的栈帧区域被sp和bp寄存器限定(对于x86,sp等价esp,bp等价rsp;对于x64,sp等价rsp,bp等价rbp)。
    bp指向栈帧的底部,sp指向栈帧的顶部。

在函数中使用的所有变量(本地变量、实参),一般使用bp定位。设N为整型字节数,bp+2N是第一个实参的地址,bp-N是第一个本地变量的地址。

  1. 自由分配,自己申请,自己释放(否则发生内存泄漏),速度较慢,更灵活
  2. 堆的特性:先入先出FIFO
  3. 堆的内存地址是不连续的,由低地址向高地址扩展,一般是链表结构

由于栈都会随着函数的创建而创建,函数的返回而销毁。所以我们大多时候谈到的内存管理,都是对堆内存的管理。

本文原文来自简书

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号