内存管理-1-madvise()


一、man madvise

翻译:https://man7.org/linux/man-pages/man2/madvise.2.html //这个文档内容要比man madvise的内容新

madvise - 提供有关内存使用的建议

位于标准C库里面,函数原型:

#include <sys/mman.h>

int madvise(void *addr, size_t length, int advice);

 

1. 描述

madvise() 系统调用用于向内核提供有关从地址 addr 开始且具有大小 length 的地址范围的建议或指示。 madvise() 仅对整个页面进行操作,因此 addr 必须是页面对齐的。 length 的值向上舍入为页面大小的倍数。 在大多数情况下,此类建议的目标是提高系统或应用程序性能

最初,系统调用支持一组“常规”建议值,这些值也可用于其他几种实现。 (但请注意,POSIX 中未指定 madvise()。)随后,添加了许多特定于 Linux 的建议值。

常规建议值:

下面列出的建议值允许应用程序告诉内核它希望如何使用某些映射或共享内存区域,以便内核可以选择适当的预读和缓存技术。 这些建议值不会影响应用程序的语义(MADV_DONTNEED 情况除外),但可能会影响其性能。 此处列出的所有建议值在 POSIX 指定的 posix_madvise(3) 函数中都有类似物,并且这些值具有相同的含义,但 MADV_DONTNEED 除外。

该建议在 advice 参数中指出,它是以下之一:

MADV_NORMAL

没有特殊处理。 这是默认设置。

MADV_RANDOM

期望页面引用按随机顺序。(因此,预读可能没有平时那么有用。)

MADV_SEQUENTIAL

期望页面引用按顺序排列。(因此,可以积极地提前读取给定范围内的页面,并且可以在访问它们后立即释放它们。)

MADV_WILLNEED

预计在不久的将来能够访问。(因此,提前阅读几页可能是个好主意。)

MADV_DONTNEED

不要指望在不久的将来能够访问。(目前,应用程序已完成给定范围,因此内核可以释放与其关联的资源。)

成功执行 MADV_DONTNEED 操作后,指定区域中的内存访问语义将发生更改:该范围内的页面的后续访问将成功,但将导致从底层映射文件的最新内容重新填充内存内容 (用于共享文件映射、共享匿名映射和基于 shmem 的技术,例如 System V 共享内存段)或用于匿名私有映射的按需零填充页面。

请注意,当应用于共享映射时,MADV_DONTNEED 可能不会导致立即释放该范围内的页面。 内核可以自由地延迟释放页面,直到适当的时刻。 然而,调用进程的驻留集大小(RSS)将立即减少。

MADV_DONTNEED 不能应用于锁定页面或 VM_PFNMAP 页面。 (标有内核内部 VM_PFNMAP 标志的页面是不由虚拟内存子系统管理的特殊内存区域。此类页面通常由将页面映射到用户空间的设备驱动程序创建。)

Linux v5.18 中添加了对大 TLB 页的支持。 大 TLB 页支持的映射内的地址必须与基础大 TLB 页大小对齐,并且范围长度向上舍入为基础大 TLB 页大小的倍数。


Linux 特定的建议值:

以下特定于 Linux 的建议值在 POSIX 指定的 posix_madvise(3) 中没有对应项,并且在其他实现上可用的 madvise() 接口中可能有也可能没有对应项。 请注意,其中一些操作会更改内存访问的语义。

MADV_REMOVE

释放给定范围的页面及其关联的后备存储。 这相当于在后备存储的相应字节范围中打了一个洞(请参阅fallocate(2))。 指定地址范围内的后续访问将看到包含零的字节。

指定的地址范围必须映射为共享且可写。 此标志不能应用于锁定页、大型 TLB 页或 VM_PFNMAP 页。

在最初的实现中,只有 tmpfs 支持 MADV_REMOVE; 但从 Linux 3.5 开始,任何支持 Fallocate(2) FALLOC_FL_PUNCH_HOLE 模式的文件系统也支持 MADV_REMOVE。不支持 MADV_REMOVE 的文件系统会失败并出现错误 EOPNOTSUPP。

Linux v4.3 中添加了对巨型 TLB 文件系统的支持。

MADV_DONTFORK

在 fork(2) 之后,不要让子进程使用此范围内的页面。 如果父级在 fork(2) 之后写入页面,这对于防止写时复制语义更改页面的物理位置很有用。(此类页面重定位会导致 DMA 进入页面的硬件出现问题。)

MADV_DOFORK

撤消 MADV_DONTFORK 的效果,恢复默认行为,从而跨 fork(2) 继承映射。

