操作系统——内存管理

273 阅读9分钟

操作系统的内存管理非常重要,主要负责下面这些事情:

  • 内存的分配与回收:对进程所需的内存进行分配和释放,malloc 函数:申请内存,free 函数:释放内存。
  • 地址转换:将程序中的虚拟地址转换成内存中的物理地址。
  • 内存扩充:当系统没有足够的内存时,利用虚拟内存技术或自动覆盖技术,从逻辑上扩充内存。
  • 内存映射:将一个文件直接映射到进程的进程空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容,速度更快。
  • 内存优化:通过调整内存分配策略和回收算法来优化内存使用效率。
  • 内存安全:保证进程之间使用内存互不干扰,避免一些恶意程序通过修改内存来破坏系统的安全性。
  • ……

常见的内存管理的方式

内存碎片是由内存的申请和释放产生的,通常分为内部碎片和外部碎片。

内部碎片是由于采用固定大小的内存分区,即以固定的大小块为单位来分配,采用这种方法,进程所分配的内存可能会比所需要的大,这多余的部分便是内部碎片。 外部碎片是由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。

连续内存管理

为一个用户程序分配一个连续的内存空间,内存利用率一般不高。

早期计算机操作系统的一种连续内存管理方式是块式管理,将内存分为几个固定大小的块,每个块中只包含一个进程。存在严重的内部内存碎片问题,也有外部内存碎片问题。

Linux系统中,采用伙伴系统(Buddy System)算法来实现连续内存分配,可以有效解决外部内存碎片的问题。伙伴系统的主要思想时将内存按2的幂次,当进行内存分配时,伙伴系统会尝试找到大小最合适的内存块。如果找到的内存块过大,就将其一分为二,一直切分。假设两块相邻的内存块都被释放,系统会将这两个内存块合并,进而形成一个更大的内存块。

对于内部内存碎片问题,Linux采用SLAB解决。针对一些经常分配并释放的小对象,如进程描述符等,slab分配器基于对象进行管理,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去;当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。

非连续内存管理

允许一个程序使用的内存分布在离散或者说不相邻的内存中,相对更加灵活一些。采用段式管理、页式管理、段页式管理。

虚拟内存

虚拟内存地址的作用:

  • 进程地址隔离

    操作系统为每个进程营造出一片独立的虚拟地址空间,使得进程与进程之间相互隔离,互不干扰的,解决了多进程同时运行时产生的内存地址冲突问题。

  • 保障内核安全

    虚拟内存还提供了系统安全方面的保障,会对进程访问内存的行为进行相关的安全权限检查,保障了系统的稳定性和安全性。

image.png

虚拟内存地址与物理内存地址是怎么映射的?

物理内存页:内核会将整个物理内存空间划分为一页一页大小相同的的内存块,每个内存块大小为 4K,称为一个物理内存页。

通过页表来管理虚拟内存和物理内存之间的映射关系,以及对物理内存的访问权限。操作系统专门划分了一页物理内存来作为页表。

单级页表

单级页表结构中,虚拟内存翻译成物理内存的过程是:

(1)页表(4KB)划分出大小相等的内存块,叫页表项PTE(Page Table Entry),PTE中保存了进程的虚拟页与物理页的映射关系,以及控制物理内存访问的相关权限位。在32位系统中PTE占用4B,在64为系统中占8B。

(2)进程的虚拟内存地址的格式为:页表内的偏移+物理内存页内的偏移。 页表相当于是PTE数组,页表内的偏移相当于PTE的索引。

(3)页表起始地址在创建进程时,写在了内核中,可以从CR3寄存器中获取。

地址翻译过程是:

想查看进程中某个字节的地址,先找到对应的PTE,公式为:页表起始地址+虚拟地址中页表内的偏移 * sizeof(PTE)。然后从PTE中拿到物理页的起始地址,然后用物理页起始地址+虚拟地址中物理内存页中的偏移,即可找到对应的物理内存地址。

image.png

多级页表

32位系统,单级页表中,一个PTE(4B)可以索引一个物理内存页(4K),一个页表(4K)中包含1024个PTE,也就是一个页表可以索引4M的物理内存。32位系统中,4G物理内存则需要1024张页表,也就是页表占用4M,而且是连续的4M内存空间(因为查找PTE是按照数据下标方式索引的),而每个进程都需要页表,也就是每个进程都需要4M的页表空间,对内存的占用比较多。

因此,可以建立多级页表。

以二级页表结构为例:

一级页表就是单级页表,用来直接索引物理内存页,一张页表可以索引4M的物理内存。

二级页表也叫页目录,用来索引一级页表,一张页目录(4K)可以索引4M的一级页表,即1024个一级页表,可以索引1024*4M=4G的物理内存。

在32位系统中,二级页表结构就能索引4G的内存空间。

地址查找的过程,变动点在:

(1)进程的虚拟地址格式为:页目录表中PDE的偏移+一级页表内中PTE的偏移+物理内存页内的偏移。

image.png

能使用多级页表进行内存空间优化,基于一个重要的思想:局部性原理。

程序局部性原理表现为:时间局部性和空间局部性。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某块数据被访问,则不久之后该数据可能再次被访问。空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。

TLB快表

TLB 属于MMU内部的单元,本质上就是一块高速缓存(Cache),缓存了虚拟页号到物理页号的映射关系。

使用 TLB 之后的地址翻译流程是这样的:

  1. 用虚拟地址中的虚拟页号作为 key 去 TLB 中查询;
  2. 如果能查到对应的物理页的话,就不用再查询页表了,这种情况称为 TLB 命中(TLB hit)。
  3. 如果不能查到对应的物理页的话,还是需要去查询主存中的页表,同时将页表中的该映射表项添加到 TLB 中,这种情况称为 TLB 未命中(TLB miss)。
  4. 当 TLB 填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。

缺页中断Page Fault

指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由 MMU 所发出的中断。

常见的页缺失有下面这两种:

  • 硬性页缺失(Hard Page Fault):物理内存中没有对应的物理页。于是,Page Fault Handler 会指示 CPU 从已经打开的磁盘文件中读取相应的内容到物理内存,而后交由 MMU 建立相应的虚拟页和物理页的映射关系。
  • 软性页缺失(Soft Page Fault):物理内存中有对应的物理页,但虚拟页还未和物理页建立映射。于是,Page Fault Handler 会指示 MMU 建立相应的虚拟页和物理页的映射关系。

页面置换算法

当发生硬性页缺失时,如果物理内存中没有空闲的物理页面可用,操作系统必须将物理内存中的一个物理页淘汰,腾出空间来加载新的页面。

  • 最佳页面置换算法(OPT,Optimal):优先选择淘汰的页面是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现,只是理论最优的页面置换算法,可以作为衡量其他置换算法优劣的标准。

  • 先进先出页面置换算法(FIFO,First In First Out) : 最简单的一种页面置换算法,总是淘汰最先进入内存的页面。 FIFO 算法只考虑了页面进入内存的顺序,而没有考虑页面访问的频率和紧迫性。

  • 最近最久未使用页面置换算法(LRU ,Least Recently Used):实际应用比较多,被认为是最接近OPT的算法。

  • 最少使用页面置换算法(LFU,Least Frequently Used)

  • 时钟页面置换算法(Clock):可以认为是一种最近未使用算法,即逐出的页面都是最近没有使用的那个。

参考:mp.weixin.qq.com/s/FzTBx32AB…

javaguide.cn/cs-basics/o…