0%

Linux内核内存管理 - KFENCE

这是<Linux内核内存管理>系列的第六篇

第一篇为内核内存管理过程知识点的的简单梳理

第二篇介绍了内核的数据结构

第三篇介绍了从内核第一行代码加载到跳转到C代码前的内存处理。

第四篇概览了初始化C代码中的内存处理

第五篇(上)第五篇(下)介绍了Memblock和伙伴系统分配器

前言

Kernel Electric-Fence (KFENCE)是5.12版本内核新引入的内存使用错误检测机制。它可以检查的错误有:

  • 内存访问越界
  • 释放后使用
  • 无效释放

显然,它可以检测的内存错误类型不如KASAN多。但与KASAN相比,它最大的优势是运行时小Overhead,可以直接用在生产环境中。因此在X86,ARM64,RISCV等平台上均默认开启。

在Arch对应的defconfig中使用CONFIG_HAVE_ARCH_KFENCE开启。

架构及原理

Kfence的原理比较简单,如下图:

KFENCE

初始化

  • 初始化过程中,KFENCE向Memblock申请一段内存,作为KFENCE内存池。
    • 这个内存池的大小配置为CONFIG_KFENCE_NUM_OBJECTS
    • 即,预留两个页面作为保护页(Guard Page),接着为每一个用于分配的内存页分配一个Guard Page。因此总大小为:
1
#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE)
  • 初始化一个Delayed Worker,定期(CONFIG_KFENCE_SAMPLE_INTEVAL)重置kfence_alloc_gate值为0。

这个值可以通过sysfs修改

分配

  • kfence_alloc_gate值为0时,使用kmem_cache_alloc所作的内存分配从KFENCE内存池中分配,并增加kfence_alloc_gate的值。kfence_alloc_gate值大于等于1时,直接从SLUB中分配。由此可以看出,kfence是基于采样的内存检测。

大于一个Page(4K)的分配不会从KFENCE Pool中分配

  • 每次通过KFENCE进行内存分配时,都会从KFENCE内存池分配一个内存页和一个Guard Page,并在实际使用内存的两端内存填充Canary数据。

解释一下为什么保护数据叫Canary。这是因为在19世纪,金丝雀在采矿业中常用的毒气检测方法,因为它们比人类对毒气更为敏感反应也更快。

  • 如果KFENCE内存池中没有可用内存,则直接从SLAB中分配。

释放

  • 释放时,检查Canary数据,将所用内存放回KFENCE内存池。

检测报错

在以下情况,会检测报错:

  • 释放时发现Canary数据不对。
  • 当KFENCE内存池的内存区域发生Page Fault时,它或者是因为越界访问、或者是释放后使用。
  • 无效释放:当一段KFENCE内存没有被标记分配,但对齐释放时,会有相应报错提示。

总结

开源社区总能带来新的idea。KFENCE,克服了KASAN等工具需要占用大量内存且影响运行时性能的缺点,是一个有效地运行时内存访问错误检测工具。

当然,因为它所针对的内存区域仅仅是KFENCE内存池,且其是周期性进行采样,检测效果还不得而知。其又有可以动态开关、参数可调节等优点,这些劣势或许也不是问题。后续若有时间可以研究分析对比其和KASAN的检测效果。