MADV_HWPISON

对 addr 和 length 指定范围内的页面进行毒害,并处理对这些页面的后续引用,例如硬件内存损坏。 此操作仅适用于特权 (CAP_SYS_ADMIN) 进程。 此操作可能会导致调用进程接收 SIGBUS 并且页面被取消映射。

此功能用于测试内存错误处理代码; 仅当内核配置了 CONFIG_MEMORY_FAILURE 时才可用。


MADV_MERGEABLE

对 addr 和 length 指定范围内的页面启用内核同页合并 (KSM)。 内核定期扫描用户内存中已标记为可合并的区域,查找具有相同内容的页面。 这些被单个写保护页面替换(如果以后进程想要更新页面内容,则会自动复制该页面)。 KSM 仅合并私有匿名页面(请参阅 mmap(2))。

KSM 功能适用于生成相同数据的许多实例的应用程序(例如 KVM 等虚拟化系统)。 它会消耗大量的处理能力;小心使用。 有关更多详细信息,请参阅 Linux 内核源文件 Documentation/vm/ksm.txt。

仅当使用 CONFIG_KSM 配置内核时,MADV_MERGEABLE 和 MADV_UNMERGEABLE 操作才可用。


MADV_UNMERGEABLE

撤消先前 MADV_MERGEABLE 操作对指定地址范围的影响; KSM 取消合并在 addr 和 length 指定的地址范围内合并的所有页面。


MADV_SOFT_OFFLINE

对 addr 和 length 指定范围内的页面进行软脱机。 指定范围内的每个页面的内存都被保留(即,当下次访问时,相同的内容将可见,但在新的物理页框架中),并且原始页面脱机(即不再使用,并从正常的内存管理中取出)。 MADV_SOFT_OFFLINE 操作的效果对于调用进程来说是不可见的(即不改变其语义)。

此功能用于测试内存错误处理代码; 仅当内核配置了 CONFIG_MEMORY_FAILURE 时才可用。


MADV_HUGEPAGE

为 addr 和 length 指定范围内的页面启用透明大页 (THP)。 内核会定期扫描标记为大页候选的区域,以用大页替换它们。 当区域自然对齐大页大小时,内核也会直接分配大页(请参阅 posix_memalign(2))。

此功能主要针对使用大型数据映射并一次访问该内存的较大区域的应用程序(例如 QEMU 等虚拟化系统)。 它很容易浪费内存(例如,仅访问 1 个字节的 2 MB 映射将导致 2 MB 有线内存,而不是 1 个 4 KB 页面)。 有关更多详细信息,请参阅 Linux 内核源文件 Documentation/admin-guide/mm/transhuge.rst。

大多数常见的内核配置默认提供 MADV_HUGEPAGE 样式的行为,因此通常不需要 MADV_HUGEPAGE。 它主要用于嵌入式系统,其中内核中默认情况下可能未启用 MADV_HUGEPAGE 样式行为。 在此类系统上,可以使用此标志来选择性地启用 THP。 每当使用 MADV_HUGEPAGE 时,它应该始终位于具有访问模式的内存区域中,开发人员事先知道在启用透明大页时不会有增加应用程序内存占用的风险。

从 Linux 5.4 开始,自动扫描符合条件的区域并用大页面替换适用于私有匿名页面(请参阅 mmap(2))、shmem 页面和文件支持页面。 对于所有内存类型,内存只能被大页对齐边界上的大页替换。 对于文件映射内存(包括 tmpfs(请参阅 tmpfs(2))),映射也必须在文件内自然大页对齐。 此外,对于文件支持的非 tmpfs 内存,文件不得打开进行写入,并且映射必须可执行。

VMA 不得标记为 VM_NOHUGEPAGE、VM_HUGETLB、VM_IO、VM_DONTEXPAND、VM_MIXEDMAP 或 VM_PFNMAP,也不能是堆栈内存或由启用 DAX 的设备支持(除非 DAX 设备作为系统 RAM 热插拔)。 该进程还不得设置 PR_SET_THP_DISABLE(请参阅 prctl(2))。

仅当内核配置了 CONFIG_TRANSPARENT_HUGEPAGE 时,MADV_HUGEPAGE、MADV_NOHUGEPAGE 和 MADV_COLLAPSE 操作才可用,并且仅当内核配置了 CONFIG_READ_ONLY_THP_FOR_FS 时才支持文件/shmem 内存。


MADV_NOHUGEPAGE

确保 addr 和 length 指定的地址范围内的内存不会受到透明大页的支持。


MADV_COLLAPSE (since Linux 6.1)

