文章目录
  1. 计算机体系结构与内存层次
    1. 计算机体系结构
    2. 内存层次
    3. 操作系统的内存管理方式
  2. 地址空间与地址生成
    1. 地址空间的定义
    2. 地址生成
    3. 地址检查
  3. 连续内存分配
    1. 内存碎片
    2. 动态分配
  4. 碎片整理
  5. 伙伴系统
  6. SLUB分配器
    1. 概述
    2. 申请内存块
    3. 释放内存块

计算机体系结构与内存层次

计算机体系结构

 

内存层次

 

操作系统的内存管理方式

预备知识

  • 物理地址:将内存当作一个数组,内存单元在数组中的索引就是该内存单元的物理地址;
  • 线性地址(虚拟地址):操作系统提供的一种对内存的抽象;线性地址到物理地址的转换如下图:

 

注:寄存器CR3用于存放当前进程正在使用的页目录基地址

  • 逻辑地址:形式为[段描述符 : 段偏移量];逻辑地址到线性地址的转换如下图:

 

正文

 

  • 重定位:操作系统把用户程序指令中的相对地址变换成为所在存储中的绝对地址的操作;
  • 分段
    • 段:段由三个参数定义:段基地址、段限长和段属性;段基地址、段限长以及段的保护属性存储在一个称为段描述符的结构体中。
    • 分段机制:将虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元;
  • 分页:虚拟地址空间按照固定大小划分成称为页面的若干单元,在物理内存中对应的单元称为页框。
  • 虚拟存储:进程的地址空间中有的页映射到内存,有的页映射到外存。当程序需要访问外存中的数据时,进程产生缺页中断,然后操作系统将这部分数据装入内存之中,然后进程重新执行失败的指令。

地址空间与地址生成

地址空间的定义

地址空间是一个进程用来寻址内存的一套地址集合;地址空间中的地址为线性地址。

地址生成

举例说明如下:

 

地址的生成有如下几种时机:

  • 编译时(地址已知);
  • 加载时;使用重定位表,在加载时生成绝对地址;
  • 执行时;需硬件支持的地址转换;

地址检查

以指令mov %eax, $0xfffa620e执行时的地址检查为例。

 

连续内存分配

以下是在内存分配没有其他技术支持条件下的讨论。此时必须对进程分配连续的地址空间。

内存碎片

外碎片:由于每个进程的寿命不一致,导致有的进程先从内存中退出,有的进程后从内存中退出,从而在进程占用的内存区域之间出现空闲的内存区域,而该内存区域的长度小于所有进程要求的长度,使得该内存区域变得不可用。这样的空闲内存区域称为外碎片。

内碎片:分配给进程区域内无法利用的内存。这是由于分配内存时只能分配2的整数次幂长度的内存区域。

动态分配

  • 最先匹配:将空闲内存区域按照地址排序,将第一个长度大于进程要求长度的空闲内存区域分配给该进程;
  • 最佳匹配:将空闲内存区域按照长度排序(从小到大),将第一个长度大于进程要求长度的空闲内存区域分配给该进程;
  • 最差匹配:将空闲内存区域按照长度排序(从大到小),将第一个空闲内存区域分配给该进程;

碎片整理

通过调整进程占用的分区位置来减少或避免分区碎片。

伙伴系统

连续内存分配的实例。

SLUB分配器

概述

针对以下情形:

  • 一些内核所用到的结构体等对象远比页要小;
  • 并且这些对象会被频繁地申请与释放;
  • 有些对象仅仅初始化的时间就超过了申请到释放的时间;

为满足这些对象申请内存的需求,需要创造页分配管理器之外的内存分配管理器——SLAB分配器,而SLUB分配器是在SLAB分配器的基础上进一步优化;使得效率更高,(分配器的)内存占用更低;下面介绍SLUB分配器的原理。

SLUB将内存分组管理,每个组包含$2^{3},\cdots,2^{11}$个字节,在4KB为一页的情形下,还有两个特殊的长度分组(96B,192B),共11组;

可以这样理解:SLUB相当于零售商,伙伴系统相当于厂家,SLUB从伙伴系统那里”批发”内存,然后SLUB再零售出去。

整个SLUB系统结构图如下:

 

整个系统的源头就是数组kmalloc_caches[12],其中每个数组元素kmem_cache结构体相当于超市,每个超市出售一种特定长度的内存;每个超市有两个部门:仓库(kmem_cache_node)与营业厅(kmem_cache_cpu),营业厅只保留一个slab(从kmem_cache获取的多个内存页),营业厅在没有空闲object(特定长度的内存)的情况下会从仓库中换出有空闲object的slab;仓库中存放着被完全使用的slab(full链表存放)与被部分使用的slab(partial链表存放)。

申请内存块

第一次申请内存块

给对象分配object,freelist指向下一空闲的object。

 

kmem_cache_cpu的slab有空闲的object时申请内存块

给对象分配object,freelist指向下一空闲的object。

 

这里可能会问一个问题,为什么kmem_cache_cpu的slab中被占用的object是分开的,这是因为中间空闲的object被占用后释放了。

kmem_cache_cpu的slab无空闲的object时申请内存块,kmem_cache_node的partial链表有slab

kmem_cache_cpu的slab添加到kmem_cache_node的full链表,再将kmem_cache_cpu的slab换成kmem_cache_node的partial链表中的slab。然后给对象分配object,freelist指向下一空闲的object。



 

kmem_cache_cpu的slab无空闲的object时申请内存块,kmem_cache_node的partial链表无slab

kmem_cache_cpu的slab添加到kmem_cache_node的full链表,向伙伴系统申请slab并初始化slab,然后给对象分配object,freelist指向下一空闲的object;



释放内存块

释放kmem_cache_cpu的slab上的object

将该object放入freelist链表即可。

释放kmem_cache_node上full链表上的object

先释放该object,那么该object所在slab的状态从full变成partial,因此需要将该object所在slab从full链表中移除,然后将其添加到partial链表。


 

释放kmem_cache_node上partial链表(有多个object被分配)上的object

先释放该object,并将该object放入所在slab的freelist链表。


 

释放kmem_cache_node上partial链表(只有一个object被分配)上的object

先释放该object,之后该object所在slab的状态变成empty,于是还要将该object所在slab也释放。



 

参考链接

SLUB算法原理

对各种地址更详细地解释