好好活就是有意义的事,有意义的事就是好好活
Memory Allocation Guide
Memory Allocation Guide

Memory Allocation Guide

翻译自:https://www.kernel.org/doc/html/v5.14/core-api/memory-allocation.html

Memory Allocation Guide

Linux 提供了多种用于内存分配的 API。 您可以使用 kmalloc 或 kmem_cache_alloc 系列分配小块,使用 vmalloc 及其派生类分配大的虚拟连续区域,或者您可以使用 alloc_pages 从页面分配器直接请求页面。 也可以使用更专业的分配器,例如 cma_alloc 或 zs_malloc。

大多数内存分配 API 使用 GFP 标志来表示应该如何分配内存。 GFP 的首字母缩略词代表“获取空闲页面(get free pages)”,即底层内存分配功能。

分配 API 的多样性与众多 GFP 标志相结合,而如何正确的分配内存,就是一个很难回答的问题。

kzalloc(<size>, GFP_KERNEL);

当然,在某些情况下,必须正确的使用内存分配 API 和不同的 GFP 标志。

Get Free Page flags

GFP 标志控制分配器的行为。 它们说明可以使用哪些内存区域分配器应该多努力地寻找空闲内存用户空间是否可以访问内存等Memory Management APIs 提供了 GFP 标志及其组合的参考文档,在这里我们简要概述 他们的推荐用法:

  • 大多数时候 GFP_KERNEL 是您所需要的。内核数据结构的内存、DMAable 内存、inode 缓存、所有这些以及许多其他分配类型都可以使用 GFP_KERNEL。注意,使用 GFP_KERNEL 隐含 GFP_RECLAIM,这意味着在内存压力下可能会触发直接回收;调用上下文必须是允许阻塞的。
  • 如果分配是从原子上下文执行的,例如中断处理程序,请使用 GFP_NOWAIT。此标志防止直接回收和 IO 或文件系统操作。因此,在内存压力下,GFP_NOWAIT 分配很可能会失败。具有合理回退的分配应该使用 GFP_NOWARN。
  • 如果您认为访问内存保留是合理的,并且除非分配成功,否则内核将受到压力,您可以使用 GFP_ATOMIC。
  • 从用户空间触发的不受信任的分配应该是 kmem 记帐的主题,并且必须设置 __GFP_ACCOUNT 位。对于 GFP_KERNEL 分配,有一个方便的 GFP_KERNEL_ACCOUNT 快捷方式,应该加以考虑。
  • 用户空间分配应使用 GFP_USER、GFP_HIGHUSER 或 GFP_HIGHUSER_MOVABLE 标志。标志名称越长,限制越少。
    • GFP_HIGHUSER_MOVABLE 不要求内核分配可通过直接映射访问的内存,并暗示数据是可移动的。
    • GFP_HIGHUSER 表示分配的内存不可移动,但不要求内核可以通过直接映射访问。 一个示例可能是将数据直接映射到用户空间但没有寻址限制的硬件分配。
    • GFP_USER 表示分配的内存是不可移动的,它必须能够被内核直接访问。

您可能会注意到现有代码中的很多分配都指定了 GFP_NOIO 或 GFP_NOFS。 从历史上看,它们用于防止由直接内存回收调用回 FS 或 IO 路径并阻塞已占用的资源引起的递归死锁。 从 4.12 开始,解决此问题的首选方法是使用 GFP masks used from FS/IO context 中描述的新范围 API

其他传统 GFP 标志是 GFP_DMA 和 GFP_DMA32。 它们用于确保分配的内存可由寻址能力有限的硬件访问。 因此,除非您正在为具有此类限制的设备编写驱动程序,否则请避免使用这些标志。 即使使用有限制的硬件,也最好使用 dma_alloc* API。

GFP flags and reclaim behavior

内存分配可能会触发直接或后台回收(direct or background reclaim),了解页面分配器为了满足各种请求的分配要求有多难是很有用的。

  • GFP_KERNEL & ~ __GFP_RECLAIM 不进行任何回收内存的尝试,积极的分配内存。这是最轻量级的模式,甚至不触发后台回收。应该小心使用,因为它可能会耗尽内存,并且下一个用户可能会进行更积极的回收。
  • GFP_KERNEL & ~__GFP_DIRECT_RECLAIM(aka GFP_NOWAIT)– 同样是积极分配,不尝试从当前上下文中释放内存,但如果内存zone低于低水位线,则可以唤醒 kswapd 以回收内存。可以在原子上下文中使用,也可以在请求是性能优化并且对于慢速路径有另一个回退时使用。
  • (GFP_KERNEL|__GFP_HIGH) & ~__GFP_DIRECT_RECLAIM (aka GFP_ATOMIC) 具有昂贵回退的非阻塞分配,因此它可以访问部分内存reserves。 通常在中断/下半部分上下文中使用,具有昂贵的慢速路径回退。
  • GFP_KERNEL | __GFP_NORETRY 覆盖默认分配器行为,所有的分配请求都在会导致内核进行破坏性回收之前失败,不会触发OMM killer
  • GFP_KERNEL | __GFP_RETRY_MAYFAIL – 覆盖默认分配器行为,所有分配请求都非常努力,如果回收无法取得任何进展,则请求将失败。 不会触发 OOM 杀手。
  • GFP_KERNEL | _GFP_NOFAIL-覆盖默认的分配器行为,所有分配请求将无休止地循环,直到成功。这可能真的很危险,尤其是对于很大的请求。
Selecting memory allocator

分配内存最直接的方法是使用 kmalloc() 系列中的函数。而且,为了安全起见,最好使用将内存设置为零的方法,例如 kzalloc()。如果需要为数组分配内存,可以使用 kmalloc_array() 和 kcalloc()。struct_size()、array_size() 和 array3_size() 可用于安全地计算对象大小而不会溢出。

可以使用 kmalloc 分配的块的最大大小是有限的。实际限制取决于硬件和内核配置,但最好将 kmalloc 用于小于页面大小的对象。

使用 kmalloc 分配的块的地址至少与 ARCH_KMALLOC_MINALIGN 字节对齐。对于大小是 2 的幂,对齐也保证至少是相应的大小。

用 kmalloc() 分配的块可以用 krealloc() 调整大小。与 kmalloc_array() 类似:以 krealloc_array() 的形式提供了一个用于调整数组大小的助手。

对于大型分配,您可以使用 vmalloc() 和 vzalloc(),或者直接从页面分配器请求页面。 vmalloc 和相关函数分配的内存在物理上不是连续的。

如果您不确定 kmalloc 的分配大小是否太大,可以使用 kvmalloc() 及其派生类。它将尝试使用 kmalloc 分配内存,如果分配失败,它将使用 vmalloc 重试。 kvmalloc 可以使用哪些 GFP 标志是有限制的;请参阅 kvmalloc_node() 参考文档。请注意,kvmalloc 可能会返回物理上不连续的内存。

如果您需要分配许多相同的对象,您可以使用slab 缓存分配器。在使用缓存之前,应该使用 kmem_cache_create() 或 kmem_cache_create_usercopy() 设置缓存。如果缓存的一部分可能被复制到用户空间,则应该使用第二个函数。创建缓存后,kmem_cache_alloc() 及其便利包装器可以从该缓存中分配内存。

当不再需要分配的内存时,必须释放它。您可以将 kvfree() 用于使用 kmalloc、vmalloc 和 kvmalloc 分配的内存。平板缓存应该使用 kmem_cache_free() 释放。并且不要忘记使用 kmem_cache_destroy() 销毁缓存。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注