内核内存分配

2025年7月3日 | 阅读 8 分钟

什么是内核内存分配?

内核内存分配是指操作系统内核为其自身分配内存的过程。内核是操作系统的一个核心组件,负责管理包括内存的系统资源,并为操作系统的其他部分提供底层服务。

通常情况下,内核会分配小块内存或页面来管理系统数据结构,例如进程表、文件系统缓存和网络缓存。这些页面的大小可能因所使用的特定操作系统和硬件架构而异。

内核内存分配可以是动态的或静态的。在动态分配中,内核根据需要从系统的内存管理器请求内存。在静态分配中,内核在启动时保留固定数量的内存,并在系统运行时一直使用它。

高效的内存管理对于操作系统的平稳运行至关重要,而内核内存分配在这一过程中起着至关重要的作用。

我们如何分配内存?

内核内存分配过程取决于所使用的特定操作系统和编程语言。

然而,总的来说,有几种常见的内核内存分配方法

kmalloc()

什么是 kmalloc()?它是如何工作的?

kmalloc() 是最常用的内核内存分配函数。它帮助我们从内核堆中分配内存。换句话说,kmalloc() 是一个在 Linux 内核中用于从内核堆动态分配内存的函数。

它是最常用的内核内存分配函数,因为它快速高效。它包含两个参数:

  1. 要分配的内存大小
  2. 一组标志,可用于指定各种选项。

当调用 kmalloc() 时,它会确定内核堆是否有足够的可用空间来满足所需的内存大小。

如果满足,则分配指定大小的连续内存块,并返回指向该块起始位置的指针。根据传递给它的标志,该函数可能会阻塞直到有足够的可用空间,或者返回 NULL 指针表示分配失败。

在内部,kmalloc() 使用一组数据结构来管理内核堆,包括一个空闲内存块列表和一个 slab 缓存集。

基本上,slab 缓存是一个预分配的、特定大小对象的容器,可以通过减少分配和释放的开销来提高性能。

换句话说,它会检查使用 kmalloc() 请求的对象是否具有特定大小。然后,该函数会检查是否存在该大小的 slab 缓存。如果不存在,它将从缓存中分配对象。

总而言之,kmalloc() 是一个强大且适应性强的内核内存分配函数,可用于各种情况。通过有效管理内核堆和支持 slab 缓存,它可以帮助提高系统的稳定性和性能。

Vmalloc()

什么是 vmalloc()?它是如何工作的?

Vmalloc() 也是一个在 Linux 内核中用于从内核的虚拟内存空间分配内存的函数。与 kmalloc() 分配物理内存不同,vmalloc() 分配的内存可以是分散的、非连续的,遍布内核的虚拟内存空间。

因此,它可以用于分配可能无法作为单个连续块访问的大内存块。

当调用 vmalloc() 时,它会确定内核的虚拟内存空间是否有足够的可用空间来支持所需的内存大小。如果满足,则分配指定大小的虚拟内存块,并返回指向该块起始位置的引用。如果可用空间不足,该函数将返回 NULL 指针表示分配失败。

在内部,vmalloc() 使用一组数据结构来管理内核的虚拟内存空间,包括一组帮助我们将虚拟内存映射到物理内存的页表。

因为我们都知道,分配的内存可能分散在整个虚拟内存空间中,因此必须更新页表以反映新的分配。

需要注意的是,设备无法直接使用 vmalloc() 分配的内存。虚拟内存必须使用内核的映射功能映射到物理内存。这会增加复杂性和开销,但它也使得 vmalloc() 能够分配比 kmalloc() 多得多的内存。

总而言之,vmalloc() 是一个强大的内核内存分配函数,在需要大量非连续内存时可以使用。然而,管理虚拟内存区域的开销有时会使其比 kmalloc() 慢且效率低,因此应谨慎使用。

GFP_kernel 标志

GFP kernel 标志用于 Linux 内核,以描述内核级软件所需的内存分配类型。为了指定正在分配的内存的属性,这些标志被用作 kmalloc() 和 vmalloc() 等内存分配过程的参数。“gfp mask”参数是一个位掩码,用于控制内存分配函数的行为,其中包含 GFP kernel 标志。

以下是常用的 GFP kernel 标志及其定义列表:

GFP_KERNEL: GFP_KERNEL 是典型内核内存分配的默认标志。如果需要,该函数可以休眠以等待内存可用。它还表示内存分配应在标准的内核上下文中进行。

GFP_ATOMIC: 此标志用于必须在原子上下文中执行的内存分配,包括中断处理程序或其他不允许休眠的关键区域。为防止系统不稳定,使用此标志的内存分配必须立即完成,没有任何延迟或休眠。但 GFP_ATOMIC 应谨慎使用,因为它可能导致内存碎片和较低的内存分配成功率。