对内存范围映射的本机页面执行尽力同步折叠为透明大页面 (THP)。 MADV_COLLAPSE 对调用进程的当前内存状态进行操作,并且不会对页面将来如何映射、构造或出错进行持久更改或保证。

MADV_COLLAPSE 支持私有匿名页面(请参阅 mmap(2))、shmem 页面和文件支持页面。 有关 THP 内存要求的一般信息,请参阅 MADV_HUGEPAGE。 如果提供的范围跨越多个 VMA,则每个 VMA 上的折叠语义独立于其他 VMA。 如果给定大页面对齐/大小区域的崩溃失败,则操作可能会继续尝试崩溃指定内存的剩余部分。 MADV_COLLAPSE 将自动将提供的范围限制为大页对齐。

该范围覆盖的所有非驻留页面将首先被swapped/faulted-in,然后被复制到新分配的大页面上。 如果本机页面组成相同的 PTE 映射大页面,并且适当对齐,则可能会省略新大页面的分配,并且可能会就地发生崩溃。 未映射的页面将在新的大页面中将其数据直接初始化为 0。 但是,对于要折叠的每个符合条件的大页对齐/大小区域,当前必须至少有一个页面由物理内存支持。

MADV_COLLAPSE 独立于 /sys/kernel/mm/transparent_hugepage 下的任何 sysfs(请参阅 sysfs(5))设置,无论是在确定 THP 资格还是分配语义方面。 有关详细信息,请参阅 Linux 内核源文件 Documentation/admin-guide/mm/transhuge.rst。 在操作 tmpfs 文件时,MADV_COLLAPSE 也会忽略 huge= tmpfs mount。 新大页的分配可能会进入直接回收和/或压缩,而不管 VMA 标志如何(尽管仍然遵守 VM_NOHUGEPAGE)。

当系统有多个NUMA节点时,hugepage 将从提供最多 native page 的节点开始分配。

如果提供的范围覆盖的所有大页大小/对齐区域都已成功折叠,或者已经是 PMD 映射的 THP,则此操作将被视为成功。 请注意,这并不能保证内存的其他可能映射。 如果多个大页对齐/大小的区域无法折叠,则只有最近失败的代码才会被设置在 errno 中。


MADV_DONTDUMP (since Linux 3.4)

从核心转储中排除 addr 和 length 指定范围内的那些页面。 这对于具有大面积内存且已知在核心转储中无用的应用程序非常有用。 MADV_DONTDUMP 的效果优先于通过 /proc/PID/coredump_filter 文件设置的位掩码(请参阅 core(5))。


MADV_DODUMP

撤消早期 MADV_DONTDUMP 的效果。


MADV_FREE (since Linux 4.5)

应用程序不再需要 addr 和 length 指定范围内的页面。 因此,内核可以释放这些页面,但释放可能会延迟,直到出现内存压力。 对于每个已标记为释放但尚未释放的页面,如果调用者写入该页面,则释放操作将被取消。 成功执行 MADV_FREE 操作后,当内核释放页面时,任何陈旧数据(即脏的、未写入的页面)都将丢失。 但是,后续对该范围内的页面的写入将会成功,然后内核无法释放那些脏页面,因此调用者始终可以看到刚刚写入的数据。 如果没有后续写入,内核可以随时释放这些页面。 一旦该范围内的页面被释放,调用者将在后续页面引用时看到按需填零的页面。

MADV_FREE 操作只能应用于私有匿名页面(请参阅 mmap(2))。 在 Linux 4.12 之前,在无交换系统上释放页面时,无论内存压力如何,给定范围内的页面都会立即释放。


MADV_WIPEONFORK (since Linux 4.14)

在 fork(2) 之后,向子进程提供此范围内的零填充内存。 这在 forking 服务器中非常有用,以确保敏感的每个进程数据(例如,PRNG 种子、加密秘密等)不会传递给子进程。

MADV_WIPEONFORK 操作只能应用于私有匿名页面(请参阅 mmap(2))。

在 fork(2) 创建的子进程中,MADV_WIPEONFORK 设置在指定的地址范围内保持不变。 该设置在 execve(2) 期间被清除。


MADV_KEEPONFORK (since Linux 4.14)

撤消早期 MADV_WIPEONFORK 的效果。


MADV_COLD (since Linux 5.4)

停用给定范围的页面。 如果存在内存压力,这将使页面更可能成为回收目标。 这是一种非破坏性操作。 当该建议不适用时,该范围内的某些页面可能会忽略该建议。


MADV_PAGEOUT (since Linux 5.4)

