内存管理-14-内核文档翻译-2-memory-allocation.rst 和 gfp_mask-from-fs-io.rst
一、memory-allocation.rst
msm-5.4/Documentation/core-api/memory-allocation.rst 翻译:
========================
内存分配指南
==========================
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 标志。
GFP标志
====================
GFP 标志控制分配器的行为。它们指示可以使用哪些 memory zones、分配器应多努力寻找可用内存、用户空间是否可以访问内存等。
:ref:`Documentation/core-api/mm-api.rst <mm-api-gfp-flags>` 提供了 GFP 标志及其组合的参考文档,
我们在此简要概述了它们的推荐用法:
* 大多数情况下,您所需要的是“GFP_KERNEL”。内核数据结构、DMA 内存、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 开始,解决此问题的首选方法是使用 :ref:“Documentation/core-api/gfp_mask-from-fs-io.rst <gfp_mask_from_fs_io>”中描述的新范围 API。
其他旧版 GFP 标志是“GFP_DMA”和“GFP_DMA32”。它们用于确保分配的内存可由具有有限寻址能力的硬件访问。因此,除非您正在为具有此类限制的设备编写驱动程序,否则请避免使用这些标志。即使对于有限制的硬件,也最好使用“dma_alloc*”API。
选择内存分配器
===========================
分配内存的最直接方法是使用 :c:func:`kmalloc()` 系列中的函数。并且,为了保证安全大小,最好使用将内存设置为零的例程,例如 :c:func:`kzalloc()`。如果您需要为数组分配内存,可以使用 :c:func:`kmalloc_array()` 和 :c:func:`kcalloc()` 帮助程序。
可以使用 `kmalloc()` 分配的块的最大大小是有限的。实际限制取决于硬件和内核配置,但对于小于页面大小的对象,使用 `kmalloc()` 是一种很好的做法。
使用 `kmalloc()` 分配的块的地址至少与 ARCH_KMALLOC_MINALIGN(默认128) 字节对齐。对于大小为 2 的幂,对齐也保证至少为相应的大小。
对于大型分配,您可以使用 :c:func:`vmalloc()` 和 :c:func:`vzalloc()`,或直接从页面分配器请求页面。 由 `vmalloc()` 和相关函数分配的内存在物理上并不连续。
如果您不确定分配大小是否对于 `kmalloc()` 来说太大,可以使用 :c:func:`kvmalloc()` 及其派生函数。它将尝试使用 `kmalloc()` 分配内存,如果分配失败,将使用 `vmalloc()` 重试。对于那些 GFP 标志可以与 `kvmalloc()` 一起使用存在限制;请参阅 :c:func:`kvmalloc_node()` 参考文档。请注意,`kvmalloc()` 可能会返回物理上不连续的内存。
如果您需要分配许多相同的对象,则可以使用 slab 缓存分配器。应使用 :c:func:`kmem_cache_create()` 或 :c:func:`kmem_cache_create_usercopy()` 设置缓存,然后才能使用。如果缓存的一部分可能被复制到用户空间,则应使用第二个函数。创建缓存后,:c:func:`kmem_cache_alloc()` 及其便利包装器可以从该缓存分配内存。
当不再需要分配的内存时,必须将其释放。您可以使用 :c:func:`kvfree()` 来释放使用 `kmalloc()`、`vmalloc()` 和 `kvmalloc()` 分配的内存。应使用 :c:func:`kmem_cache_free()` 释放 slab 缓存。并且不要忘记使用 :c:func:`kmem_cache_destroy()` 销毁缓存。
二、gfp_mask-from-fs-io.rst
.. _gfp_mask_from_fs_io:
=================================
FS/IO 上下文中使用的 GFP 掩码
===================================
:日期:2018 年 5 月
:作者:Michal Hocko <mhocko@kernel.org>
简介
============
文件系统和 IO 堆栈中的代码路径在分配内存时必须小心,以防止由直接内存回收调用回 FS 或 IO 路径并阻塞已持有的资源(例如锁 - 最常用于事务上下文的锁)引起的递归死锁。
避免此死锁问题的传统方法是在调用分配器时分别清除 gfp 掩码中的 __GFP_FS 和 __GFP_IO (注意后者也意味着清除第一个)。GFP_NOFS 和 GFP_NOIO 可以用作快捷方式。但事实证明,当“以防万一”使用受限 gfp 掩码而没有进行更深入的考虑时,上述方法会导致滥用,这会导致问题,因为过度使用 GFP_NOFS/GFP_NOIO 会导致内存过度回收或其他内存回收问题。
新 API
========
自 4.12 以来,我们确实有一个适用于 NOFS 和 NOIO 上下文的通用范围 API
``memalloc_nofs_save()``、``memalloc_nofs_restore()``相应地``memalloc_noio_save()``、``memalloc_noio_restore()``,它们允许从文件系统或 I/O 的角度将范围标记为临界区。来自该范围的任何分配都将从给定掩码中固有地删除 __GFP_FS 和 __GFP_IO,因此在 FS/IO 中不会有内存分配递归。补充:其实就是将 PF_MEMALLOC_NOFS/PF_MEMALLOC_NOIO 标记到 current->flags 中。
.. 内核文档:: include/linux/sched/mm.h
:函数: memalloc_nofs_save memalloc_nofs_restore
.. 内核文档:: include/linux/sched/mm.h
:函数: memalloc_noio_save memalloc_noio_restore
举例:
static void o2net_start_connect(struct work_struct *work) //tcp.c { noio_flag = memalloc_noio_save(); XXX memalloc_noio_restore(noio_flag); }
那么 __vmalloc(GFP_NOFS) 呢
================================
vmalloc() 不支持 GFP_NOFS 语义,因为在分配器内部深处有硬编码的 GFP_KERNEL 分配,修复起来非常困难。这意味着使用 GFP_NOFS/GFP_NOIO 调用 ``vmalloc()`` 几乎总是一个错误。好消息是 NOFS/NOIO 语义可以通过作用域 API 实现。
在理想情况下,上层应该已经标记了危险上下文,因此不需要特别注意,并且应该可以毫无问题地调用 vmalloc。有时,如果上下文不太清楚或存在分层违规,那么建议的解决方法是通过作用域 API 包装 ``vmalloc`` 并使用注释解释问题。
posted on 2024-09-02 20:32 Hello-World3 阅读(42) 评论(0) 编辑 收藏 举报