GFP_NOWAIT: 与 GFP_ATOMIC 类似,GFP_NOWAIT 允许在必要时进行一定程度的阻塞以等待内存可用。当不允许休眠但可以容忍短期阻塞以等待内存分配时使用。

内核内存分配中使用的技术

两种用于管理分配给内核进程的空闲内存的策略:

1. 伙伴系统 (Buddy System)

此方法将一个大内存块分割成小块以满足请求。该算法提供最佳匹配。块的两个较小部分称为“伙伴”,大小相同。同样,直到满足请求,其中一个伙伴会将其分割成更小的块。这种方法的优点是,两个伙伴可以根据内存需求重新组合成一个更大的块。

四种伙伴系统类型

  • 二进制伙伴系统
  • 斐波那契伙伴系统
  • 带权重的伙伴系统
  • 三次伙伴系统

为什么使用伙伴系统?

分区大小与进程大小之间的不匹配可能导致空间利用效率低下。与动态分配相比,它更有效且易于实现。

好处

  • 有效的内存管理:内核内存分配器可以实现有效的内存管理,确保内存以优化可用空间和减少碎片的方式进行分配和释放。
  • 可修改的分配策略:内核内存分配器可以修改以应用特定的分配策略,包括首次适应、最差适应和最佳适应。这使得可以更精确地控制内存分配和利用。
  • 高性能:内核内存分配器通常是为高性能系统设计的,可提供快速有效的内存分配和释放。
  • 减少内存开销:通过实现更有效的内存资源利用并降低系统的总内存占用,内存分配器可以降低内存开销。
  • 提高系统稳定性:通过减少内存泄漏、崩溃和其他与内存分配相关的问题的可能性,有效的内存管理可以提高系统稳定性。

2. Slab 分配 (Slab Allocation)

Slab 分配是第二种内核内存分配技术。它消除了由分配和释放引起的碎片。通过使用此技术,可以保留包含特定类型数据对象的已分配内存,以便在分配其他相同类型的对象时使用。在 slab 分配中,预先分配了适合特定大小或类型数据对象的内存块。尽管它存储了常用数据以便在请求时快速检索,但缓存不会在使用后立即释放空间。需要两个术语:

Slab:一个或多个物理上相邻的页面构成一个 slab。Slab 是所包含缓存中特定类型项的实际数据容器。

Cache:Cache 是一个极快的内存小单元。一个或多个 slab 构成一个 cache。每个不同的内核数据结构都有自己的 cache。

例如

一个包含进程描述符数据结构的独立缓存
用于文件对象的独立缓存,用于信号量等事物的独立缓存。用于填充缓存的对象是每个缓存所代表的特定内核数据结构的实例。例如,信号量实例存储在代表信号量的缓存中,而进程描述符对象实例存储在代表进程描述符的缓存中。

实施

在 slab 分配方法中,缓存用于保存内核对象。当创建缓存时,分配给它的几个最初标记为空闲的项。缓存中项的数量取决于相关 slab 的大小。

例如,一个由三个连续 4KB 页面组成的 12KB slab 可以容纳六个 2KB 项。缓存中的每个对象最初都被标记为空闲。当需要新对象时,分配器可以将缓存中的任何可用对象分配出去以满足内核数据结构请求。这表明缓存中的项已被使用。

Slab 分配器的优势

  • 每个不同的内核数据结构都有自己的缓存,因此碎片不会浪费任何 RAM。
  • 内存请求可以立即得到满足。
  • 当对象频繁分配或释放时,slab 分配策略效果非常好。分配和释放 RAM 可能是一个耗时的过程。然而,由于对象是预先构建的,因此可以快速从缓存中分配对象。当内核完成使用某个对象后,该对象会被标记为空闲并返回到其缓存,使其可以立即用于其他内核请求。

常见问题

如何定义内核内存?

操作系统将重要数据(如设备驱动程序、文件系统信息和进程控制块)存储在内核内存(RAM 的专用区域)中。

为什么分配内核内存很重要?

它确保操作系统有足够的内存来安全、可靠地执行关键功能,而不会崩溃。

用户内存和内核内存有什么区别?

用户内存分配给程序并在用户模式下访问,而内核内存由操作系统使用并在特权模式下访问。

在内核内存分配中,什么是伙伴系统?

为了帮助控制碎片,伙伴系统会将内存分割或合并成 2 的幂次方块,以满足分配请求。

什么是 slab 分配器?

对于常用内核对象,slab 分配器会预先分配内存块(slab),从而提高速度并降低分配开销。


下一主题Indus 操作系统