回收给定范围的页面。 这样做是为了释放这些页面占用的内存。 如果页面是匿名的,它将被换出。 如果页面是文件支持的且脏的,则它将被写回后备存储。 当该建议不适用时,该范围内的某些页面可能会忽略该建议。


MADV_POPULATE_READ (since Linux 5.14)

“填充(故障前)页表可读,在范围内的所有页面中出现故障,就像从每个页面手动读取一样;但是,请避免在处理故障后执行的实际内存访问。

与 MAP_POPULATE 相比,MADV_POPULATE_READ 不会隐藏错误,可以应用于(部分)现有映射,并且始终填充(默认)可读的页表。 一个示例用例是对文件映射进行 prefaulting 预故障,从磁盘读取所有文件内容; 但是,页面不会被弄脏,因此在从内存中逐出页面时不必将页面写回磁盘。

根据底层映射,映射共享zeropage、预分配内存或者读取底层文件; 有漏洞的文件可能会也可能不会预分配块。 如果填充失败,则不会生成SIGBUS信号; 相反,会返回错误。

如果 MADV_POPULATE_READ 成功,则所有页表都已填充(预故障)可读一次。 如果 MADV_POPULATE_READ 失败,则可能已填充某些页表。

MADV_POPULATE_READ 不能应用于没有读取权限和特殊映射的映射,例如,使用内核内部标志(如 VM_PFNMAP 或 VM_IO)标记的映射,或使用 memfd_secret(2) 创建的秘密内存区域。

请注意,使用 MADV_POPULATE_READ,当系统内存不足时,可以随时终止进程。


MADV_POPULATE_WRITE (since Linux 5.14)

填充(故障前)页表可写,在范围内的所有页面中发生故障,就像手动写入每个页面一样; 但是,请避免在处理故障后执行的实际内存访问。

与 MAP_POPULATE 相比,MADV_POPULATE_WRITE 不会隐藏错误,可以应用于(部分)现有映射,并且始终填充(默认)可写的页表。 一个示例用例是预分配内存,破坏任何 CoW(写入时复制)。

根据底层映射,预分配内存或者读取底层文件; 有漏洞的文件将预先分配块。 如果填充失败,则不会生成SIGBUS信号; 相反,会返回错误。

如果 MADV_POPULATE_WRITE 成功,则所有页表都已填充(预先故障)可写一次。 如果 MADV_POPULATE_WRITE 失败,则可能已填充某些页表。

MADV_POPULATE_WRITE 不能应用于没有写权限和特殊映射的映射,例如,使用内核内部标志(如 VM_PFNMAP 或 VM_IO)标记的映射,或使用 memfd_secret(2) 创建的秘密内存区域。

请注意,使用 MADV_POPULATE_WRITE,当系统内存不足时,可以随时终止进程。


2. 返回值

成功时, madvise() 返回零。 出错时,它返回 -1 并适当设置 errno。

版本:

该系统调用的版本实现了各种建议值,存在于许多其他实现中。 其他实现通常至少实现上面在常规建议标志下列出的标志,尽管语义上有一些变化。

POSIX.1-2001 描述了 posix_madvise(3),其中包含常量 POSIX_MADV_NORMAL、POSIX_MADV_RANDOM、POSIX_MADV_SEQUENTIAL、POSIX_MADV_WILLNEED 和 POSIX_MADV_DONTNEED 等,其行为类似于上面列出的类似命名的标志。

Linux

Linux 实现要求地址 addr 是页对齐的,并且允许 length 为零。 如果指定地址范围的某些部分未映射,则 Linux 版本的 madvise() 会忽略它们并将调用应用于其余部分(但应从系统调用中返回 ENOMEM)。

madvise(0, 0, advice) 将返回零,当且仅当 advice 受内核支持并且可以依靠它来探测支持。

历史:
从 Linux 3.18 开始,对此系统调用的支持是可选的,具体取决于 CONFIG_ADVISE_SYSCALLS 配置选项的设置。

 

二、总结

1. madvise() 函数建议内核,在从 addr 指定的地址开始,长度等于 len 参数值的范围内,该区域的用户虚拟内存应遵循特定的使用模式。内核使用这些信息优化与指定范围关联的资源的处理和维护过程。如果使用 madvise() 函数的程序明确了解其内存访问模式,则使用此函数可以提高系统性能。

2. MADV_DONTNEED 会触发内存回收。

3. MADV_PAGEOUT 会触发内存会被压缩到ZRAM里。

 

posted on 2024-03-11 09:35  Hello-World3  阅读(145)  评论(0编辑  收藏  举报

导航