Page 503 - 《软件学报》2024年第4期
P. 503

欧阳湘臻 等: 榫卯: 一种可组合的定制化内存分配框架                                                     2081


                 巨页  cache 机制减少向操作系统归还巨页的开销. Temeraire 以一定的执行时间开销为代价来换取                      CPU  访存等待
                 周期的降低.
                    Mesh [38] 是针对  C/C++程序的运行时动态内存碎片整理机制, 它旨在通过替换               malloc 实现, 尽可能消除   C/C++
                 程序中的内存碎片. Mesh      结合了一种随机算法和共享内存机制减少内存碎片, 它在保证性能的情况下减少内存碎
                 片, 降低应用内存占用.
                    内存安全防护的分配器: Scudo, smimalloc, Diehard    [22] 与  mallocng. Scudo  分配器是  Android 11  的原生代码
                 (native code) 内存分配器, 它针对常见的内存漏洞        (如内存溢出, 释放后使用和重复释放) 提供额外的检查和缓解
                 措施, 同时确保良好的性能. Scudo       在用户内存块的头部插入额外的           CRC32  校验字用于检测重复释放和缓冲区溢
                 出, 当头部校验字出现错误意味着数据被改写. 同时, Scudo              采用延迟释放策略, 能够允许一定程度的释放后使用
                 行为. 延迟释放策略提供固定大小的释放隔离区. 用户释放的内存首先会被移入该隔离区, 在被换出隔离区之后才
                 会真正释放. Scudo   在检测出内存异常使用之后, 会报告错误并立即中止用户进程. smimalloc 是                  mimalloc 的安全
                 版本, 它使用异或校验的方式防止页头部元数据中的自由内存块链表被缓冲区溢出所改写. Diehard                           将内存块元数
                 据存放在用户堆外避免元数据被改写, 同时应用随机方法检测未初始化的内存读取问题. 考虑到元数据与内存块
                 分离不利于访问元数据时的内存局部性, mallocng            采用了折中的方式, 一部分内存元数据存放在内存块头部以提
                 高局部性, 另一部分则存放在用户堆外防止被改写.
                  1.3   内存分配框架

                    现代内存分配器通常是多种内存分配算法的结合, 而内存分配框架可以帮助开发者根据应用场景, 利用已有
                 的内存分配算法搭建出满足需求的内存分配器, 并进行一定程度的定制与性能优化.
                    Linux  内核内存分配框架由页分配器和对象分配器构成, 其中页分配器主要使用分割-合并式的二分伙伴系统
                 算法, 对象分配器是可配置的, 可选择          slab/slob/slub  其中一种实现. slab  将物理页预先分割成不可合并的小内存对
                 象, 结合每  CPU  核心的  cache 机制以提供快速的对象分配. slob       为了降低对象分配器本身的内存开销, 使用简单的
                 自由链表维护空闲内存, 以首次适配策略分配内存. slub               针对大型系统优化, 它将        slab  的页空闲对象数组和每核
                 心  cache 数组改为链表以降低额外的储存开销.
                    针对通用内存分配器可定制性差的缺点以及现有内存分配器框架灵活性有限的问题, Berger 等人提出了一种
                                                  [5]
                 可组合的高性能内存分配器框架           HeapLayer . HeapLayer 借助面向对象编程中的继承, 封装的特性, 将内存分配框
                 架视为   Heap  类的复合, 而通过   Heap  类的继承可以实现层级的定制. HeapLayer 内存分配器框架基于               C++模板元
                 编程特性实现, 它利用       mix-in  组合层级. mix-in [16] 是一种  C++的抽象类, 它将一个类中使用的其他类的类型抽象,
                 作为该类模板的一个输入参数. 前文提到的              Hoard, Mesh  以及  Diehard  内存分配器均使用  HeapLayer 框架搭建.

                  2   榫卯内存分配框架

                    内存分配框架      HeapLayer 从面向对象的编程角度考虑内存分配框架的思路启发了本文研究. 本文将从函数式
                 编程视角考虑内存分配流程, 提出一种更直观, 同时更具组合性和定制性的分配器框架.
                  2.1   榫卯框架的总体设计
                    本文将处理内存请求的分配器层级视为多个具备可组合性的函数, 内存分配框架中的分配算法的组合即为层
                 级函数的组合. 函数的可组合性定义源自数学函数的复合关系                   compose( f,g) := fun x : A → f(g(x)) . 其中  g : A → B ,
                 f : B → C  . 根据定义, 高阶函数  compose 接受两个函数    g  和  f 作为输入, 其中  g  的输出类型是   f 的输入类型. 它将
                 函数  g  和函数  f 复合成一个新函数     compose( f,g) , 这个新函数接受一个参数     x : A , 返回结果的类型为   C. 通常使用
                 算子   f ◦g 表示  compose( f,g) . 该高阶函数满足结合律, 即  ( f ◦g)◦k = f ◦(g◦k) . 本文针对内存分配流程给出抽象
                 的函数组合模型. 模型相关的函数定义如表              1  所示.
                    首先, 本文定义一个内存请求上下文            mctx, 用于传递用户请求与分配器的状态. 内存请求上下文能够记录各
                 级函数产生的副作用        (side effect), 从而保证各层函数的无状态     (stateless) 属性. 可组合的层级函数为    L, 它会修改
   498   499   500   501   502   503   504   505   506   507   508