内存管理 API

用户空间内存访问

get_user

get_user (x, ptr)

从用户空间获取一个简单变量。

参数

x

存储结果的变量。

ptr

源地址,在用户空间中。

上下文

仅限用户上下文。如果启用了页故障,此函数可能会休眠。

描述

此宏将单个简单变量从用户空间复制到内核空间。它支持 char 和 int 等简单类型,但不支持结构体或数组等较大类型。

ptr 必须是指向简单变量的指针类型,并且解引用 ptr 的结果必须可以赋值给 x,无需类型转换。

返回

成功时返回零,错误时返回 -EFAULT。出错时,变量 x 设置为零。

__get_user

__get_user (x, ptr)

从用户空间获取一个简单变量,检查较少。

参数

x

存储结果的变量。

ptr

源地址,在用户空间中。

上下文

仅限用户上下文。如果启用了页故障,此函数可能会休眠。

描述

此宏将单个简单变量从用户空间复制到内核空间。它支持 char 和 int 等简单类型,但不支持结构体或数组等较大类型。

ptr 必须是指向简单变量的指针类型,并且解引用 ptr 的结果必须可以赋值给 x,无需类型转换。

调用此函数前,调用者必须使用 access_ok() 检查指针。

返回

成功时返回零,错误时返回 -EFAULT。出错时,变量 x 设置为零。

put_user

put_user (x, ptr)

将一个简单值写入用户空间。

参数

x

要复制到用户空间的值。

ptr

目标地址,在用户空间中。

上下文

仅限用户上下文。如果启用了页故障,此函数可能会休眠。

描述

此宏将单个简单值从内核空间复制到用户空间。它支持 char 和 int 等简单类型,但不支持结构体或数组等较大类型。

ptr 必须是指向简单变量的指针类型,并且 x 必须可以赋值给解引用 ptr 的结果。

返回

成功时返回零,错误时返回 -EFAULT。

__put_user

__put_user (x, ptr)

将一个简单值写入用户空间,检查较少。

参数

x

要复制到用户空间的值。

ptr

目标地址,在用户空间中。

上下文

仅限用户上下文。如果启用了页故障,此函数可能会休眠。

描述

此宏将单个简单值从内核空间复制到用户空间。它支持 char 和 int 等简单类型,但不支持结构体或数组等较大类型。

ptr 必须是指向简单变量的指针类型,并且 x 必须可以赋值给解引用 ptr 的结果。

调用此函数前,调用者必须使用 access_ok() 检查指针。

返回

成功时返回零,错误时返回 -EFAULT。

unsigned long clear_user(void __user *to, unsigned long n)

将用户空间中的一块内存清零。

参数

void __user *to

目标地址,在用户空间中。

unsigned long n

要清零的字节数。

描述

将用户空间中的一块内存清零。

返回

未能清零的字节数。成功时,此值为零。

unsigned long __clear_user(void __user *to, unsigned long n)

将用户空间中的一块内存清零,检查较少。

参数

void __user *to

目标地址,在用户空间中。

unsigned long n

要清零的字节数。

描述

将用户空间中的一块内存清零。调用此函数前,调用者必须使用 access_ok() 检查指定的内存块。

返回

未能清零的字节数。成功时,此值为零。

int get_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages)

锁定用户页面到内存

参数

unsigned long start

起始用户地址

int nr_pages

从起始地址开始锁定的页数

unsigned int gup_flags

修改锁定行为的标志

struct page **pages

接收锁定页面指针的数组。长度应至少为 nr_pages。

描述

尝试在不获取 mm->mmap_lock 的情况下锁定用户页面到内存。如果失败,它将回退到获取锁并调用 get_user_pages()。

返回锁定的页数。这可能少于请求的页数。如果 nr_pages 为 0 或负数,返回 0。如果没有锁定任何页面,返回 -errno。

内存分配控制

页面移动性和放置提示

这些标志提供了关于页面移动性的提示。具有相似移动性的页面被放置在相同的页块中,以尽量减少外部碎片引起的问题。

__GFP_MOVABLE (也是一个区域修饰符)表示页面可以在内存压缩期间通过页面迁移移动或可以被回收。

__GFP_RECLAIMABLE 用于指定 SLAB_RECLAIM_ACCOUNT 的 slab 分配,其页面可以通过收缩器释放。

__GFP_WRITE 表示调用者打算脏化页面。在可能的情况下,这些页面将分散在本地区域之间,以避免所有脏页面集中在一个区域(公平区域分配策略)。

__GFP_HARDWALL 强制执行 cpuset 内存分配策略。

__GFP_THISNODE 强制分配从请求的节点满足,不进行回退或放置策略强制。

__GFP_ACCOUNT 导致分配被计入 kmemcg。

__GFP_NO_OBJ_EXT 导致 slab 分配没有对象扩展。

水印修饰符 -- 控制对紧急储备的访问

__GFP_HIGH 表示调用者优先级高,并且在系统能够向前推进之前,必须满足请求。例如,创建 IO 上下文以清除页面和原子上下文的请求。

__GFP_MEMALLOC 允许访问所有内存。这只应在调用者保证分配将很快释放更多内存时使用,例如进程退出或交换。用户要么是 MM,要么与 VM 密切协调(例如,通过 NFS 交换)。此标志的用户必须极其小心,不要完全耗尽储备,并实施一种节流机制,根据释放的内存量控制储备的消耗。在使用此标志之前,应始终考虑使用预分配池(例如 mempool)。

__GFP_NOMEMALLOC 用于明确禁止访问紧急储备。如果两者都设置,此标志优先于 __GFP_MEMALLOC 标志。

回收修饰符

请注意,以下所有标志仅适用于可休眠分配(例如 GFP_NOWAITGFP_ATOMIC 将忽略它们)。

__GFP_IO 可以启动物理 IO。

__GFP_FS 可以调用底层 FS。清除此标志可避免分配器递归进入可能已持有锁的文件系统。

__GFP_DIRECT_RECLAIM 表示调用者可能进入直接回收。可以清除此标志以避免在有备用选项时出现不必要的延迟。

__GFP_KSWAPD_RECLAIM 表示当达到低水位线时,调用者希望唤醒 kswapd 并让其回收页面直到达到高水位线。当有备用选项并且回收可能扰乱系统时,调用者可能希望清除此标志。典型的例子是 THP 分配,其中回退成本低廉,但回收/压缩可能导致间接停顿。

__GFP_RECLAIM 是允许/禁止直接回收和 kswapd 回收的简写。

默认分配器行为取决于请求大小。我们有成本高昂的分配(order > PAGE_ALLOC_COSTLY_ORDER)的概念。非成本高昂的分配通常非常关键,默认情况下不会失败(除了 OOM 受害者可能会失败,因此调用者仍然需要检查失败),而成本高昂的请求则尽量不具有破坏性,即使不调用 OOM killer 也会回退。以下三个修饰符可用于覆盖这些隐式规则中的一些。请注意,所有这些修饰符都必须与 __GFP_DIRECT_RECLAIM 标志一起使用。

__GFP_NORETRY: VM 实现只会尝试非常轻量的内存直接回收以在内存压力下获取一些内存(因此它可以休眠)。它将避免像 OOM killer 这样的破坏性操作。调用者必须处理在重内存压力下很可能发生的失败。当可以以较小代价轻松处理失败时,例如降低吞吐量,此标志是合适的。

__GFP_RETRY_MAYFAIL: 如果有迹象表明其他地方取得了进展,VM 实现将重试以前失败的内存回收过程。它可以等待其他任务尝试释放内存的高级方法,例如碎片整理(清除碎片)和页面换出。重试次数仍然有明确的限制,但比 __GFP_NORETRY 更大。带有此标志的分配可能会失败,但仅当真正没有多少未使用的内存时。虽然这些分配不会直接触发 OOM killer,但它们的失败表明系统可能很快需要使用 OOM killer。调用者必须处理失败,但可以合理地通过使更高级别的请求失败,或以效率低得多的方式完成它来实现。如果分配确实失败,并且调用者能够释放一些非必要内存,这样做可能会使整个系统受益。

__GFP_NOFAIL: VM 实现_必须_无限期重试:调用者无法处理分配失败。分配可能无限期阻塞,但绝不会返回失败。测试失败是毫无意义的。它_必须_是可阻塞的,并与 __GFP_DIRECT_RECLAIM 一起使用。它_绝不_应在不可休眠的上下文中使用。新用户应仔细评估(并且此标志应仅在没有合理失败策略时使用),但使用此标志绝对优于围绕分配器编写无限循环代码。不支持使用 __GFP_NOFAIL 且 order > 1 从 buddy 分配页面。请考虑使用 kvmalloc() 代替。

有用的 GFP 标志组合

常用的一些有用的 GFP 标志组合。建议子系统从这些组合之一开始,然后根据需要设置/清除 __GFP_FOO 标志。

GFP_ATOMIC 用户不能休眠,并且需要分配成功。应用较低的水位线以允许访问“原子储备”。当前实现不支持 NMI 和其他一些严格的非抢占上下文(例如 raw_spin_lock)。GFP_NOWAIT 也是如此。

GFP_KERNEL 是内核内部分配的典型标志。调用者需要 ZONE_NORMAL 或较低的区域进行直接访问,但可以进行直接回收。

GFP_KERNEL_ACCOUNT 与 GFP_KERNEL 相同,只是分配会计入 kmemcg。

GFP_NOWAIT 用于不应因直接回收而停顿、启动物理 IO 或使用任何文件系统回调的内核分配。它很可能无法分配内存,即使是很小的分配。

GFP_NOIO 将使用直接回收来丢弃不需要启动任何物理 IO 的干净页面或 slab 页面。请尽量避免直接使用此标志,而是使用 memalloc_noio_{save,restore} 标记整个不能执行任何 IO 的范围,并附带简短的解释。所有分配请求都将隐式继承 GFP_NOIO。

GFP_NOFS 将使用直接回收,但不会使用任何文件系统接口。请尽量避免直接使用此标志,而是使用 memalloc_nofs_{save,restore} 标记整个不能/不应递归进入 FS 层的范围,并附带简短的解释。所有分配请求都将隐式继承 GFP_NOFS。

GFP_USER 用于用户空间分配,这些分配也需要内核或硬件直接访问。它通常由硬件用于映射到用户空间的缓冲区(例如图形),硬件仍必须对其进行 DMA。这些分配会强制执行 cpuset 限制。

GFP_DMA 出于历史原因存在,应尽可能避免使用。此标志表示调用者要求使用最低区域(ZONE_DMA 或 x86-64 上的 16M)。理想情况下,这将被删除,但这需要仔细审计,因为有些用户确实需要它,而另一些用户则使用此标志来避免 ZONE_DMA 中的低内存储备,并将最低区域视为一种紧急储备。

GFP_DMA32 类似于 GFP_DMA,只是调用者需要一个 32 位地址。请注意,kmalloc(..., GFP_DMA32) 不会返回 DMA32 内存,因为 DMA32 kmalloc 缓存数组未实现。(原因:内核中没有这样的用户)。

GFP_HIGHUSER 用于用户空间分配,这些分配可能映射到用户空间,不需要内核直接访问,但一旦使用就不能移动。一个例子可能是将数据直接映射到用户空间但没有寻址限制的硬件分配。

GFP_HIGHUSER_MOVABLE 用于内核不需要直接访问但需要访问时可以使用 kmap() 的用户空间分配。它们预计可以通过页面回收或页面迁移移动。通常,LRU 上的页面也将使用 GFP_HIGHUSER_MOVABLE 分配。

GFP_TRANSHUGEGFP_TRANSHUGE_LIGHT 用于 THP 分配。它们是复合分配,如果内存不可用,通常会迅速失败,并且在失败时不会唤醒 kswapd/kcompactd。_LIGHT 版本完全不尝试回收/压缩,默认用于页故障路径,而非 light 版本由 khugepaged 使用。

Slab 缓存

SLAB_HWCACHE_ALIGN

SLAB_HWCACHE_ALIGN

将对象对齐到缓存行边界。

描述

足够大的对象会按照缓存行边界对齐。对于小于缓存行大小一半的对象,对齐方式是缓存行大小的一半。通常,如果对象大小小于缓存行大小的 1/2^n,则对齐方式调整为 1/2^n。

如果相应的 struct kmem_cache_args 字段也请求了显式对齐,则应用两者中较大的对齐方式。

SLAB_TYPESAFE_BY_RCU

SLAB_TYPESAFE_BY_RCU

警告 阅读此内容!

描述

这会将 SLAB 页的释放延迟一个宽限期,但_不_会延迟对象的释放。这意味着,如果您调用 kmem_cache_free(),该内存位置可以随时被重用。因此,在同一个 RCU 宽限期内,可能会在该位置看到另一个对象。

此功能仅确保支持对象的内存位置保持有效,使用此功能的诀窍是依赖独立的“对象验证”过程。类似于

begin:
 rcu_read_lock();
 obj = lockless_lookup(key);
 if (obj) {
   if (!try_get_ref(obj)) // might fail for free objects
     rcu_read_unlock();
     goto begin;

   if (obj->key != key) { // not the object we expected
     put_ref(obj);
     rcu_read_unlock();
     goto begin;
   }
 }
rcu_read_unlock();

如果我们从没有常规锁定的情况下获得的地址,需要间接访问内核结构,这会很有用。我们可以锁定该结构以使其稳定,并检查它是否仍在该给定地址,前提是我们能够确保该内存没有被重用于其他类型的对象(这可能会破坏我们子系统的锁)。

在读取地址前调用 rcu_read_lock,然后在预期在该地址的结构内获取自旋锁后调用 rcu_read_unlock。

请注意,对象身份检查必须在获取引用后进行,因此用户必须确保正确的加载顺序。同样,在使用 SLAB_TYPESAFE_BY_RCU 分配的对象进行初始化时,新分配的对象必须在其引用计数初始化之前完全初始化,并且需要适当的存储顺序。refcount_{add|inc}_not_zero_acquire() 和 refcount_set_release() 的设计考虑了使用 SLAB_TYPESAFE_BY_RCU 分配的引用计数对象所需的正确屏障。

请注意,在不首先如上所述获取引用的情况下,无法获取使用 SLAB_TYPESAFE_BY_RCU 分配的结构中的锁。原因在于 SLAB_TYPESAFE_BY_RCU 页面在交给 slab 之前不会清零,这意味着任何锁都必须在每次 kmem_struct_alloc() 之后初始化。或者,让传递给 kmem_cache_create() 的 ctor 在页面分配时初始化锁,就像在 __i915_request_ctor()、sighand_ctor() 和 anon_vma_ctor() 中所做的那样。这样的 ctor 允许读取者在 rcu_read_lock() 保护下安全地获取那些 ctor 初始化的锁。

请注意,SLAB_TYPESAFE_BY_RCU 最初名为 SLAB_DESTROY_BY_RCU。

SLAB_ACCOUNT

SLAB_ACCOUNT

将分配计入 memcg。

描述

此缓存的所有对象分配都将进行 memcg 记账,无论 __GFP_ACCOUNT 是否传递给单个分配。

SLAB_RECLAIM_ACCOUNT

SLAB_RECLAIM_ACCOUNT

对象可回收。

描述

将此标志用于具有关联收缩器的缓存。因此,slab 页面将使用 __GFP_RECLAIMABLE 分配,这会影响按移动性分组页面,并计入 /proc/meminfo 中的 SReclaimable 计数器。

struct kmem_cache_args

kmem_cache_create() 的不常用参数

定义:

struct kmem_cache_args {
    unsigned int align;
    unsigned int useroffset;
    unsigned int usersize;
    unsigned int freeptr_offset;
    bool use_freeptr_offset;
    void (*ctor)(void *);
};

成员

align

对象所需的对齐方式。

0 表示未请求特定对齐。

useroffset

用户复制区域偏移。

0 是一个有效偏移,当 usersize0 时。

usersize

用户复制区域大小。

0 表示未指定用户复制区域。

freeptr_offset

SLAB_TYPESAFE_BY_RCU 缓存中自由指针的自定义偏移量

默认情况下,SLAB_TYPESAFE_BY_RCU 缓存会将自由指针放在对象之外。这可能会导致对象大小增加。需要避免这种情况的缓存创建者可以在其结构中指定一个自定义的自由指针偏移量,自由指针将放在那里。

请注意,将自由指针放在对象内部要求调用者确保不会使阻止对象回收所需的字段失效(详见 SLAB_TYPESAFE_BY_RCU)。

使用 0 作为 freeptr_offset 的值是有效的。如果指定了 freeptr_offset,则 use_freeptr_offset 必须设置为 true

请注意,由于 ctor 需要外部自由指针,目前不支持自定义自由指针的 ctor

use_freeptr_offset

是否使用 freeptr_offset

ctor

对象的构造函数。

在每个新分配的 slab 页中的对象上都会调用构造函数。缓存用户有责任在调用构造函数后以相同的状态释放对象,或者适当地处理新构造对象和重新分配对象之间的任何差异。

NULL 表示没有构造函数。

描述

结构的任何未初始化字段都被解释为未使用。例外是 freeptr_offset,其中 0 是一个有效值,因此 use_freeptr_offset 也必须设置为 true 才能将该字段解释为已使用。对于 useroffset0 也是有效的,但仅限于 usersize0 的情况。

当将 NULL args 传递给 kmem_cache_create() 时,它等同于所有字段都未使用。

struct kmem_cache *kmem_cache_create_usercopy(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, unsigned int useroffset, unsigned int usersize, void (*ctor)(void*))

创建一个带有适合复制到用户空间区域的 kmem 缓存。

参数

const char *name

用于在 /proc/slabinfo 中标识此缓存的字符串。

unsigned int size

在此缓存中创建的对象大小。

unsigned int align

对象所需的对齐方式。

slab_flags_t flags

SLAB 标志

unsigned int useroffset

用户复制区域偏移

unsigned int usersize

用户复制区域大小

void (*ctor)(void *)

对象的构造函数,或 NULL

描述

这是一个遗留封装器,新代码应使用 KMEM_CACHE_USERCOPY()(如果白名单单个字段就足够)或 kmem_cache_create() 并通过 args 参数传递必要参数(参见 struct kmem_cache_args

返回

成功时返回指向缓存的指针,失败时返回 NULL。

kmem_cache_create

kmem_cache_create (__name, __object_size, __args, ...)

创建一个 kmem 缓存。

参数

__name

用于在 /proc/slabinfo 中标识此缓存的字符串。

__object_size

在此缓存中创建的对象大小。

__args

可选参数,参见 struct kmem_cache_args。传递 NULL 意味着所有参数将使用默认值。

...

可变参数

描述

此函数目前作为宏实现,使用 _Generic() 调用新版本函数或遗留版本函数。

新版本有 4 个参数:kmem_cache_create(name, object_size, args, flags)

参见实现此功能的 __kmem_cache_create_args()

遗留版本有 5 个参数:kmem_cache_create(name, object_size, align, flags, ctor)

align 和 ctor 参数映射到 struct kmem_cache_args 的相应字段。

上下文

不能在中断中调用,但可以被中断。

返回

成功时返回指向缓存的指针,失败时返回 NULL。

size_t ksize(const void *objp)

报告关联对象的实际分配大小

参数

const void *objp

从先前 kmalloc() 族分配返回的指针。

描述

此函数不应用于写入超出最初请求的分配大小的区域。应使用 krealloc() 或在分配前使用 kmalloc_size_roundup() 将分配大小向上取整。如果此函数用于访问超出最初请求的分配大小的区域,UBSAN_BOUNDS 和/或 FORTIFY_SOURCE 可能会触发,因为它们只知道最初分配的大小(通过 __alloc_size 属性)。

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

分配一个对象

参数

struct kmem_cache *cachep

从中分配的缓存。

gfp_t flags

参见 kmalloc()

描述

从此缓存中分配一个对象。有关将 __GFP_ZERO 添加到 flags 的快捷方式,请参见 kmem_cache_zalloc()。

返回

指向新对象的指针,如果出错则为 NULL

bool kmem_cache_charge(void *objp, gfp_t gfpflags)

memcg 记账已分配的 slab 内存

参数

void *objp

要进行 memcg 记账的 slab 对象地址

gfp_t gfpflags

描述分配上下文

描述

kmem_cache_charge 允许将 slab 对象记账到当前 memcg,主要用于在分配时可能无法确定目标 memcg 的情况(即软中断上下文)

objp 应该是 slab 分配器函数(如 kmalloc(带 __GFP_ACCOUNT 标志)或 kmem_cache_alloc)返回的指针。memcg 记账行为可以通过 gfpflags 参数控制,这会影响必要内部元数据的分配方式。包含 __GFP_NOFAIL 表示请求超额记账而不是失败,但不适用于内部元数据分配。

在几种情况下,即使未进行记账,它也会返回 true:更具体地说

  1. 对于 !CONFIG_MEMCG 或 cgroup_disable=memory 系统。

  2. 已记账的 slab 对象。

  3. 对于来自 KMALLOC_NORMAL 缓存的 slab 对象——由不带 __GFP_ACCOUNT 的 kmalloc() 分配

  4. 分配内部元数据失败

返回

如果记账成功则为 true,否则为 false。

void *kmalloc(size_t size, gfp_t flags)

分配内核内存

参数

size_t size

所需的内存字节数。

gfp_t flags

描述分配上下文

描述

kmalloc 是在内核中为小于页面大小的对象分配内存的常用方法。

分配的内存对象地址至少对齐到 ARCH_KMALLOC_MINALIGN 字节。对于 size 为 2 的幂的字节数,对齐也保证至少达到该大小。对于其他大小,对齐保证至少达到 size 的最大 2 的幂因子。

flags 参数可以是 include/linux/gfp_types.h 中定义并于 Documentation/core-api/mm-api.rst 中描述的 GFP 标志之一

flags 的推荐用法在 Documentation/core-api/memory-allocation.rst 中描述

以下是关于最有用的 GFP 标志的简要概述

GFP_KERNEL

分配普通内核内存。可能会休眠。

GFP_NOWAIT

分配不会休眠。

GFP_ATOMIC

分配不会休眠。可能使用紧急池。

还可以通过“或”上以下一个或多个额外 flags 来设置不同的标志

__GFP_ZERO

返回前将分配的内存清零。另请参见 kzalloc()

__GFP_HIGH

此分配具有高优先级,可能使用紧急池。

__GFP_NOFAIL

表示此分配绝不允许失败(使用前请三思)。

__GFP_NORETRY

如果内存无法立即获得,则立即放弃。

__GFP_NOWARN

如果分配失败,不要发出任何警告。

__GFP_RETRY_MAYFAIL

非常努力地尝试使分配成功,但最终仍可能失败。

void *kmalloc_array(size_t n, size_t size, gfp_t flags)

为数组分配内存。

参数

size_t n

元素数量。

size_t size

元素大小。

gfp_t flags

要分配的内存类型(参见 kmalloc)。

void *krealloc_array(void *p, size_t new_n, size_t new_size, gfp_t flags)

重新分配数组内存。

参数

void *p

指向要重新分配的内存块的指针

size_t new_n

要分配的新元素数量

size_t new_size

数组单个成员的新大小

gfp_t flags

要分配的内存类型(参见 kmalloc)

描述

如果请求 __GFP_ZERO 逻辑,调用者必须确保从初始内存分配开始,每次后续对同一内存分配的此 API 调用都必须标记为 __GFP_ZERO。否则,此 API 可能无法完全遵守 __GFP_ZERO。

有关更多详细信息,请参见 krealloc_noprof()。

在任何情况下,指向对象的内存内容都会保留到新旧大小中的较小者。

kcalloc

kcalloc (n, size, flags)

为数组分配内存。内存设置为零。

参数

n

元素数量。

size

元素大小。

flags

要分配的内存类型(参见 kmalloc)。

void *kzalloc(size_t size, gfp_t flags)

分配内存。内存设置为零。

参数

size_t size

所需的内存字节数。

gfp_t flags

要分配的内存类型(参见 kmalloc)。

size_t kmalloc_size_roundup(size_t size)

报告给定大小的分配桶大小

参数

size_t size

要向上取整的字节数。

描述

此函数返回 kmalloc() 分配 size 字节后可用的字节数。例如,126 字节的请求将被向上取整到下一个大小的 kmalloc 桶,即 128 字节。(这严格适用于通用 kmalloc()-based 分配,不适用于预设大小的 kmem_cache_alloc()-based 分配。)

使用此函数可以提前 kmalloc() 完整的桶大小,而不是在分配后使用 ksize() 查询大小。

void kmem_cache_free(struct kmem_cache *s, void *x)

释放一个对象

参数

struct kmem_cache *s

分配来自的缓存。

void *x

先前分配的对象。

描述

释放先前从此缓存分配的对象。

void kfree(const void *object)

释放先前分配的内存

参数

const void *object

kmalloc()kmem_cache_alloc() 返回的指针

描述

如果 object 为 NULL,则不执行任何操作。

void *kvfree(const void *addr)

释放内存。

参数

const void *addr

指向已分配内存的指针。

描述

kvfree 释放由 vmalloc()、kmalloc() 或 kvmalloc() 分配的内存。如果您确定知道使用哪个函数,使用 kfree()vfree() 会稍微更高效。

上下文

要么是可抢占的任务上下文,要么是非 NMI 中断。

void kvfree_sensitive(const void *addr, size_t len)

释放包含敏感信息的数据对象。

参数

const void *addr

要释放的数据对象地址。

size_t len

数据对象的长度。

描述

使用特殊的 memzero_explicit() 函数清除包含敏感数据的 kvmalloc'ed 对象的内容,以确保编译器不会优化掉数据清除操作。

struct kmem_cache *__kmem_cache_create_args(const char *name, unsigned int object_size, struct kmem_cache_args *args, slab_flags_t flags)

创建一个 kmem 缓存。

参数

const char *name

用于在 /proc/slabinfo 中标识此缓存的字符串。

unsigned int object_size

在此缓存中创建的对象大小。

struct kmem_cache_args *args

缓存创建的附加参数(参见 struct kmem_cache_args)。

slab_flags_t flags

参见各个标志的描述。常用标志列于以下描述中。

描述

不要直接调用,使用参数相同的 kmem_cache_create() 封装器。

常用的 flags

SLAB_ACCOUNT - 将分配计入 memcg。

SLAB_HWCACHE_ALIGN - 将对象对齐到缓存行边界。

SLAB_RECLAIM_ACCOUNT - 对象是可回收的。

SLAB_TYPESAFE_BY_RCU - Slab 页(而非单个对象)的释放延迟一个宽限期 - 使用前请阅读完整描述。

上下文

不能在中断中调用,但可以被中断。

返回

成功时返回指向缓存的指针,失败时返回 NULL。

kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, unsigned int useroffset, unsigned int usersize, void (*ctor)(void*))

创建一组通过 kmem_buckets_alloc() 处理动态大小分配的缓存

参数

const char *name

一个前缀字符串,用于在 /proc/slabinfo 中标识此缓存。单个缓存将以其大小作为后缀。

slab_flags_t flags

SLAB 标志(详见 kmem_cache_create())。

unsigned int useroffset

分配中可以复制到/从用户空间的起始偏移量。

unsigned int usersize

useroffset 开始,可以复制到/从用户空间的字节数。

void (*ctor)(void *)

对象的构造函数,在新分配时运行。

描述

不能在中断中调用,但可以被中断。

返回

成功时返回指向缓存的指针,失败时返回 NULL。当 CONFIG_SLAB_BUCKETS 未启用时,返回 ZERO_SIZE_PTR,并且后续对 kmem_buckets_alloc() 的调用将回退到 kmalloc()。(即,调用者只需在失败时检查 NULL。)

int kmem_cache_shrink(struct kmem_cache *cachep)

收缩一个缓存。

参数

struct kmem_cache *cachep

要收缩的缓存。

描述

为缓存释放尽可能多的 slab。为了帮助调试,退出状态为零表示所有 slab 都已释放。

返回

如果所有 slab 都已释放,则为 0,否则为非零

bool kmem_dump_obj(void *object)

打印可用的 slab 来源信息

参数

void *object

要查找来源信息的 slab 对象。

描述

此函数使用 pr_cont(),因此调用者应已打印出适当的序言。来源信息取决于对象类型和启用的调试程度。对于 slab-cache 对象,会打印它是 slab 对象的事实,如果可用,还会打印 slab 名称、返回地址以及该对象的分配和上次释放路径的堆栈跟踪。

返回

如果指针指向从 kmalloc()kmem_cache_alloc() 未释放的对象,则为 true;如果指针指向已释放的对象,则为 truefalse;否则为 false

void kfree_sensitive(const void *p)

在释放内存前清除内存中的敏感信息

参数

const void *p

要释放内存的对象

描述

指针 p 指向的对象的内存将在释放前清零。如果 pNULLkfree_sensitive() 不执行任何操作。

注意

此函数会清零整个已分配缓冲区,其大小可能远大于传递给 kmalloc() 的请求缓冲区大小。因此,在性能敏感的代码中使用此函数时请务必小心。

void kvfree_rcu_barrier(void)

等待所有进行中的 kvfree_rcu() 完成。

参数

void

无参数

描述

请注意,kvfree_rcu() 调用的单个参数有一个慢速路径,它会触发 synchronize_rcu(),然后释放指针。这在函数返回之前完成。因此,对于任何导致 kfree() 到将在模块退出时销毁的缓存的单参数调用,开发人员有责任确保所有此类调用在调用 kmem_cache_destroy() 之前已返回。

void kfree_const(const void *x)

有条件地释放内存

参数

const void *x

指向内存的指针

描述

仅当 x 不在 .rodata 段时,函数才调用 kfree。

虚拟连续映射

void vm_unmap_aliases(void)

解除 vmap 层中未决的惰性别名映射

参数

void

无参数

描述

vmap/vmalloc 层会惰性刷新内核虚拟映射,主要是为了分摊 TLB 刷新开销。这意味着您现在拥有的任何页面,在以前的生命周期中,可能已经被 vmap 层映射到内核虚拟地址空间,因此可能有一些 CPU 仍然引用该页面(除了常规的 1:1 内核映射)。

vm_unmap_aliases 刷新所有此类惰性映射。它返回后,我们可以确定我们控制的任何页面都不会有来自 vmap 层的任何别名。

void vm_unmap_ram(const void *mem, unsigned int count)

解除由 vm_map_ram 设置的线性内核地址空间映射

参数

const void *mem

vm_map_ram 返回的指针

unsigned int count

传递给 vm_map_ram 调用的计数(不能解除部分映射)

void *vm_map_ram(struct page **pages, unsigned int count, int node)

将页面线性映射到内核虚拟地址(vmalloc 空间)

参数

struct page **pages

指向要映射页面的指针数组

unsigned int count

要映射的页数

int node

优先在此节点上分配数据结构

描述

如果您使用此函数映射少于 VMAP_MAX_ALLOC 页,它可能比 vmap 更快,所以它很好。但是,如果您将长生命周期和短生命周期对象与 vm_map_ram() 混合使用,它可能会因碎片化而消耗大量地址空间(尤其是在 32 位机器上)。最终您可能会看到失败。请为短生命周期对象使用此函数。

返回

映射区域的地址,如果失败则为 NULL

void vfree(const void *addr)

释放 vmalloc() 分配的内存

参数

const void *addr

内存基地址

描述

释放从 vmalloc() 系列 API 获取的、从 addr 开始的虚拟连续内存区域。这通常也会释放虚拟分配底层的物理内存,但该内存是引用计数的,因此在最后一个用户离开之前不会被释放。

如果 addr 为 NULL,则不执行任何操作。

上下文

如果调用并非来自中断上下文,则可能会休眠。不能在 NMI 上下文调用(严格来说,如果我们有 CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG,它可能可以,但使 vfree() 的调用约定依赖于架构是一个非常糟糕的主意)。

void vunmap(const void *addr)

释放由 vmap() 获取的虚拟映射

参数

const void *addr

内存基地址

描述

释放从页面数组创建的、由 vmap() 传入的、从 addr 开始的虚拟连续内存区域。

不能在中断上下文调用。

void *vmap(struct page **pages, unsigned int count, unsigned long flags, pgprot_t prot)

将页面数组映射到虚拟连续空间

参数

struct page **pages

页面指针数组

unsigned int count

要映射的页数

unsigned long flags

vm_area->flags

pgprot_t prot

映射的页面保护

描述

pages 中的 count 个页面映射到连续的内核虚拟空间。如果 flags 包含 VM_MAP_PUT_PAGES,则页面数组本身(必须是 kmalloc 或 vmalloc 内存)的所有权以及其中每个页面的一个引用从调用者转移到 vmap(),并在对返回值调用 vfree() 时被释放/删除。

返回

区域的地址,如果失败则为 NULL

void *vmap_pfn(unsigned long *pfns, unsigned int count, pgprot_t prot)

将 PFN 数组映射到虚拟连续空间

参数

unsigned long *pfns

PFN 数组

unsigned int count

要映射的页数

pgprot_t prot

映射的页面保护

描述

pfns 中的 count 个 PFN 映射到连续的内核虚拟空间,并返回映射的起始地址。

int remap_vmalloc_range(struct vm_area_struct *vma, void *addr, unsigned long pgoff)

将vmalloc页映射到用户空间

参数

struct vm_area_struct *vma

要覆盖的vma(映射vma的整个范围)

void *addr

vmalloc内存

unsigned long pgoff

在映射第一个页之前,`addr` 中的页数偏移

返回

成功返回0,失败返回-Exxx

描述

此函数检查 `addr` 是否是一个有效的 vmalloc 区域,并且它是否足够大以覆盖 vma。如果条件不满足,将返回失败。

类似于 remap_pfn_range()(参见 mm/memory.c)

文件映射和页缓存

文件映射

int filemap_fdatawrite_wbc(struct address_space *mapping, struct writeback_control *wbc)

开始回写指定范围内映射的脏页

参数

struct address_space *mapping

要回写的 address space 结构

struct writeback_control *wbc

控制回写的 writeback_control

描述

使用提供的 `wbc` 调用 `writepages` 对 `mapping` 进行回写。

返回

成功返回 0,否则返回负错误码。

int filemap_fdatawrite_range_kick(struct address_space *mapping, loff_t start, loff_t end)

开始范围回写

参数

struct address_space *mapping

目标地址空间

loff_t start

开始回写的索引

loff_t end

回写的最后一个(包含)索引

描述

这是一个非完整性回写辅助函数,用于开始回写指定范围内的 folio。

返回

成功返回 0,否则返回负错误码。

int filemap_flush(struct address_space *mapping)

主要是一个非阻塞刷新

参数

struct address_space *mapping

目标地址空间

描述

这主要是一个非阻塞刷新。不适用于数据完整性目的——I/O 可能不会针对所有脏页启动。

返回

成功返回 0,否则返回负错误码。

bool filemap_range_has_page(struct address_space *mapping, loff_t start_byte, loff_t end_byte)

检查指定范围内是否存在页。

参数

struct address_space *mapping

要检查的地址空间

loff_t start_byte

范围起始的字节偏移量

loff_t end_byte

范围结束的字节偏移量(包含)

描述

在提供的范围内查找至少一个页,通常用于检查在此范围内的直接写入是否会触发回写。

返回

如果指定范围内至少存在一个页,返回 true,否则返回 false

int filemap_fdatawait_range(struct address_space *mapping, loff_t start_byte, loff_t end_byte)

等待回写完成

参数

struct address_space *mapping

要等待的地址空间结构

loff_t start_byte

范围起始的字节偏移量

loff_t end_byte

范围结束的字节偏移量(包含)

描述

遍历给定地址空间中指定范围内正在回写的页列表,并等待所有这些页完成。检查地址空间的错误状态并返回它。

由于此函数会清除地址空间的错误状态,调用者有责任检查返回值并处理和/或报告错误。

返回

地址空间的错误状态。

int filemap_fdatawait_range_keep_errors(struct address_space *mapping, loff_t start_byte, loff_t end_byte)

等待回写完成

参数

struct address_space *mapping

要等待的地址空间结构

loff_t start_byte

范围起始的字节偏移量

loff_t end_byte

范围结束的字节偏移量(包含)

描述

遍历给定地址空间中指定范围内正在回写的页列表,并等待所有这些页完成。与 filemap_fdatawait_range() 不同,此函数不会清除地址空间的错误状态。

如果调用者不自行处理错误,请使用此函数。预期调用场景是系统范围/文件系统范围的数据刷新器:例如 `sync(2)`、`fsfreeze(8)`。

int file_fdatawait_range(struct file *file, loff_t start_byte, loff_t end_byte)

等待回写完成

参数

struct file *file

指向要等待的地址空间结构的文件

loff_t start_byte

范围起始的字节偏移量

loff_t end_byte

范围结束的字节偏移量(包含)

描述

遍历 `file` 所指向的地址空间中指定范围内正在回写的页列表,并等待所有这些页完成。检查地址空间的错误状态与 `file->f_wb_err` 光标,并返回它。

由于此函数会使文件的错误状态向前推进,调用者有责任检查返回值并处理和/或报告错误。

返回

地址空间的错误状态与 `file->f_wb_err` 光标。

int filemap_fdatawait_keep_errors(struct address_space *mapping)

等待回写而不清除错误

参数

struct address_space *mapping

要等待的地址空间结构

描述

遍历给定地址空间中正在回写的页列表,并等待所有这些页完成。与 `filemap_fdatawait()` 不同,此函数不会清除地址空间的错误状态。

如果调用者不自行处理错误,请使用此函数。预期调用场景是系统范围/文件系统范围的数据刷新器:例如 `sync(2)`、`fsfreeze(8)`。

返回

地址空间的错误状态。

int filemap_write_and_wait_range(struct address_space *mapping, loff_t lstart, loff_t lend)

回写并等待文件范围

参数

struct address_space *mapping

页面的 address_space

loff_t lstart

范围起始的字节偏移量

loff_t lend

范围结束的字节偏移量(包含)

描述

回写并等待文件偏移量 `lstart` 到 `lend`(包含)。

请注意,**lend** 是包含的(描述要写入的最后一个字节),因此此函数可以用于写入到文件末尾(end = -1)。

返回

地址空间的错误状态。

int file_check_and_advance_wb_err(struct file *file)

报告之前发生的回写错误(如果有),并将 `wb_err` 前进到当前错误

参数

struct file *file

正在报告错误的 struct file

描述

当用户空间调用 `fsync`(或 `nfsd` 等效操作)时,我们希望报告自上次 `fsync` 以来(或自文件打开以来,如果没有发生过)发生的任何回写错误。

从 `mapping` 中获取 `wb_err`。如果它与我们在文件中拥有的匹配,则快速返回 `0`。文件已全部同步。

如果不匹配,则获取映射值,设置其“seen”标志并尝试将其交换到位。如果成功,或另一个任务以新值抢先我们,则更新 `f_wb_err` 并返回错误部分。此时的错误必须通过适当的渠道(例如 `fsync` 或 NFS `COMMIT` 操作等)报告。

虽然我们使用原子操作处理 `mapping->wb_err`,但 `f_wb_err` 值受 `f_lock` 保护,因为我们必须确保它反映为此文件描述符交换的最新值。

返回

成功返回 0,否则返回负错误码。

int file_write_and_wait_range(struct file *file, loff_t lstart, loff_t lend)

回写并等待文件范围

参数

struct file *file

指向包含页面的 address_space 的文件

loff_t lstart

范围起始的字节偏移量

loff_t lend

范围结束的字节偏移量(包含)

描述

回写并等待文件偏移量 `lstart` 到 `lend`(包含)。

请注意,**lend** 是包含的(描述要写入的最后一个字节),因此此函数可以用于写入到文件末尾(end = -1)。

在回写并等待数据后,我们检查并将 `f_wb_err` 光标推进到最新值,并返回在那里检测到的任何错误。

返回

成功返回 0,否则返回负错误码。

void replace_page_cache_folio(struct folio *old, struct folio *new)

用新的 folio 替换页缓存中的 folio

参数

struct folio *old

要被替换的 folio

struct folio *new

替换用的 folio

描述

此函数用一个新的 folio 替换页缓存中的现有 folio。成功时,它会为新 folio 获取页缓存引用,并为旧 folio 释放引用。旧 folio 和新 folio 都必须被锁定。此函数不会将新 folio 添加到 LRU,调用者必须自行处理。

移除和添加是原子操作。此函数不会失败。

void folio_unlock(struct folio *folio)

解锁一个已锁定的 folio。

参数

struct folio *folio

该 folio。

描述

解锁 folio 并唤醒任何在页锁上睡眠的线程。

上下文

可以在中断或进程上下文中调用。不能在 NMI 上下文中调用。

void folio_end_read(struct folio *folio, bool success)

结束对 folio 的读取。

参数

struct folio *folio

该 folio。

bool success

如果所有读取都成功完成,则为真。

描述

当针对某个 folio 的所有读取都完成后,文件系统应调用此函数以告知页缓存不再有未完成的读取。这将解锁 folio 并唤醒任何在锁上睡眠的线程。如果所有读取都成功,folio 也将被标记为最新。

上下文

可以在中断或进程上下文中调用。不能在 NMI 上下文中调用。

void folio_end_private_2(struct folio *folio)

清除 PG_private_2 并唤醒任何等待者。

参数

struct folio *folio

该 folio。

描述

清除 folio 上的 `PG_private_2` 位,并唤醒任何等待它的睡眠者。为 `PG_private_2` 设置而持有的 folio 引用将被释放。

例如,当网络文件系统 (netfs) 的 folio 正在写入本地磁盘缓存时,此功能可用于序列化对同一 folio 的缓存写入。

void folio_wait_private_2(struct folio *folio)

等待 folio 上的 PG_private_2 被清除。

参数

struct folio *folio

要等待的 folio。

描述

等待 folio 上的 PG_private_2 被清除。

int folio_wait_private_2_killable(struct folio *folio)

等待 folio 上的 PG_private_2 被清除。

参数

struct folio *folio

要等待的 folio。

描述

等待 folio 上的 `PG_private_2` 被清除,或者直到调用任务收到致命信号。

返回

  • 成功返回0。

  • 如果遇到致命信号,返回 -EINTR。

void folio_end_writeback(struct folio *folio)

结束对 folio 的回写。

参数

struct folio *folio

该 folio。

描述

该 folio 必须实际处于回写状态。

上下文

可以在进程或中断上下文中调用。

void __folio_lock(struct folio *folio)

在 folio 上获取锁,假设我们需要睡眠才能获取它。

参数

struct folio *folio

要锁定的 folio

pgoff_t page_cache_next_miss(struct address_space *mapping, pgoff_t index, unsigned long max_scan)

在页缓存中查找下一个空隙。

参数

struct address_space *mapping

映射。

pgoff_t index

索引。

unsigned long max_scan

要搜索的最大范围。

描述

在范围 `[index, min(index + max_scan - 1, ULONG_MAX)]` 中搜索索引最低的空隙。

此函数可以在 `rcu_read_lock` 下调用。但是,这不会原子地搜索缓存的单个时间点快照。例如,如果在索引 `5` 处创建了一个空隙,然后又在索引 `10` 处创建了一个空隙,那么如果在 `rcu_read_lock` 下调用覆盖这两个索引的 `page_cache_next_miss`,它可能会返回 `10`。

返回

如果找到空隙,返回该空隙的索引;否则返回超出指定范围的索引(在这种情况下,`return - index >= max_scan` 将为真)。在极少数的索引环绕情况下,将返回 `0`。

pgoff_t page_cache_prev_miss(struct address_space *mapping, pgoff_t index, unsigned long max_scan)

在页缓存中查找前一个空隙。

参数

struct address_space *mapping

映射。

pgoff_t index

索引。

unsigned long max_scan

要搜索的最大范围。

描述

在范围 `[max(index - max_scan + 1, 0), index]` 中搜索索引最高的空隙。

此函数可以在 `rcu_read_lock` 下调用。但是,这不会原子地搜索缓存的单个时间点快照。例如,如果在索引 `10` 处创建了一个空隙,然后又在索引 `5` 处创建了一个空隙,那么如果在 `rcu_read_lock` 下调用覆盖这两个索引的 page_cache_prev_miss(),它可能会返回 `5`。

返回

如果找到空隙,返回该空隙的索引;否则返回超出指定范围的索引(在这种情况下,`index - return >= max_scan` 将为真)。在极少数的环绕情况下,将返回 `ULONG_MAX`。

struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index, fgf_t fgp_flags, gfp_t gfp)

查找并获取对 folio 的引用。

参数

struct address_space *mapping

要搜索的 address_space。

pgoff_t index

页索引。

fgf_t fgp_flags

FGP 标志修改 folio 的返回方式。

gfp_t gfp

如果指定了 FGP_CREAT,则使用的内存分配标志。

描述

查找 **mapping** & **index** 处的页缓存条目。

如果指定了 FGP_LOCKFGP_CREAT,则即使为 FGP_CREAT 指定的 GFP 标志是原子的,函数也可能睡眠。

如果此函数返回一个 folio,它将以增加的引用计数返回。

返回

找到的 folio,否则返回 ERR_PTR()

unsigned filemap_get_folios(struct address_space *mapping, pgoff_t *start, pgoff_t end, struct folio_batch *fbatch)

获取一批 folio

参数

struct address_space *mapping

要搜索的 address_space

pgoff_t *start

起始页索引

pgoff_t end

最终页索引(包含)

struct folio_batch *fbatch

要填充的批次。

描述

在 `mapping` 中搜索并返回从索引 **start** 开始到索引 **end**(包含)的一批 folio。这些 folio 将以增加的引用计数返回到 **fbatch** 中。

返回

找到的 folio 数量。我们还会更新 **start** 以索引下一次遍历的 folio。

unsigned filemap_get_folios_contig(struct address_space *mapping, pgoff_t *start, pgoff_t end, struct folio_batch *fbatch)

获取一批连续的 folio

参数

struct address_space *mapping

要搜索的 address_space

pgoff_t *start

起始页索引

pgoff_t end

最终页索引(包含)

struct folio_batch *fbatch

要填充的批次

描述

filemap_get_folios_contig() 的工作方式与 filemap_get_folios() 完全相同,不同之处在于返回的 folio 保证是连续的。如果批次被填满,这可能不会返回所有连续的 folio。

返回

找到的 folio 数量。同时更新 **start**,使其定位为遍历下一个 folio。

unsigned filemap_get_folios_tag(struct address_space *mapping, pgoff_t *start, pgoff_t end, xa_mark_t tag, struct folio_batch *fbatch)

获取一批与 **tag** 匹配的 folio

参数

struct address_space *mapping

要搜索的 address_space

pgoff_t *start

起始页索引

pgoff_t end

最终页索引(包含)

xa_mark_t tag

标签索引

struct folio_batch *fbatch

要填充的批次

描述

第一个 folio 可能在 **start** 之前开始;如果它确实如此,它将包含 **start**。最后一个 folio 可能超出 **end**;如果它确实如此,它将包含 **end**。folio 具有升序索引。如果页缓存中没有 folio 的索引,则 folio 之间可能存在空隙。如果在此函数运行时 folio 被添加到或从页缓存中移除,则此调用可能找到也可能找不到它们。仅返回带有 **tag** 标签的 folio。

返回

找到的 folio 数量。同时更新 **start** 以索引下一次遍历的 folio。

ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter, ssize_t already_read)

从页缓存中读取数据。

参数

struct kiocb *iocb

要读取的 iocb。

struct iov_iter *iter

数据目的地。

ssize_t already_read

调用者已读取的字节数。

描述

从页缓存复制数据。如果数据当前不存在,则使用预读和 `read_folio` 地址空间操作来获取它。

返回

复制的总字节数,包括调用者已读取的字节。如果复制任何字节之前发生错误,则返回负错误码。

ssize_t generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)

通用文件系统读取例程

参数

struct kiocb *iocb

内核 I/O 控制块

struct iov_iter *iter

读取数据目的地

描述

这是所有可以直接使用页缓存的文件系统的“read_iter()”例程。

`iocb->ki_flags` 中的 `IOCB_NOWAIT` 标志表示当无法在不等待 I/O 请求完成的情况下读取数据时,应返回 `-EAGAIN`;它不会阻止预读。

`iocb->ki_flags` 中的 `IOCB_NOIO` 标志表示不应为读取或预读发出新的 I/O 请求。当无法读取数据时,应返回 `-EAGAIN`。当会触发预读时,应返回部分(可能为空)读取。

返回

  • 复制的字节数,即使是部分读取

  • 如果没有读取任何数据,则为负错误码(或如果 `IOCB_NOIO` 则为 `0`)

ssize_t filemap_splice_read(struct file *in, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags)

将文件页缓存中的数据拼接进管道

参数

struct file *in

要读取的文件

loff_t *ppos

指向要读取的文件位置的指针

struct pipe_inode_info *pipe

要拼接到的管道

size_t len

要拼接的量

unsigned int flags

SPLICE_F_* 标志

描述

此函数从文件的页缓存中获取 folio 并将其拼接进管道。必要时将调用预读以填充更多 folio。此函数也可用于块设备。

返回

成功时,将返回读取的字节数,并酌情更新 **\*ppos**;如果没有更多数据可读,将返回 0;如果管道没有空间,将返回 `-EAGAIN`;如果出错,将返回其他负错误码。如果管道空间不足、我们到达数据末尾或遇到空洞,可能会发生短读。

vm_fault_t filemap_fault(struct vm_fault *vmf)

读入文件数据以进行页错误处理

参数

struct vm_fault *vmf

包含错误详细信息的 struct vm_fault

描述

filemap_fault() 通过映射内存区域的 vma 操作向量调用,以在页错误期间读入文件数据。

goto 语句有些难看,但这简化了将其放入页缓存的正常情况,并合理地处理了特殊情况,而没有大量重复代码。

进入时必须持有 `vma->vm_mm->mmap_lock`。

如果我们的返回值设置了 `VM_FAULT_RETRY`,那是因为 `mmap_lock` 可能会在进行 I/O 之前或被 `lock_folio_maybe_drop_mmap()` 释放。

如果我们的返回值没有设置 `VM_FAULT_RETRY`,则 `mmap_lock` 未被释放。

我们从不返回 `VM_FAULT_RETRY` 并设置 `VM_FAULT_ERROR` 中的位。

返回

VM_FAULT_ 代码的按位或。

struct folio *read_cache_folio(struct address_space *mapping, pgoff_t index, filler_t filler, struct file *file)

读入页缓存,如果需要则填充。

参数

struct address_space *mapping

要读取的地址空间。

pgoff_t index

要读取的索引。

filler_t filler

执行读取的函数,如果使用 `aops->read_folio()` 则为 `NULL`。

struct file *file

传递给填充函数,如果不需要可以为 `NULL`。

描述

将一页读入页缓存。如果成功,返回的 folio 将包含 **index**,但它可能不是 folio 的第一页。

如果填充函数返回错误,该错误将返回给调用者。

上下文

可能会睡眠。期望 `mapping->invalidate_lock` 被持有。

返回

成功时返回一个最新的 folio,失败时返回 ERR_PTR()

struct folio *mapping_read_folio_gfp(struct address_space *mapping, pgoff_t index, gfp_t gfp)

读入页缓存,使用指定的分配标志。

参数

struct address_space *mapping

folio 的 address_space。

pgoff_t index

分配的 folio 将包含的索引。

gfp_t gfp

如果进行分配,使用的页分配器标志。

描述

这与“read_cache_folio(mapping, index, NULL, NULL)”相同,但任何新的内存分配都使用指定的分配标志。

此函数最可能出现的错误是 `EIO`,但也可能出现 `ENOMEM` 和 `EINTR`。如果 `->read_folio` 返回其他错误,该错误将返回给调用者。

函数期望 `mapping->invalidate_lock` 已被持有。

返回

成功时返回最新的 folio,失败时返回 ERR_PTR()

struct page *read_cache_page_gfp(struct address_space *mapping, pgoff_t index, gfp_t gfp)

读入页缓存,使用指定的页分配标志。

参数

struct address_space *mapping

页面的地址空间

pgoff_t index

页索引

gfp_t gfp

如果进行分配,使用的页分配器标志

描述

这与“read_mapping_page(mapping, index, NULL)”相同,但任何新的页分配都使用指定的分配标志。

如果页面未更新,则返回 `-EIO`。

函数期望 `mapping->invalidate_lock` 已被持有。

返回

成功时返回最新的页面,失败时返回 ERR_PTR()

ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)

向文件写入数据

参数

struct kiocb *iocb

I/O 状态结构(文件、偏移量等)

struct iov_iter *from

包含要写入数据的 iov_iter

描述

此函数执行将数据实际写入文件所需的所有工作。它执行所有基本检查,从文件中移除 SUID,更新修改时间,并根据我们是执行直接 I/O 还是标准缓冲写入来调用适当的子例程。

它期望 `i_rwsem` 被获取,除非我们处理块设备或类似不需要任何锁定的对象。

此函数**不**负责在 `O_SYNC` 写入的情况下同步数据。调用者必须处理它。这主要是因为我们希望避免在 `i_rwsem` 下进行同步。

返回

  • 写入的字节数,即使是截断写入

  • 如果完全没有写入数据,则为负错误码

ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)

向文件写入数据

参数

struct kiocb *iocb

I/O 状态结构

struct iov_iter *from

包含要写入数据的 iov_iter

描述

这是 __generic_file_write_iter() 的包装器,供大多数文件系统使用。它处理 `O_SYNC` 文件情况下的文件同步,并根据需要获取 `i_rwsem`。

返回

  • 如果完全没有写入数据,或者对于同步写入,vfs_fsync_range() 失败,则为负错误码

  • 写入的字节数,即使是截断写入

bool filemap_release_folio(struct folio *folio, gfp_t gfp)

释放 folio 上的文件系统特定元数据。

参数

struct folio *folio

内核试图释放的 folio。

gfp_t gfp

内存分配标志(和 I/O 模式)。

描述

`address_space` 试图释放附加到 folio(大概在 `folio->private`)的任何数据。

如果页面上设置了 `private_2` 标志,指示该 folio 具有其他相关元数据,也将调用此函数。

**gfp** 参数指定是否可以执行 I/O 来释放此页(`__GFP_IO`),以及调用是否可能阻塞(`__GFP_RECLAIM` & `__GFP_FS`)。

返回

如果释放成功,则为 true,否则为 false

int filemap_invalidate_inode(struct inode *inode, bool flush, loff_t start, loff_t end)

使 inode 页缓存的某个范围无效/强制回写

参数

struct inode *inode

要刷新的 inode

bool flush

设置为回写而非简单地使其无效。

loff_t start

范围内的第一个字节。

loff_t end

范围内的最后一个字节(包含),或 `LLONG_MAX` 表示从 `start` 开始的所有内容。

描述

使 inode 上对指定范围有贡献的所有 folio 无效,可能首先将它们回写。在执行操作期间,`invalidate` 锁被持有以防止新 folio 的安装。

预读

预读用于在应用程序明确请求之前将内容读入页缓存。预读仅尝试读取尚未在页缓存中的 folio。如果 folio 存在但不是最新状态,预读将不会尝试读取它。在这种情况下,将请求简单的 `->read_folio()`。

当应用程序读取请求(无论是系统调用还是页错误)发现请求的 folio 不在页缓存中,或者它在页缓存中且设置了预读标志时,会触发预读。此标志表示该 folio 是作为先前预读请求的一部分读取的,现在已被访问,是进行下一次预读的时候了。

每个预读请求都部分是同步读取,部分是异步预读。这反映在 struct file_ra_state 中,其中 `->size` 是总页数,`->async_size` 是异步部分的页数。预读标志将设置在此异步部分中的第一个 folio 上,以触发后续预读。一旦建立了一系列顺序读取,就不需要同步组件,所有预读请求都将完全异步。

当任一触发器导致预读时,需要确定三个数字:要读取区域的起始,区域的大小,以及异步尾部的大小。

区域的起始只是访问地址处或之后的第一个页地址,该地址当前未在页缓存中填充。这通过在页缓存中的简单搜索找到。

异步尾部的大小通过从确定的请求大小中减去明确请求的大小来确定,除非这会小于零——在这种情况下使用零。注意:当区域的起始不是访问页时,此计算是错误的。而且此计算未被一致使用。

区域的大小通常根据加载前一页的先前预读的大小来确定。这可以从简单顺序读取的 struct file_ra_state 中发现,或者在多个顺序读取交错时检查页缓存的状态。具体来说:如果预读是由预读标志触发的,则假定先前预读的大小是从触发页到新预读开始的页数。在这些情况下,先前预读的大小会按比例缩放,通常会翻倍,用于新的预读,但具体细节请参阅 `get_next_ra_size()`。

如果无法确定前一次读取的大小,则使用页缓存中前置页的数量来估计前一次读取的大小。这个估计很容易被偶然相邻的随机读取误导,因此除非它大于当前请求,否则它会被忽略,并且它不会按比例放大,除非它位于文件开头。

通常,预读在文件开头会加速,因为从那里读取通常是顺序的。在各种特殊情况下,预读大小还有其他微小的调整,最好通过阅读代码来发现这些调整。

上述基于先前预读大小的计算确定了预读的大小,在此基础上可以添加任何请求的读取大小。

预读请求通过 `->readahead()` 地址空间操作发送到文件系统,其中 mpage_readahead() 是一个典型的实现。`->readahead()` 通常应启动所有 folio 的读取,但可能无法读取任何或所有 folio 而不导致 I/O 错误。页缓存读取代码将对 `->readahead()` 未读取的任何 folio 发出 `->read_folio()` 请求,并且只有来自此请求的错误才是最终的。

`->readahead()` 通常会重复调用 readahead_folio() 来获取为预读准备的每个 folio。它可能因以下原因无法读取 folio:

  • 未充分多次调用 readahead_folio(),实际上忽略了一些 folio,这在存储路径拥堵时可能适用。

  • 未能实际提交给定 folio 的读取请求,可能是由于资源不足,或者

  • 在请求的后续处理过程中出现错误。

在后两种情况下,文件系统应解锁 folio 以指示读取尝试失败。在第一种情况下,folio 将由 VFS 解锁。

请求最终 `async_size` 中未包含的那些 folio 应被视为重要,`->readahead()` 不应因拥塞或临时资源不可用而使它们失败,而应等待必要的资源(例如内存或索引信息)变得可用。最终 `async_size` 中的 folio 可能被视为不那么紧急,并且未能读取它们更容易接受。在这种情况下,最好使用 `filemap_remove_folio()` 将 folio 从页缓存中移除,这对于未通过 readahead_folio() 获取的 folio 会自动完成。这将允许后续的同步预读请求再次尝试它们。如果它们留在页缓存中,那么它们将使用 `->read_folio()` 单独读取,这可能效率较低。

void page_cache_ra_unbounded(struct readahead_control *ractl, unsigned long nr_to_read, unsigned longlookahead_size)

开始无边界预读。

参数

struct readahead_control *ractl

预读控制。

unsigned long nr_to_read

要读取的页数。

unsigned long lookahead_size

下一次预读的起始位置。

描述

此函数供文件系统在希望开始超出文件声明的 `i_size` 的预读时调用。这几乎肯定不是您想要调用的函数。请改用 page_cache_async_readahead()page_cache_sync_readahead()

上下文

文件由调用者引用。调用者可能持有互斥锁。可能会睡眠,但不会重入文件系统以回收内存。

void readahead_expand(struct readahead_control *ractl, loff_t new_start, size_t new_len)

扩展预读请求

参数

struct readahead_control *ractl

要扩展的请求

loff_t new_start

修订后的起始位置

size_t new_len

修订后的请求大小

描述

尝试将预读请求从当前大小向外扩展到指定大小,通过在当前窗口之前和之后插入锁定的页面来增加大小到新窗口。这可能涉及插入 THP,在这种情况下,窗口可能会扩展超出请求的范围。

如果算法遇到页缓存中已有的冲突页面,它将停止并保留比请求更小的扩展。

调用者必须通过检查修订后的 **ractl** 对象是否与请求的扩展不同来检查这一点。

回写

int balance_dirty_pages_ratelimited_flags(struct address_space *mapping, unsigned int flags)

平衡脏内存状态。

参数

struct address_space *mapping

被标记为脏的 address_space。

unsigned int flags

BDP 标志。

描述

正在将内存标记为脏的进程应为每个新标记为脏的页调用此函数一次。该函数将定期检查系统的脏状态,并在需要时启动回写。

有关详细信息,请参阅 balance_dirty_pages_ratelimited()

返回

如果 flags 包含 BDP_ASYNC,它可能返回 -EAGAIN 以指示内存失衡,调用者必须等待 I/O 完成。否则,它将返回 0,表示内存已平衡,或者它能够休眠直到脏内存量恢复平衡。

void balance_dirty_pages_ratelimited(struct address_space *mapping)

平衡脏内存状态。

参数

struct address_space *mapping

被标记为脏的 address_space。

描述

正在将内存标记为脏的进程应为每个新标记为脏的页调用此函数一次。该函数将定期检查系统的脏状态,并在需要时启动回写。

一旦我们超过了脏内存限制,我们会大幅降低速率限制,以防止单个进程各自超出限制(ratelimit_pages)。

void tag_pages_for_writeback(struct address_space *mapping, pgoff_t start, pgoff_t end)

标记要由回写机制写入的页面

参数

struct address_space *mapping

要回写的 address space 结构

pgoff_t start

起始页索引

pgoff_t end

结束页索引(包含)

描述

此函数扫描从 startend(包含)的页范围,并为所有设置了 DIRTY 标签的页标记一个特殊的 TOWRITE 标签。调用者随后可以使用 TOWRITE 标签来识别符合回写条件的页。此机制用于避免因进程不断在文件中创建新的脏页而导致回写活锁(因此此函数必须快速,以便它标记页的速度快于脏页创建进程的速度)。

struct folio *writeback_iter(struct address_space *mapping, struct writeback_control *wbc, struct folio *folio, int *error)

迭代映射的 folio 以进行回写

参数

struct address_space *mapping

要回写的 address space 结构

struct writeback_control *wbc

回写上下文

struct folio *folio

之前迭代的 folio(NULL 表示开始)

int *error

用于回写错误的输入/输出指针(参见下文)

描述

此函数返回 **wbc** 在 **mapping** 上描述的回写操作的下一个 folio,应在 `->writepages` 实现的 while 循环中调用。

要开始回写操作,在 **folio** 参数中传递 NULL,对于随后的每次迭代,应将之前返回的 folio 传回。

如果在 writeback_iter() 循环内部的每个 folio 回写中发生错误,应将 **error** 设置为错误值。

一旦 **wbc** 中描述的回写完成,此函数将返回 NULL,如果任何迭代中发生错误,则将其恢复到 **error**。

注意

调用者不应使用 `break` 或 `goto` 手动跳出循环,而必须持续调用 writeback_iter() 直到它返回 NULL

返回

要写入的 folio,如果循环完成则为 NULL

int write_cache_pages(struct address_space *mapping, struct writeback_control *wbc, writepage_t writepage, void *data)

遍历给定 address space 的脏页列表并写入所有这些页。

参数

struct address_space *mapping

要回写的 address space 结构

struct writeback_control *wbc

从 **`*wbc->nr_to_write`** 中减去已写入页的数量

writepage_t writepage

为每个页调用的函数

void *data

传递给 `writepage` 函数的数据

返回

成功时返回 0,否则返回负错误码

注意

请改用 writeback_iter()

bool filemap_dirty_folio(struct address_space *mapping, struct folio *folio)

为不使用 buffer_heads 的文件系统将 folio 标记为脏。

参数

struct address_space *mapping

此 folio 所属的地址空间。

struct folio *folio

要标记为脏的 folio。

描述

不使用 `buffer_heads` 的文件系统应从其 `dirty_folio` 地址空间操作中调用此函数。它忽略 `folio_get_private()` 的内容,因此如果文件系统将单个块标记为脏,文件系统应自行处理。

使用 `buffer_heads` 的文件系统有时也会在单个缓冲区被标记为脏时使用此函数:在这种情况下,我们希望将 folio 标记为脏,而不是所有缓冲区。这是一种“自下而上”的脏标记,而 block_dirty_folio() 是一种“自上而下”的脏标记。

调用者必须确保这不会与截断发生竞争。大多数情况下只需持有 folio 锁即可,但例如 `zap_pte_range()` 在 folio 已映射且持有 pte 锁时调用,这也会阻止截断。

bool folio_redirty_for_writepage(struct writeback_control *wbc, struct folio *folio)

拒绝写入脏 folio。

参数

struct writeback_control *wbc

回写控制。

struct folio *folio

该 folio。

描述

当 `writepage` 实现因某种原因决定不写入 **folio** 时,它应该调用此函数,解锁 **folio** 并返回 0。

返回

如果重新标记 folio 为脏则为 True。如果其他人先将其标记为脏则为 False。

bool folio_mark_dirty(struct folio *folio)

将 folio 标记为已修改。

参数

struct folio *folio

该 folio。

描述

此函数运行时,folio 不得被截断。持有 folio 锁足以防止截断,但有些调用者无法获取睡眠锁。这些调用者会转而持有包含此 folio 中至少一个页的页表的页表锁。截断会在解除页映射并从其映射中移除 folio 之前阻塞在页表锁上。

返回

如果 folio 是新标记为脏的则为 True,如果已是脏的则为 False。

void folio_wait_writeback(struct folio *folio)

等待 folio 完成回写。

参数

struct folio *folio

要等待的 folio。

描述

如果 folio 当前正在回写到存储,则等待 I/O 完成。

上下文

休眠。必须在进程上下文中调用,并且不能持有自旋锁。调用者应持有 folio 的引用。如果 folio 未锁定,回写完成后可能会再次开始回写。

int folio_wait_writeback_killable(struct folio *folio)

等待 folio 完成回写。

参数

struct folio *folio

要等待的 folio。

描述

如果 folio 当前正在回写到存储,则等待 I/O 完成或致命信号到达。

上下文

休眠。必须在进程上下文中调用,并且不能持有自旋锁。调用者应持有 folio 的引用。如果 folio 未锁定,回写完成后可能会再次开始回写。

返回

成功时返回 0,如果在等待期间收到致命信号则返回 -EINTR。

void folio_wait_stable(struct folio *folio)

等待回写完成,如果需要的话。

参数

struct folio *folio

要等待的 folio。

描述

此函数判断给定 folio 是否与要求在回写期间保持 folio 内容稳定的后端设备相关。如果是,则它将等待任何待处理的回写完成。

上下文

休眠。必须在进程上下文中调用,并且不能持有自旋锁。调用者应持有 folio 的引用。如果 folio 未锁定,回写完成后可能会再次开始回写。

截断

void folio_invalidate(struct folio *folio, size_t offset, size_t length)

使 folio 的部分或全部无效。

参数

struct folio *folio

受影响的 folio。

size_t offset

要无效化范围的起始

size_t length

要无效化范围的长度

描述

当 folio 的全部或部分因截断操作而变得无效时,会调用 folio_invalidate()

folio_invalidate() 无需释放所有缓冲区,但它必须确保 **offset** 之外没有脏缓冲区,并且没有针对截断点之外的任何块进行 I/O。因为调用者即将释放(并可能重用)磁盘上的那些块。

void truncate_inode_pages_range(struct address_space *mapping, loff_t lstart, loff_t lend)

截断由起始和结束字节偏移量指定的页范围

参数

struct address_space *mapping

要截断的映射

loff_t lstart

从哪个偏移量开始截断

loff_t lend

到哪个偏移量结束截断(包含)

描述

截断页缓存,移除指定偏移量之间的页(如果 `lstart` 或 `lend + 1` 未按页对齐,则将部分页清零)。

截断分两次进行——第一次是非阻塞的。它不会阻塞页锁,也不会阻塞回写。第二次会等待。这是为了尽可能阻止受影响区域的 I/O。第一次会移除大部分页,因此第二次的搜索成本较低。

我们将缓存热点提示传递给页释放代码。即使映射很大,通常也是最后几个页是最近访问过的,并且释放是按文件偏移量升序进行的。

请注意,由于 `->invalidate_folio()` 接受要无效化的范围,truncate_inode_pages_range 能够正确处理 `lend + 1` 未按页对齐的情况。

void truncate_inode_pages(struct address_space *mapping, loff_t lstart)

从一个偏移量开始截断 *所有* 页

参数

struct address_space *mapping

要截断的映射

loff_t lstart

从哪个偏移量开始截断

描述

在 `inode->i_rwsem` 和 `mapping->invalidate_lock` 的保护下(并串行化)调用。

注意

当此函数返回时,指定范围内可能有一个正在删除过程中的页(在 `__filemap_remove_folio()` 内部)。因此,即使在整个映射被截断之后,当此函数返回时 `mapping->nrpages` 也可以是非零的。

void truncate_inode_pages_final(struct address_space *mapping)

在 inode 销毁前截断 *所有* 页

参数

struct address_space *mapping

要截断的映射

描述

在 `inode->i_rwsem` 的保护下(并串行化)调用。

文件系统必须在 `.evict_inode` 路径中使用此函数,以通知 VM 这是最终截断,并且 inode 将被移除。

unsigned long invalidate_mapping_pages(struct address_space *mapping, pgoff_t start, pgoff_t end)

使一个 inode 的所有干净、未锁定缓存无效

参数

struct address_space *mapping

包含要无效化缓存的 `address_space`

pgoff_t start

要无效化的起始偏移量

pgoff_t end

要无效化的结束偏移量(包含)

描述

此函数移除干净、未映射和未锁定的页,以及影子条目。它不会阻塞 I/O 活动。

如果您想移除一个 inode 的所有页,无论其使用和回写状态如何,请使用 truncate_inode_pages()

返回

其内容已无效化的索引数量

int invalidate_inode_pages2_range(struct address_space *mapping, pgoff_t start, pgoff_t end)

从 `address_space` 中移除页范围

参数

struct address_space *mapping

该 `address_space`

pgoff_t start

要无效化的页起始偏移量

pgoff_t end

要无效化的页结束偏移量(包含)

描述

在无效化之前,任何发现映射到页表中的页都将被解除映射。

返回

如果任何页无法无效化,则返回 -EBUSY。

int invalidate_inode_pages2(struct address_space *mapping)

从 `address_space` 中移除所有页

参数

struct address_space *mapping

该 `address_space`

描述

在无效化之前,任何发现映射到页表中的页都将被解除映射。

返回

如果任何页无法无效化,则返回 -EBUSY。

void truncate_pagecache(struct inode *inode, loff_t newsize)

解除映射并移除已截断的页缓存

参数

struct inode *inode

inode

loff_t newsize

新文件大小

描述

在调用 `truncate_pagecache` 之前,inode 的新 `i_size` 必须已写入。

此函数通常应在文件系统释放与已释放范围相关的资源(例如,解除分配块)之前调用。这样,页缓存将始终与磁盘格式保持逻辑一致,文件系统也无需处理诸如为底层块已解除分配的页调用 `writepage` 的情况。

void truncate_setsize(struct inode *inode, loff_t newsize)

更新 inode 和页缓存以适应新的文件大小

参数

struct inode *inode

inode

loff_t newsize

新文件大小

描述

`truncate_setsize` 更新 `i_size` 并(如果需要)将页缓存截断到 **newsize**。它通常在文件系统的 `setattr` 函数中,当传递 `ATTR_SIZE` 时被调用。

必须在持有串行化截断和写入的锁(通常是 `i_rwsem`,但例如 xfs 使用不同的锁)并且在执行所有文件系统特定的块截断之前调用。

void pagecache_isize_extended(struct inode *inode, loff_t from, loff_t to)

在 `i_size` 扩展后更新页缓存

参数

struct inode *inode

`i_size` 被扩展的 inode

loff_t from

原始 inode 大小

loff_t to

新 inode 大小

描述

处理因扩展截断或在当前 `i_size` 之后开始写入而导致的 inode 大小扩展。我们将跨越当前 `i_size` 的页标记为只读 (RO),以便在首次写入访问该页时调用 `page_mkwrite()`。在 `i_size` 更改后,文件系统将在用户通过 `mmap` 写入该页之前更新其每块信息。

此函数必须在 `i_size` 更新后调用,以便在我们解锁 folio 之后发生的页错误能够看到新的 `i_size`。此函数必须在我们仍持有 `i_rwsem` 时调用——这不仅确保 `i_size` 稳定,而且确保在准备好以新的 inode 大小存储 `mmap` 写入之前,用户空间无法观察到新的 `i_size` 值。

void truncate_pagecache_range(struct inode *inode, loff_t lstart, loff_t lend)

解除映射并移除已打洞的页缓存

参数

struct inode *inode

inode

loff_t lstart

洞的起始偏移量

loff_t lend

洞的最后一个字节的偏移量

描述

此函数通常应在文件系统释放与已释放范围相关的资源(例如,解除分配块)之前调用。这样,页缓存将始终与磁盘格式保持逻辑一致,文件系统也无需处理诸如为底层块已解除分配的页调用 `writepage` 的情况。

void filemap_set_wb_err(struct address_space *mapping, int err)

在 `address_space` 上设置回写错误

参数

struct address_space *mapping

要设置回写错误的映射

int err

要在映射中设置的错误

描述

当回写以某种方式失败时,我们必须记录该错误,以便在调用 `fsync` 等操作时通知用户空间。我们力求报告错误发生时所有打开的文件上的错误。一些内部调用者也需要知道何时发生了回写错误。

当发生回写错误时,大多数文件系统会希望调用 `filemap_set_wb_err` 来在映射中记录错误,以便在文件上调用 `fsync` 时自动报告该错误。

int filemap_check_wb_err(struct address_space *mapping, errseq_t since)

自标记采样以来是否发生过错误?

参数

struct address_space *mapping

要检查回写错误的映射

errseq_t since

之前采样的 `errseq_t`

描述

从映射中获取 `errseq_t` 值,并查看它自给定值采样以来是否已更改。

如果已更改,则报告最新设置的错误,否则返回 0。

errseq_t filemap_sample_wb_err(struct address_space *mapping)

采样当前 `errseq_t` 以测试后续错误

参数

struct address_space *mapping

要采样的映射

描述

回写错误总是相对于过去的特定采样点报告。此函数提供这些采样点。

errseq_t file_sample_sb_err(struct file *file)

采样当前 `errseq_t` 以测试后续错误

参数

struct file *file

要采样的文件指针

描述

获取给定 struct file 的最新超块级别 `errseq_t` 值。

void mapping_set_error(struct address_space *mapping, int error)

在 `address_space` 中记录回写错误

参数

struct address_space *mapping

应设置错误的映射

int error

要在映射中设置的错误

描述

当回写以某种方式失败时,我们必须记录该错误,以便在调用 `fsync` 等操作时通知用户空间。我们力求报告错误发生时所有打开的文件上的错误。一些内部调用者也需要知道何时发生了回写错误。

当发生回写错误时,大多数文件系统会希望调用 `mapping_set_error` 来在映射中记录错误,以便在应用程序调用 `fsync(2)` 时可以报告该错误。

void mapping_set_large_folios(struct address_space *mapping)

指示文件支持大 folio。

参数

struct address_space *mapping

文件的地址空间。

描述

文件系统应在其 inode 构造函数中调用此函数,以指示 VFS 可以使用大 folio 来缓存文件内容。

上下文

不应在 inode 活动时调用此函数,因为它不是原子的。

pgoff_t mapping_align_index(struct address_space *mapping, pgoff_t index)

为此映射对齐索引。

参数

struct address_space *mapping

该 `address_space`。

pgoff_t index

页索引。

描述

folio 的索引必须自然对齐。如果您要向页缓存添加新的 folio 并需要知道为其分配哪个索引,请调用此函数。

struct folio *folio_flush_mapping(struct folio *folio)

查找此 folio 所属的文件映射。

参数

struct folio *folio

该 folio。

描述

对于页缓存中的 folio,返回此页所属的映射。匿名 folio 返回 `NULL`,即使它们在交换缓存中。其他类型的 folio 也返回 `NULL`。

此函数仅由架构缓存刷新代码使用。如果您不是编写缓存刷新代码,您可能需要 folio_mapping() 或 `folio_file_mapping()`。

struct inode *folio_inode(struct folio *folio)

获取此 folio 的宿主 inode。

参数

struct folio *folio

该 folio。

描述

对于页缓存中的 folio,返回此 folio 所属的 inode。

不要为不在页缓存中的 folio 调用此函数。

void folio_attach_private(struct folio *folio, void *data)

将私有数据附加到 folio。

参数

struct folio *folio

要附加数据的 folio。

void *data

要附加到 folio 的数据。

描述

将私有数据附加到 folio 会增加页的引用计数。数据必须在 folio 释放之前分离。

void *folio_change_private(struct folio *folio, void *data)

更改 folio 上的私有数据。

参数

struct folio *folio

要更改数据的 folio。

void *data

要在 folio 上设置的数据。

描述

更改附加到 folio 的私有数据并返回旧数据。该页之前必须已附加数据,并且数据必须在 folio 释放之前分离。

返回

之前附加到 folio 的数据。

void *folio_detach_private(struct folio *folio)

从 folio 分离私有数据。

参数

struct folio *folio

要分离数据的 folio。

描述

移除之前附加到 folio 的数据,并减少页的引用计数。

返回

附加到 folio 的数据。

type fgf_t

从页缓存获取 folio 的标志。

描述

大多数页缓存用户不需要使用这些标志;有一些便利函数,例如 filemap_get_folio()filemap_lock_folio()。对于需要更精确控制 folio 操作的用户,可以使用这些传递给 __filemap_get_folio() 的标志。

  • FGP_ACCESSED - folio 将被标记为已访问。

  • FGP_LOCK - 返回的 folio 将被锁定。

  • FGP_CREAT - 如果没有 folio,则分配一个新的 folio,并将其添加到页缓存和 VM 的 LRU 列表中。返回的 folio 将被锁定。

  • FGP_FOR_MMAP - 如果 folio 已在缓存中,调用者希望执行自己的锁定操作。如果 folio 是新分配的,则在返回前解锁它,以便调用者可以执行相同的操作。

  • FGP_WRITE - folio 将由调用者写入。

  • FGP_NOFS - `__GFP_FS` 将在 `gfp` 中被清除。

  • FGP_NOWAIT - 不阻塞 folio 锁。

  • FGP_STABLE - 等待 folio 稳定(完成回写)

  • FGP_DONTCACHE - 无缓存的缓冲 I/O

  • FGP_WRITEBEGIN - 在文件系统 `write_begin()` 实现中使用的标志。

fgf_t fgf_set_order(size_t size)

在 `fgf_t` 标志中编码长度。

参数

size_t size

建议创建的 folio 大小。

描述

__filemap_get_folio() 的调用者可以使用此函数来建议创建的 folio 的首选大小。如果索引处已存在 folio,则无论其大小如何,都将返回该 folio。如果新创建 folio,则由于对齐约束、内存压力或附近索引处存在其他 folio,其大小可能与请求的不同。

struct folio *filemap_get_folio(struct address_space *mapping, pgoff_t index)

查找并获取 folio。

参数

struct address_space *mapping

要搜索的 address_space。

pgoff_t index

页索引。

描述

查找 **mapping** 和 **index** 处的页缓存条目。如果存在 folio,则返回该 folio 并增加引用计数。

返回

一个 folio,如果此索引的缓存中没有 folio,则返回 `ERR_PTR(-ENOENT)`。不会返回影子、交换或 DAX 条目。

struct folio *filemap_lock_folio(struct address_space *mapping, pgoff_t index)

查找并锁定 folio。

参数

struct address_space *mapping

要搜索的 address_space。

pgoff_t index

页索引。

描述

查找 **mapping** 和 **index** 处的页缓存条目。如果存在 folio,则返回该 folio 并锁定,同时增加引用计数。

上下文

可能休眠。

返回

一个 folio,如果此索引的缓存中没有 folio,则返回 `ERR_PTR(-ENOENT)`。不会返回影子、交换或 DAX 条目。

struct folio *filemap_grab_folio(struct address_space *mapping, pgoff_t index)

从页缓存中获取 folio

参数

struct address_space *mapping

要搜索的地址空间

pgoff_t index

页索引

描述

查找 **mapping** 和 **index** 处的页缓存条目。如果未找到 folio,则创建一个新的 folio。该 folio 被锁定,标记为已访问,并返回。

返回

找到或创建的 folio。如果未找到 folio 且创建失败,则返回 `ERR_PTR(-ENOMEM)`。

struct page *find_get_page(struct address_space *mapping, pgoff_t offset)

查找并获取页引用

参数

struct address_space *mapping

要搜索的 `address_space`

pgoff_t offset

页索引

描述

查找 **mapping** 和 **offset** 处的页缓存槽。如果存在页缓存页,则返回该页并增加引用计数。

否则,返回 NULL

struct page *find_lock_page(struct address_space *mapping, pgoff_t index)

定位、固定并锁定页缓存页

参数

struct address_space *mapping

要搜索的 `address_space`

pgoff_t index

页索引

描述

查找 **mapping** 和 **index** 处的页缓存条目。如果存在页缓存页,则返回该页并锁定,同时增加引用计数。

上下文

可能休眠。

返回

一个 `struct page`,如果此索引的缓存中没有页,则为 NULL

struct page *find_or_create_page(struct address_space *mapping, pgoff_t index, gfp_t gfp_mask)

定位或添加页缓存页

参数

struct address_space *mapping

页面的地址空间

pgoff_t index

页在映射中的索引

gfp_t gfp_mask

页分配模式

描述

查找 **mapping** 和 **offset** 处的页缓存槽。如果存在页缓存页,则返回该页并锁定,同时增加引用计数。

如果页不存在,则使用 **gfp_mask** 分配一个新页,并将其添加到页缓存和 VM 的 LRU 列表中。该页被锁定并返回,同时增加引用计数。

内存耗尽时,返回 NULL

find_or_create_page() 可能会休眠,即使 **gfp_flags** 指定了原子分配!

struct page *grab_cache_page_nowait(struct address_space *mapping, pgoff_t index)

返回给定缓存中给定索引处的锁定页

参数

struct address_space *mapping

目标地址空间

pgoff_t index

页索引

描述

与 `grab_cache_page()` 相同,但如果页不可用则不等待。这适用于推测性数据生成器,其中如果无法获取页,则可以重新生成数据。在持有另一个页的锁时调用此例程应该是安全的。

在分配页时清除 `__GFP_FS`,以避免递归进入文件系统并与调用者的锁定页产生死锁。

pgoff_t folio_next_index(struct folio *folio)

获取下一个 folio 的索引。

参数

struct folio *folio

当前 folio。

返回

文件中此 folio 之后的 folio 的索引。

struct page *folio_file_page(struct folio *folio, pgoff_t index)

特定索引对应的页。

参数

struct folio *folio

包含此索引的 folio。

pgoff_t index

我们想要查找的索引。

描述

有时在页缓存中查找 folio 后,我们需要获取特定索引对应的页(例如页错误)。

返回

包含此索引文件数据的页。

bool folio_contains(struct folio *folio, pgoff_t index)

此 folio 是否包含此索引?

参数

struct folio *folio

该 folio。

pgoff_t index

文件内的页索引。

上下文

调用者应已锁定 folio 并确保例如 `shmem` 未将此 folio 移动到交换缓存。

返回

真或假。

pgoff_t page_pgoff(const struct folio *folio, const struct page *page)

计算此页的逻辑页偏移量。

参数

const struct folio *folio

包含此页的 folio。

const struct page *page

我们需要获取偏移量的页。

描述

对于文件页,这是文件开头以 `PAGE_SIZE` 为单位的偏移量。对于匿名页,这是 `anon_vma` 开头以 `PAGE_SIZE` 为单位的偏移量。对于 KSM 页,这将返回无意义的值。

上下文

调用者必须持有 folio 的引用,否则阻止其被拆分或释放。

返回

以 `PAGE_SIZE` 为单位的偏移量。

loff_t folio_pos(const struct folio *folio)

返回此 folio 在其文件中的字节位置。

参数

const struct folio *folio

该 folio。

bool folio_trylock(struct folio *folio)

尝试锁定 folio。

参数

struct folio *folio

要尝试锁定的 folio。

描述

有时不希望等待 folio 解锁(例如,当锁以错误的顺序获取时,或者批处理 folio 比按顺序处理它们更重要时)。通常 folio_lock() 是正确的调用函数。

上下文

任何上下文。

返回

锁是否成功获取。

void folio_lock(struct folio *folio)

锁定此 folio。

参数

struct folio *folio

要锁定的 folio。

描述

folio 锁保护许多方面,可能比它应该保护的更多。它主要在 folio 从其后端文件或交换空间更新时持有。它也在 folio 从其 `address_space` 截断时持有,因此持有此锁足以保持 `folio->mapping` 稳定。

当 `write()` 修改页以提供 POSIX 原子性保证时(只要写入不跨越页边界),也会持有 folio 锁。对 folio 中数据的其他修改不持有 folio 锁,并且可能与写入竞争,例如 DMA 和对映射页的存储。

上下文

可能休眠。如果您需要获取两个或更多 folio 的锁,如果它们在同一个 `address_space` 中,则必须按升序索引获取。如果它们在不同的 `address_space` 中,则首先获取属于内存中地址最低的 `address_space` 的 folio 的锁。

void lock_page(struct page *page)

锁定包含此页的 folio。

参数

struct page *page

要锁定的页。

描述

有关锁保护内容的描述,请参阅 folio_lock()。这是一个遗留函数,新代码可能应该改用 folio_lock()

上下文

可能休眠。同一个 folio 中的页共享一个锁,因此不要尝试锁定共享一个 folio 的两个页。

int folio_lock_killable(struct folio *folio)

锁定此 folio,可被致命信号中断。

参数

struct folio *folio

要锁定的 folio。

描述

尝试锁定 folio,类似于 folio_lock(),但获取锁的休眠可被致命信号中断。

上下文

可能休眠;参见 folio_lock()

返回

如果成功获取锁则为 0;如果收到致命信号则为 -EINTR。

bool filemap_range_needs_writeback(struct address_space *mapping, loff_t start_byte, loff_t end_byte)

检查范围是否可能需要回写

参数

struct address_space *mapping

要检查的地址空间

loff_t start_byte

范围起始的字节偏移量

loff_t end_byte

范围结束的字节偏移量(包含)

描述

在提供的范围内查找至少一个页,通常用于检查在此范围内直接写入是否会触发回写。由带 `IOCB_NOWAIT` 的 `O_DIRECT` 读/写使用,以查看调用者在继续之前是否需要执行 filemap_write_and_wait_range()

返回

如果调用者在此范围内对页执行 `O_DIRECT` 操作之前应执行 filemap_write_and_wait_range(),则为 true,否则为 false

struct readahead_control

描述一个预读请求。

定义:

struct readahead_control {
    struct file *file;
    struct address_space *mapping;
    struct file_ra_state *ra;
};

成员

文件

文件,主要由网络文件系统用于身份验证。如果由文件系统内部调用,则可能为 `NULL`。

映射

预读此文件系统对象。

ra

文件预读状态。可能为 `NULL`。

描述

预读请求针对连续页。实现 `->readahead` 方法的文件系统应在循环中调用 readahead_folio() 或 `__readahead_batch()`,并尝试开始读取请求中的每个 folio。

此结构中的大多数字段是私有的,应由以下函数访问。

void page_cache_sync_readahead(struct address_space *mapping, struct file_ra_state *ra, struct file *file, pgoff_t index, unsigned long req_count)

通用文件预读

参数

struct address_space *mapping

包含页缓存和 I/O 向量的 `address_space`

struct file_ra_state *ra

包含预读状态的 `file_ra_state`

struct file *file

文件系统用于身份验证。

pgoff_t index

要读取的第一个页的索引。

unsigned long req_count

调用者正在读取的页总数。

描述

page_cache_sync_readahead() 应该在缓存未命中时被调用:它将提交读取。预读逻辑可能会决定,如果访问模式表明能提高性能,则在读取请求中附加更多页。

void page_cache_async_readahead(struct address_space *mapping, struct file_ra_state *ra, struct file *file, struct folio *folio, unsigned long req_count)

文件预读,用于标记的页

参数

struct address_space *mapping

包含页缓存和 I/O 向量的 `address_space`

struct file_ra_state *ra

包含预读状态的 `file_ra_state`

struct file *file

文件系统用于身份验证。

struct folio *folio

触发预读调用的folio。

unsigned long req_count

调用者正在读取的页总数。

描述

page_cache_async_readahead() 应该在页面被标记为 PageReadahead 并被使用时调用;这是一个标记,表明应用程序已使用了足够的预读窗口,我们应该开始引入更多页。

struct folio *readahead_folio(struct readahead_control *ractl)

获取下一个要读取的folio。

参数

struct readahead_control *ractl

当前的预读请求。

上下文

该folio已被锁定。一旦所有对该folio的I/O操作完成,调用者应解锁该folio。

返回

指向下一个folio的指针,如果已完成则为 NULL

loff_t readahead_pos(struct readahead_control *rac)

此预读请求在文件中的字节偏移量。

参数

struct readahead_control *rac

预读请求。

size_t readahead_length(struct readahead_control *rac)

此预读请求中的字节数。

参数

struct readahead_control *rac

预读请求。

pgoff_t readahead_index(struct readahead_control *rac)

此预读请求中第一个页的索引。

参数

struct readahead_control *rac

预读请求。

unsigned int readahead_count(struct readahead_control *rac)

此预读请求中的页数。

参数

struct readahead_control *rac

预读请求。

size_t readahead_batch_length(struct readahead_control *rac)

当前批处理中的字节数。

参数

struct readahead_control *rac

预读请求。

ssize_t folio_mkwrite_check_truncate(struct folio *folio, struct inode *inode)

检查folio是否被截断

参数

struct folio *folio

要检查的folio

struct inode *inode

用于检查folio的inode

返回

folio中到文件末尾(EOF)的字节数,如果folio被截断则为 -EFAULT。

unsigned int i_blocks_per_folio(struct inode *inode, struct folio *folio)

此folio中可容纳的块数。

参数

struct inode *inode

包含这些块的inode。

struct folio *folio

该 folio。

描述

如果块大小大于此folio的大小,则返回零。

上下文

调用者应持有对folio的引用计数,以防止其被拆分。

返回

此folio覆盖的文件系统块数量。

内存池

void mempool_exit(mempool_t *pool)

退出一个用 mempool_init() 初始化的内存池

参数

mempool_t *pool

指向已用 mempool_init() 初始化的内存池的指针。

描述

释放 **pool** 中所有保留的元素以及 **pool** 本身。此函数仅在 free_fn() 函数休眠时休眠。

可以在归零但未初始化的内存池(即使用 kzalloc() 分配的内存池)上调用。

void mempool_destroy(mempool_t *pool)

解除分配内存池

参数

mempool_t *pool

指向通过 mempool_create() 分配的内存池的指针。

描述

释放 **pool** 中所有保留的元素以及 **pool** 本身。此函数仅在 free_fn() 函数休眠时休眠。

int mempool_resize(mempool_t *pool, int new_min_nr)

调整现有内存池的大小

参数

mempool_t *pool

指向通过 mempool_create() 分配的内存池的指针。

int new_min_nr

保证为此池分配的最小元素新数量。

描述

此函数会收缩/增长内存池。在增长的情况下,不能保证内存池会立即增长到新大小,但新的 mempool_free() 调用会重新填充它。此函数可能会休眠。

请注意,调用者必须保证在此函数运行时不会调用 mempool_destroy。在此函数执行期间,mempool_alloc() 和 mempool_free() 可能会被调用(例如,来自 IRQ 上下文)。

返回

成功返回 0,否则返回负错误码。

void *mempool_alloc_preallocated(mempool_t *pool)

从属于特定内存池的预分配元素中分配一个元素

参数

mempool_t *pool

指向通过 mempool_create() 分配的内存池的指针。

描述

此函数类似于 mempool_alloc,但它只尝试从预分配的元素中分配一个元素。它不会休眠,如果没有预分配的元素可用,则立即返回。

返回

指向已分配元素的指针,如果没有元素可用则为 NULL

void mempool_free(void *element, mempool_t *pool)

将一个元素返回给池。

参数

void *element

池元素指针。

mempool_t *pool

指向通过 mempool_create() 分配的内存池的指针。

描述

此函数仅在 free_fn() 函数休眠时休眠。

DMA 池

struct dma_pool *dma_pool_create_node(const char *name, struct device *dev, size_t size, size_t align, size_t boundary, int node)

创建一个用于 DMA 的一致性内存块池。

参数

const char *name

池的名称,用于诊断

struct device *dev

将执行 DMA 的设备

size_t size

此池中块的大小。

size_t align

块的对齐要求;必须是2的幂

size_t boundary

返回的块不会跨越此2的幂边界

int node

可选的 NUMA 节点,用于分配 `dma_pool` 和 `dma_page` 结构体

上下文

不在中断上下文

描述

给定其中一个池,可以使用 dma_pool_alloc() 来分配内存。此类内存将全部具有“一致性” DMA 映射,设备及其驱动程序无需使用缓存刷新原语即可访问。由于对齐原因,实际分配的块大小可能大于请求的大小。

如果 **boundary** 不为零,从 dma_pool_alloc() 返回的对象将不会跨越该大小边界。这对于对单个 DMA 传输有寻址限制的设备很有用,例如不跨越 4KBytes 的边界。

返回

具有请求特性的 DMA 分配池,如果无法创建则为 NULL

void dma_pool_destroy(struct dma_pool *pool)

销毁 DMA 内存块池。

参数

struct dma_pool *pool

将被销毁的 DMA 池

上下文

不在中断上下文

描述

调用者保证池中不再有内存正在使用,并且在此调用之后没有任何东西会尝试使用该池。

void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle)

获取一块一致性内存

参数

struct dma_pool *pool

将产生该块的 DMA 池

gfp_t mem_flags

GFP_* 位掩码

dma_addr_t *handle

指向块的 DMA 地址的指针

返回

当前未使用的块的内核虚拟地址,并通过句柄报告其 DMA 地址。如果无法分配此类内存块,则返回 NULL

void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t dma)

将块放回 DMA 池

参数

struct dma_pool *pool

持有该块的 DMA 池

void *vaddr

块的虚拟地址

dma_addr_t dma

块的 DMA 地址

描述

调用者承诺,除非此块首先被重新分配,否则设备和驱动程序都不会再触碰此块。

struct dma_pool *dmam_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation)

受管理的 dma_pool_create()

参数

const char *name

池的名称,用于诊断

struct device *dev

将执行 DMA 的设备

size_t size

此池中块的大小。

size_t align

块的对齐要求;必须是2的幂

size_t allocation

返回的块不会跨越此边界(或零)

描述

受管理的 dma_pool_create()。使用此函数创建的 DMA 池会在驱动程序分离时自动销毁。

返回

具有请求特性的受管理 DMA 分配池,如果无法创建则为 NULL

void dmam_pool_destroy(struct dma_pool *pool)

受管理的 dma_pool_destroy()

参数

struct dma_pool *pool

将被销毁的 DMA 池

描述

受管理的 dma_pool_destroy()

更多内存管理函数

void zap_vma_ptes(struct vm_area_struct *vma, unsigned long address, unsigned long size)

移除映射 VMA 的 PTE

参数

struct vm_area_struct *vma

包含要清除的 PTE 的 vm_area_struct

unsigned long address

要清除的页的起始地址

unsigned long size

要清除的字节数

描述

此函数仅解除映射分配给 VM_PFNMAP VMA 的 PTE。

整个地址范围必须完全包含在 VMA 中。

int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr, struct page **pages, unsigned long *num)

将多个页插入用户 VMA,批量处理 PMD 锁。

参数

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

这些页的目标起始用户地址

struct page **pages

源内核页

unsigned long *num

输入:要映射的页数。输出:*未*映射的页数。(0 表示所有页都成功映射)。

描述

在插入多个页时,优先于 vm_insert_page()

如果发生错误,我们可能只映射了所提供页的子集。调用者有责任处理这种情况。

适用与 vm_insert_page() 相同的限制。

int vm_insert_page(struct vm_area_struct *vma, unsigned long addr, struct page *page)

将单个页插入用户 VMA

参数

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

此页的目标用户地址

struct page *page

源内核页

描述

这允许驱动程序将它们分配的单个页插入用户 VMA。某些 VMA 支持零页,请参阅 vm_mixed_zeropage_allowed()。

该页必须是一个干净的 _独立_ 内核分配。如果你分配了一个复合页,你需要将其标记为复合页(__GFP_COMP),或者手动将其拆分(参见 split_page())。

注意!传统上这是通过“remap_pfn_range()”完成的,该函数接受任意页保护参数。此函数不允许这样做。你的 VMA 保护必须正确设置,这意味着如果你想要一个共享可写映射,最好请求一个共享可写映射!

该页不需要被保留。

通常,此函数在 mm->mmap_lock 写锁下从 f_op->mmap() 处理程序中调用,因此它可以更改 vma->vm_flags。如果调用者想从其他地方(例如页错误处理程序)调用此函数,则必须在 VMA 上设置 VM_MIXEDMAP。

返回

成功返回 0,否则返回负错误码。

int vm_map_pages(struct vm_area_struct *vma, struct page **pages, unsigned long num)

映射从非零偏移量开始的内核页范围

参数

struct vm_area_struct *vma

要映射到的用户 VMA

struct page **pages

指向源内核页数组的指针

unsigned long num

页数组中的页数

描述

映射一个由 **num** 个页组成的对象,满足用户请求的 vm_pgoff

如果我们无法将任何页插入 VMA,函数将立即返回,保留之前已插入的页。来自 mmap 处理程序的调用者可以立即返回错误,因为它们的调用者会销毁 VMA,从而移除任何成功插入的页。其他调用者应自行安排调用 unmap_region()。

上下文

进程上下文。由 mmap 处理程序调用。

返回

成功时返回 0,否则返回错误代码。

int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages, unsigned long num)

映射从零偏移量开始的内核页范围

参数

struct vm_area_struct *vma

要映射到的用户 VMA

struct page **pages

指向源内核页数组的指针

unsigned long num

页数组中的页数

描述

类似于 vm_map_pages(),但它明确将偏移量设置为 0。此函数适用于未考虑 vm_pgoff 的驱动程序。

上下文

进程上下文。由 mmap 处理程序调用。

返回

成功时返回 0,否则返回错误代码。

vm_fault_t vmf_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, pgprot_t pgprot)

将单个 PFN 插入用户 VMA 并指定 pgprot

参数

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

此页的目标用户地址

unsigned long pfn

源内核 PFN

pgprot_t pgprot

插入页的 pgprot 标志

描述

这与 vmf_insert_pfn() 完全相同,只是它允许驱动程序按每页覆盖 pgprot。

这仅对 I/O 映射有意义,对 COW 映射则无意义。通常,使用多个 VMA 更好;vmf_insert_pfn_prot 应该只在无法使用多个 VMA 的情况下使用。

pgprot 通常仅在驱动程序设置的缓存和加密位与 **vma->vm_page_prot** 不同时才与 **vma->vm_page_prot** 不同,因为缓存或加密模式在 mmap() 时可能未知。

只要核心 VM 不使用 **vma->vm_page_prot** 来设置这些 VMA 的缓存和加密位(除了 COW 页),这都是可以的。核心 VM 仅使用不触及缓存或加密位的函数(如果需要则使用 pte_modify())来修改这些页表项,从而确保这一点。(例如参见 mprotect())。

此外,当创建新的页表项时,这仅通过 fault() 回调完成,从不使用 vma->vm_page_prot 的值,除了由于 COW 而指向匿名页的页表项。

上下文

进程上下文。可以使用 GFP_KERNEL 分配。

返回

vm_fault_t 值。

vm_fault_t vmf_insert_pfn(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn)

将单个 PFN 插入用户 VMA

参数

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

此页的目标用户地址

unsigned long pfn

源内核 PFN

描述

类似于 vm_insert_page,这允许驱动程序将它们分配的单个页插入用户 VMA。同样的注释适用。

此函数只能从 vm_ops->fault 处理程序中调用,在这种情况下,处理程序应返回此函数的结果。

VMA 不能是 COW 映射。

由于此函数仅针对当前不存在的页调用,因此我们不需要刷新旧的虚拟缓存或 TLB。

上下文

进程上下文。可以使用 GFP_KERNEL 分配。

返回

vm_fault_t 值。

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot)

将内核内存重新映射到用户空间

参数

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

目标页对齐的用户地址起始点

unsigned long pfn

内核物理内存地址的页帧号

unsigned long size

映射区域的大小

pgprot_t prot

此映射的页保护标志

注意

仅在调用时持有 MM 信号量才安全。

返回

成功返回 0,否则返回负错误码。

int vm_iomap_memory(struct vm_area_struct *vma, phys_addr_t start, unsigned long len)

将内存重新映射到用户空间

参数

struct vm_area_struct *vma

要映射到的用户 VMA

phys_addr_t start

要映射的物理内存的起始地址

unsigned long len

区域大小

描述

这是 io_remap_pfn_range() 的简化版本,供常见驱动程序使用。驱动程序只需提供要映射的物理内存范围,我们将根据 VMA 信息推断出其余部分。

注意!某些驱动程序可能希望首先调整 vma->vm_page_prot,以获取写入合并的详细信息或类似内容。

返回

成功返回 0,否则返回负错误码。

void unmap_mapping_pages(struct address_space *mapping, pgoff_t start, pgoff_t nr, bool even_cows)

从进程中解除页映射。

参数

struct address_space *mapping

包含要解除映射的页的地址空间。

pgoff_t start

要解除映射的第一个页的索引。

pgoff_t nr

要解除映射的页数。如果为 0,则解除映射到文件末尾。

bool even_cows

是否解除映射甚至私有的 COW(写时复制)页。

描述

从任何已将这些页映射到用户空间的进程中解除此地址空间中的页映射。通常,在截断文件时,您也希望移除 COW 页,但在使页缓存中的页无效时则不希望。

void unmap_mapping_range(struct address_space *mapping, loff_t const holebegin, loff_t const holelen, int even_cows)

解除指定 address_space 中所有 mmap 映射中对应于底层文件中指定字节范围的部分。

参数

struct address_space *mapping

包含要解除映射的 mmap 的地址空间。

loff_t const holebegin

要解除映射的第一个页中的字节,相对于底层文件的起始。这将向下舍入到 PAGE_SIZE 边界。请注意,这与 truncate_pagecache() 不同,后者必须保留部分页。相比之下,我们必须清除部分页。

loff_t const holelen

预期空洞的字节大小。这将向上舍入到 PAGE_SIZE 边界。holelen 为零表示截断到文件末尾。

int even_cows

截断文件时为 1,解除映射甚至私有 COW 页;但使页缓存无效时为 0,不丢弃私有数据。

int follow_pfnmap_start(struct follow_pfnmap_args *args)

在用户虚拟地址处查找 PFN 映射

参数

struct follow_pfnmap_args *args

指向结构体 **follow_pfnmap_args** 的指针

描述

调用者需要设置 args->vma 和 args->address 指向作为查找目标的虚拟地址。成功返回时,结果将放入其他输出字段。

调用者在使用完这些字段后,必须调用 follow_pfnmap_end() 来正确释放此类查找请求的锁和资源。

start() 和 end() 调用期间,**args** 中的结果将有效,因为会持有适当的锁。在调用 end() 后,**follow_pfnmap_args** 中的所有字段将无效,无法进一步访问。在 end() 调用后进一步使用此类信息可能需要调用者与页表更新进行适当的同步,否则可能会产生安全漏洞。

如果 PTE 映射了一个带引用计数的页,调用者有责任使用 MMU 通知器来防止失效;否则,稍后访问 PFN 可能会触发使用后释放(use-after-free)。

只允许 I/O 映射和原始 PFN 映射。mmap 信号量应以读模式持有,并且在调用 end() 之前不能释放 mmap 信号量。

此函数不得用于修改 PTE 内容。

返回

成功时返回零,否则返回负值。

void follow_pfnmap_end(struct follow_pfnmap_args *args)

结束一个 follow_pfnmap_start() 进程

参数

struct follow_pfnmap_args *args

指向结构体 **follow_pfnmap_args** 的指针

描述

必须与 follow_pfnmap_start() 配对使用。有关更多信息,请参见上面的 start() 函数。

int generic_access_phys(struct vm_area_struct *vma, unsigned long addr, void *buf, int len, int write)

iomem mmap 访问的通用实现

参数

struct vm_area_struct *vma

要访问的 VMA

unsigned long addr

用户空间地址,不是 **vma** 内的相对偏移量

void *buf

要读/写的缓冲区

int len

传输长度

int write

写入时设置为 FOLL_WRITE,否则为读取

描述

这是 iomem 映射的 vm_operations_struct.access 的通用实现。当 **vma** 不是基于页时,access_process_vm() 使用此回调。

int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr, void *buf, int len, unsigned int gup_flags)

从另一个进程的地址空间复制字符串。

参数

struct task_struct *tsk

目标地址空间的任务

unsigned long addr

要读取的起始地址

void *buf

目标缓冲区

int len

要复制的字节数

unsigned int gup_flags

修改查找行为的标志

描述

调用者必须持有 **mm** 的引用。

返回

从 **addr**(源)复制到 **buf**(目标)的字节数;不包括末尾的 NUL。始终保证留下以 NUL 结尾的缓冲区。发生任何错误时,返回 -EFAULT。

unsigned long get_pfnblock_flags_mask(const struct page *page, unsigned long pfn, unsigned long mask)

返回 pageblock_nr_pages 块页的请求标志组

参数

const struct page *page

感兴趣块内的页

unsigned long pfn

目标页帧号

unsigned long mask

调用者感兴趣的位掩码

返回

pageblock_bits 标志

void set_pfnblock_flags_mask(struct page *page, unsigned long flags, unsigned long pfn, unsigned long mask)

设置 pageblock_nr_pages 页块的请求标志组

参数

struct page *page

感兴趣块内的页

unsigned long flags

要设置的标志

unsigned long pfn

目标页帧号

unsigned long mask

调用者感兴趣的位掩码

bool move_freepages_block_isolate(struct zone *zone, struct page *page, int migratetype)

移动块中的空闲页以进行页隔离

参数

struct zone *zone

区域

struct page *page

页块页

int migratetype

要在页块上设置的迁移类型

描述

这类似于 move_freepages_block(),但处理了页隔离中遇到的特殊情况,即感兴趣的块可能是跨多个页块的更大 buddy 的一部分。

与常规页分配器路径(在从空闲列表窃取 buddy 时移动页)不同,页隔离关注的是两端可能有重叠 buddy 的任意 PFN 范围。

此函数处理了这种情况。跨越的 buddy 会被拆分成单独的页块。只移动感兴趣的块。

如果页可以移动,返回 true,否则返回 false

void __putback_isolated_page(struct page *page, unsigned int order, int mt)

将已隔离的页返回到其来源处

参数

struct page *page

被隔离的页

unsigned int order

隔离页的阶

int mt

页的页块的迁移类型

描述

此函数旨在将通过 __isolate_free_page 从空闲列表取出的页返回到它们被取出的空闲列表。

void ___free_pages(struct page *page, unsigned int order, fpi_t fpi_flags)

释放使用 alloc_pages() 分配的页。

参数

struct page *page

alloc_pages() 返回的页指针。

unsigned int order

分配的阶。

fpi_t fpi_flags

空闲页内部标志。

描述

此函数可以释放非复合页的多页分配。它不检查传入的 **order** 是否与分配的阶匹配,因此很容易导致内存泄漏。释放比分配更多的内存可能会发出警告。

如果此页的最后一个引用是推测性的,它将由 put_page() 释放,put_page() 只释放非复合分配的第一个页。为了防止剩余的页泄漏,我们在此处释放后续的页。如果您想使用页的引用计数来决定何时释放分配,您应该分配一个复合页,并使用 put_page() 而不是 __free_pages()。

上下文

可以在中断上下文或持有普通自旋锁时调用,但不能在 NMI 上下文或持有原始自旋锁时调用。

void *alloc_pages_exact(size_t size, gfp_t gfp_mask)

分配精确数量的物理连续页。

参数

size_t size

要分配的字节数

gfp_t gfp_mask

分配的 GFP 标志,不得包含 __GFP_COMP

描述

此函数类似于 alloc_pages(),不同之处在于它分配满足请求所需的最小页数。alloc_pages() 只能以2的幂页数分配内存。

此函数也受 MAX_PAGE_ORDER 的限制。

此函数分配的内存必须通过 free_pages_exact() 释放。

返回

指向已分配区域的指针,如果出错则为 NULL

void *alloc_pages_exact_nid(int nid, size_t size, gfp_t gfp_mask)

在一个节点上分配精确数量的物理连续页。

参数

int nid

内存应分配到的首选节点 ID

size_t size

要分配的字节数

gfp_t gfp_mask

分配的 GFP 标志,不得包含 __GFP_COMP

描述

类似于 alloc_pages_exact(),但会首先尝试在节点 nid 上分配,然后才回退。

返回

指向已分配区域的指针,如果出错则为 NULL

void *free_pages_exact(void *virt, size_t size)

释放通过 alloc_pages_exact() 分配的内存

参数

void *virt

alloc_pages_exact 返回的值。

size_t size

分配大小,与传递给 alloc_pages_exact() 的值相同。

描述

释放先前调用 alloc_pages_exact 所分配的内存。

unsigned long nr_free_zone_pages(int offset)

计算超出高水位线的页数

参数

int offset

最高区域的区域索引

描述

nr_free_zone_pages() 计算在给定区域索引处或以下的所有区域中,超出高水位线的页数。对于每个区域,页数计算为

nr_free_zone_pages = managed_pages - high_pages

返回

超出高水位线的页数。

unsigned long nr_free_buffer_pages(void)

计算超出高水位线的页数

参数

void

无参数

描述

nr_free_buffer_pages() 计算 ZONE_DMA 和 ZONE_NORMAL 中超出高水位线的页数。

返回

ZONE_DMA 和 ZONE_NORMAL 中超出高水位线的页数。

int find_next_best_node(int node, nodemask_t *used_node_mask)

找到应该出现在给定节点的后备列表中的下一个节点

参数

int node

我们正在追加其后备列表的节点

nodemask_t *used_node_mask

已使用节点的 nodemask_t

描述

我们使用多种因素来确定哪个是应该出现在给定节点的后备列表中的下一个节点。该节点不应已出现在 **node** 的后备列表中,并且根据距离数组(其中包含从系统中每个节点到每个节点的任意距离值),它应该是下一个最近的节点,并且还应优先选择没有 CPU 的节点,因为它们通常否则分配压力很小。

返回

找到的节点 ID,如果未找到节点则为 NUMA_NO_NODE

void setup_per_zone_wmarks(void)

当 min_free_kbytes 改变或内存热插拔(添加/移除)时调用

参数

void

无参数

描述

确保每个区域的水位线[min,low,high]值相对于 min_free_kbytes 正确设置。

int alloc_contig_range(unsigned long start, unsigned long end, unsigned migratetype, gfp_t gfp_mask)
  • 尝试分配给定范围的页

参数

unsigned long start

要分配的起始 PFN

unsigned long end

待分配的超出最后一个 PFN 的 PFN

unsigned migratetype

底层页块的迁移类型(可以是 #MIGRATE_MOVABLE 或 #MIGRATE_CMA)。范围内所有页块必须具有相同的迁移类型,并且必须是这两种类型中的一种。

gfp_t gfp_mask

GFP掩码。节点/区域/放置提示被忽略;只支持部分动作和回收修饰符。回收修饰符控制紧缩/迁移/回收期间的分配行为。

描述

PFN范围不必页块对齐。PFN范围必须属于单个区域。

此例程做的第一件事是尝试MIGRATE_ISOLATE该范围内的所有页块。一旦隔离,页块不应被其他方修改。

返回

成功时返回零,否则返回负错误码。成功时,PFN在[start, end)范围内的所有页都为调用者分配,并需要使用free_contig_range()释放。

struct page *alloc_contig_pages(unsigned long nr_pages, gfp_t gfp_mask, int nid, nodemask_t *nodemask)
  • 尝试查找和分配连续的页范围

参数

unsigned long nr_pages

要分配的连续页数

gfp_t gfp_mask

GFP掩码。节点/区域/放置提示限制搜索;只支持部分动作和回收修饰符。回收修饰符控制紧缩/迁移/回收期间的分配行为。

int nid

目标节点

nodemask_t *nodemask

其他可能节点的掩码

描述

此例程是alloc_contig_range()的包装器。它扫描适用区域列表中的区域,以查找连续的PFN范围,然后可以使用alloc_contig_range()尝试分配。此例程旨在处理buddy分配器无法满足的分配请求。

分配的内存始终与页边界对齐。如果nr_pages是2的幂,则分配的范围也保证与相同的nr_pages对齐(例如,1GB请求将与1GB对齐)。

已分配的页可以使用free_contig_range()释放,或手动对每个已分配的页调用__free_page()。

返回

成功时返回指向连续页的指针,否则返回NULL。

struct page *alloc_pages_nolock(int nid, unsigned int order)

任何上下文中的机会性重入分配

参数

int nid

要从中分配的节点

unsigned int order

分配阶大小

描述

从给定节点分配给定阶的页。此函数在任何上下文(原子上下文、NMI,以及重入分配器 -> 跟踪点 -> alloc_pages_nolock_noprof)中调用都是安全的。分配是尽力而为的,并且很容易失败,因此不应依赖于其成功。失败不会通过warn_alloc()报告。请参见下面的“总是失败条件”。

返回

成功时返回分配的页,失败时返回NULL。NULL不表示EBUSY或EAGAIN。它表示ENOMEM。没有理由再次调用它并期望非NULL。

int numa_nearest_node(int node, unsigned int state)

按状态查找最近的节点

参数

int node

开始搜索的节点ID

unsigned int state

过滤搜索的状态

描述

如果nid不在状态中,则按距离查找最近的节点。

返回

如果此node处于指定状态,则返回此node;否则,返回距离最近的节点。

int nearest_node_nodemask(int node, nodemask_t *mask)

mask中查找距离node最近的节点。

参数

int node

一个有效的节点ID,用于开始搜索。

nodemask_t *mask

指向表示允许节点的节点掩码的指针。

描述

此函数遍历mask中的所有节点,并计算与起始node的距离,然后返回最接近node的节点ID,如果未找到节点则返回MAX_NUMNODES。

请注意,node必须是可与node_distance()一起使用的有效节点ID,提供无效节点ID(例如NUMA_NO_NODE)可能导致崩溃或意外行为。

struct page *alloc_pages_mpol(gfp_t gfp, unsigned int order, struct mempolicy *pol, pgoff_t ilx, int nid)

根据NUMA内存策略分配页。

参数

gfp_t gfp

GFP标志。

unsigned int order

页分配的阶。

struct mempolicy *pol

指向NUMA内存策略的指针。

pgoff_t ilx

交错内存策略的索引(也区分alloc_pages())。

int nid

首选节点(通常是numa_node_id(),但mpol可能覆盖它)。

返回

成功时返回页,如果分配失败则返回NULL。

struct folio *vma_alloc_folio(gfp_t gfp, int order, struct vm_area_struct *vma, unsigned long addr)

为VMA分配一个folio。

参数

gfp_t gfp

GFP标志。

int order

folio的阶。

struct vm_area_struct *vma

指向VMA的指针。

unsigned long addr

分配的虚拟地址。必须在vma内部。

描述

vma中特定地址分配一个folio,使用适当的NUMA策略。调用者必须持有VMA的mm_struct的mmap_lock,以防止它消失。应将其用于将映射到用户空间的所有folio分配,hugetlbfs除外,以及直接使用folio_alloc_mpol()更合适的情况除外。

返回

成功时返回folio,如果分配失败则返回NULL。

struct page *alloc_pages(gfp_t gfp, unsigned int order)

分配页。

参数

gfp_t gfp

GFP标志。

unsigned int order

要分配的页数的2的幂。

描述

分配1 << order个连续页。第一个页的物理地址是自然对齐的(例如,3阶分配将对齐到8 * PAGE_SIZE字节的倍数)。在进程上下文中,将遵循当前进程的NUMA策略。

上下文

只要使用适当的GFP标志,就可以从任何上下文调用。

返回

成功时返回页,如果分配失败则返回NULL。

int mpol_misplaced(struct folio *folio, struct vm_fault *vmf, unsigned long addr)

检查当前folio节点在策略中是否有效

参数

struct folio *folio

要检查的folio

struct vm_fault *vmf

描述错误的结构体

unsigned long addr

用于共享策略查找和交错策略的vma中的虚拟地址

描述

查找vma,addr的当前策略节点ID,并与folio的节点ID“比较”。策略确定“模仿”alloc_page_vma()。从我们知道vma和错误地址的错误路径中调用。

返回

如果页位于对该策略有效的节点中,则返回NUMA_NO_NODE;否则,返回可用于分配替代folio的合适节点ID。

void mpol_shared_policy_init(struct shared_policy *sp, struct mempolicy *mpol)

初始化inode的共享策略

参数

struct shared_policy *sp

指向inode共享策略的指针

struct mempolicy *mpol

要安装的struct mempolicy

描述

在inode的共享策略rb-tree中安装非NULL的mpol。进入时,当前任务对非NULL的mpol具有引用。退出时必须释放此引用。这在get_inode()调用时调用,我们可以使用GFP_KERNEL。

int mpol_parse_str(char *str, struct mempolicy **mpol)

解析字符串到内存策略,用于tmpfs mpol挂载选项。

参数

char *str

包含要解析的内存策略的字符串

struct mempolicy **mpol

指向struct mempolicy指针的指针,成功时返回。

描述

输入格式

<mode>[=<flags>][:<nodelist>]

返回

成功时返回0,否则返回1

void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)

格式化内存策略结构体以供打印

参数

char *buffer

用于存放格式化后的内存策略字符串

int maxlen

buffer的长度

struct mempolicy *pol

指向要格式化的内存策略的指针

描述

pol转换为字符串。如果buffer太短,则截断字符串。建议maxlen至少为51,以便容纳最长的模式“weighted interleave”加上最长的标志“relative|balancing”,并显示至少几个节点ID。

struct folio

表示一组连续的字节。

定义:

struct folio {
    unsigned long flags;
    union {
        struct list_head lru;
        unsigned int mlock_count;
        struct dev_pagemap *pgmap;
    };
    struct address_space *mapping;
    union {
        pgoff_t index;
        unsigned long share;
    };
    union {
        void *private;
        swp_entry_t swap;
    };
    atomic_t _mapcount;
    atomic_t _refcount;
#ifdef CONFIG_MEMCG;
    unsigned long memcg_data;
#elif defined(CONFIG_SLAB_OBJ_EXT);
    unsigned long _unused_slab_obj_exts;
#endif;
#if defined(WANT_PAGE_VIRTUAL);
    void *virtual;
#endif;
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS;
    int _last_cpupid;
#endif;
    atomic_t _large_mapcount;
    atomic_t _nr_pages_mapped;
#ifdef CONFIG_64BIT;
    atomic_t _entire_mapcount;
    atomic_t _pincount;
#endif ;
    mm_id_mapcount_t _mm_id_mapcount[2];
    union {
        mm_id_t _mm_id[2];
        unsigned long _mm_ids;
    };
#ifdef NR_PAGES_IN_LARGE_FOLIO;
    unsigned int _nr_pages;
#endif ;
    struct list_head _deferred_list;
#ifndef CONFIG_64BIT;
    atomic_t _entire_mapcount;
    atomic_t _pincount;
#endif ;
    void *_hugetlb_subpool;
    void *_hugetlb_cgroup;
    void *_hugetlb_cgroup_rsvd;
    void *_hugetlb_hwpoison;
};

成员

flags

与页标志相同。

{匿名联合体}

匿名

lru

最近最少使用列表;跟踪此folio最近的使用情况。

mlock_count

此folio被mlock()锁定的次数。

pgmap

ZONE_DEVICE映射的元数据

映射

此页所属的文件,或指向匿名内存的anon_vma。

{匿名联合体}

匿名

index

文件内的偏移量,以页为单位。对于匿名内存,这是mmap起始处的索引。

share

引用此folio的DAX映射数量。参见dax_associate_entry。

{匿名联合体}

匿名

private

文件系统每folio数据(参见folio_attach_private())。

swap

如果folio_test_swapcache(),则用于swp_entry_t。

_mapcount

不要直接访问此成员。使用folio_mapcount()来查找此folio被用户空间映射的次数。

_refcount

不要直接访问此成员。使用folio_ref_count()来查找此folio的引用数量。

memcg_data

内存控制组数据。

_unused_slab_obj_exts

用于匹配struct slab中obj_exts的占位符。

virtual

内核直接映射中的虚拟地址。

_last_cpupid

最后访问此folio的CPU和进程ID。

_large_mapcount

不要直接使用,请调用folio_mapcount()

_nr_pages_mapped

不要在rmap和调试代码之外使用。

_entire_mapcount

不要直接使用,请调用folio_entire_mapcount()。

_pincount

不要直接使用,请调用folio_maybe_dma_pinned()

_mm_id_mapcount

不要在rmap代码之外使用。

{匿名联合体}

匿名

_mm_id

不要在rmap代码之外使用。

_mm_ids

不要在rmap代码之外使用。

_nr_pages

不要直接使用,请调用folio_nr_pages()

_deferred_list

内存压力下待拆分的folios。

_entire_mapcount

不要直接使用,请调用folio_entire_mapcount()。

_pincount

不要直接使用,请调用folio_maybe_dma_pinned()

_hugetlb_subpool

不要直接使用,请使用hugetlb.h中的访问器。

_hugetlb_cgroup

不要直接使用,请使用hugetlb_cgroup.h中的访问器。

_hugetlb_cgroup_rsvd

不要直接使用,请使用hugetlb_cgroup.h中的访问器。

_hugetlb_hwpoison

不要直接使用,请调用raw_hwp_list_head()。

描述

Folio是物理上、虚拟上和逻辑上连续的字节集。它的大小是2的幂,并且与相同的2的幂对齐。它至少与PAGE_SIZE一样大。如果它在页缓存中,则其文件偏移量是2的幂的倍数。它可能以任意页偏移量映射到用户空间,但其内核虚拟地址与其大小对齐。

struct ptdesc

页表的内存描述符。

定义:

struct ptdesc {
    unsigned long __page_flags;
    union {
        struct rcu_head pt_rcu_head;
        struct list_head pt_list;
        struct {
            unsigned long _pt_pad_1;
            pgtable_t pmd_huge_pte;
        };
    };
    unsigned long __page_mapping;
    union {
        pgoff_t pt_index;
        struct mm_struct *pt_mm;
        atomic_t pt_frag_refcount;
#ifdef CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING;
        atomic_t pt_share_count;
#endif;
    };
    union {
        unsigned long _pt_pad_2;
#if ALLOC_SPLIT_PTLOCKS;
        spinlock_t *ptl;
#else;
        spinlock_t ptl;
#endif;
    };
    unsigned int __page_type;
    atomic_t __page_refcount;
#ifdef CONFIG_MEMCG;
    unsigned long pt_memcg_data;
#endif;
};

成员

__page_flags

与页标志相同。仅限PowerPC。

{匿名联合体}

匿名

pt_rcu_head

用于释放页表页。

pt_list

已使用页表的列表。用于s390 gmap影子页(未链接到用户页表)和x86 pgds。

{匿名结构体}

匿名

_pt_pad_1

与页的复合头别名的填充。

pmd_huge_pte

受ptdesc->ptl保护,用于THPs。

__page_mapping

与page->mapping别名。未用于页表。

{匿名联合体}

匿名

pt_index

用于s390 gmap。

pt_mm

用于x86 pgds。

pt_frag_refcount

用于碎片化页表跟踪。仅限PowerPC。

pt_share_count

用于HugeTLB PMD页表共享计数。

{匿名联合体}

匿名

_pt_pad_2

确保正确对齐的填充。

ptl

页表的锁。

ptl

页表的锁。

__page_type

与page->page_type相同。未用于页表。

__page_refcount

与页引用计数相同。

pt_memcg_data

Memcg数据。在此跟踪页表。

描述

此结构体目前覆盖了struct page。在不充分理解问题的情况下,请勿修改。

type vm_fault_t

页错误处理程序的返回类型。

描述

页错误处理程序返回VM_FAULT值的位掩码。

enum vm_fault_reason

页错误处理程序返回这些值的位掩码,以告知核心VM在处理错误时发生了什么。用于决定进程是否收到SIGBUS或仅增加主要/次要错误计数器。

常量

VM_FAULT_OOM

内存不足

VM_FAULT_SIGBUS

错误访问

VM_FAULT_MAJOR

页从存储中读取

VM_FAULT_HWPOISON

遇到被污染的小页

VM_FAULT_HWPOISON_LARGE

遇到被污染的大页。索引编码在高位

VM_FAULT_SIGSEGV

段错误

VM_FAULT_NOPAGE

->fault安装了PTE,未返回页

VM_FAULT_LOCKED

->fault锁定了返回的页

VM_FAULT_RETRY

->fault被阻塞,必须重试

VM_FAULT_FALLBACK

大页错误失败,回退到小页

VM_FAULT_DONE_COW

->fault已完全处理COW

VM_FAULT_NEEDDSYNC

->fault未修改页表,需要fsync()完成(用于DAX中的同步页错误)

VM_FAULT_COMPLETED

->fault完成,同时mmap锁已释放

VM_FAULT_HINDEX_MASK

掩码HINDEX值

enum fault_flag

错误标志定义。

常量

FAULT_FLAG_WRITE

错误是写错误。

FAULT_FLAG_MKWRITE

错误是对现有PTE的mkwrite操作。

FAULT_FLAG_ALLOW_RETRY

如果被阻塞,允许重试错误。

FAULT_FLAG_RETRY_NOWAIT

重试时不要释放mmap_lock并等待。

FAULT_FLAG_KILLABLE

错误任务处于SIGKILL可终止区域。

FAULT_FLAG_TRIED

错误已尝试过一次。

FAULT_FLAG_USER

错误源于用户空间。

FAULT_FLAG_REMOTE

错误不针对当前任务/mm。

FAULT_FLAG_INSTRUCTION

错误发生在指令获取期间。

FAULT_FLAG_INTERRUPTIBLE

错误可被非致命信号中断。

FAULT_FLAG_UNSHARE

错误是一个取消共享请求,用于在COW映射中打破COW,确保错误后映射一个专用的匿名页。

FAULT_FLAG_ORIG_PTE_VALID

错误是否缓存了vmf->orig_pte。只有当此标志设置时,我们才应访问orig_pte。

FAULT_FLAG_VMA_LOCK

错误在VMA锁下处理。

描述

关于FAULT_FLAG_ALLOW_RETRYFAULT_FLAG_TRIED:我们可以通过正确设置这两个错误标志来指定是否允许页错误重试。目前有三种合法组合:

  1. ALLOW_RETRY和!TRIED:这表示页错误允许重试,并且

    这是第一次尝试

  2. ALLOW_RETRY和TRIED:这表示页错误允许重试,并且

    我们已至少尝试过一次

  3. !ALLOW_RETRY和!TRIED:这表示页错误不允许重试

未列出的组合(!ALLOW_RETRY && TRIED)是非法的,不应使用。请注意,页错误可以被允许重试多次,在这种情况下,我们将有一个带标志(a)的初始错误,然后是带标志(b)的连续错误。我们应始终在重试前尝试检测待处理信号,以确保必要时连续页错误仍可被中断。

组合FAULT_FLAG_WRITE|FAULT_FLAG_UNSHARE是非法的。当应用于非COW映射时,FAULT_FLAG_UNSHARE将被忽略并被视为普通的读错误。

int folio_is_file_lru(const struct folio *folio)

此folio应该在文件LRU还是匿名LRU上?

参数

struct folio *folio

要测试的folio。

描述

我们希望在没有页标志的情况下获取此信息,但状态需要一直持续到folio最后从LRU中删除,这可能远至__page_cache_release。

返回

一个整数(不是布尔值!),用于将folio排序到正确的LRU列表并正确统计folios。如果folio是常规文件系统支持的页缓存folio或延迟释放的匿名folio(例如通过MADV_FREE),则为1。如果folio是普通匿名folio、tmpfs folio或其他RAM或交换支持的folio,则为0。

void __folio_clear_lru_flags(struct folio *folio)

在释放页之前清除页LRU标志。

参数

struct folio *folio

已在LRU上且现在引用计数为零的folio。

enum lru_list folio_lru_list(struct folio *folio)

folio应该在哪个LRU列表上?

参数

struct folio *folio

要测试的folio。

返回

folio应该所在的LRU列表,作为LRU列表数组的索引。

page_folio

page_folio (p)

将页转换为folio。

参数

p

该页。

描述

每个页都是一个folio的一部分。此函数不能在NULL指针上调用。

上下文

page不需要引用或锁。如果调用者未持有引用,此调用可能与folio拆分发生竞争,因此在获得folio的引用后,应重新检查folio是否仍包含此页。

返回

包含此页的folio。

folio_page

folio_page (folio, n)

从folio返回一个页。

参数

folio

该 folio。

n

要返回的页号。

描述

n是相对于folio起始位置的。此函数不检查页号是否在folio内;假定调用者持有该页的引用。

bool folio_xor_flags_has_waiters(struct folio *folio, unsigned long mask)

更改一些folio标志。

参数

struct folio *folio

该 folio。

unsigned long mask

此字中设置的位将被更改。

描述

这只能用于在持有folio锁时更改的标志。例如,它不能安全地用于PG_dirty,因为PG_dirty可以在不持有folio锁的情况下设置。它也只能用于0-6范围内的标志,因为某些实现只影响这些位。

返回

是否有任务在等待此folio。

bool folio_test_uptodate(const struct folio *folio)

此folio是否最新?

参数

const struct folio *folio

该 folio。

描述

当folio中的每个字节都至少与存储中对应的字节一样新时,folio上的uptodate标志被设置。匿名和CoW folio始终是最新状态。如果folio不是最新状态,其中的一些字节可能仍是最新状态;请参见is_partially_uptodate() address_space操作。

bool folio_test_large(const struct folio *folio)

此folio是否包含多个页?

参数

const struct folio *folio

要测试的folio。

返回

如果folio大于一个页,则为True。

bool PageSlab(const struct page *page)

确定页是否属于slab分配器

参数

const struct page *page

要测试的页。

上下文

任何上下文。

返回

对于slab页为True,对于其他任何类型的页为False。

bool PageHuge(const struct page *page)

确定页是否属于hugetlbfs

参数

const struct page *page

要测试的页。

上下文

任何上下文。

返回

对于hugetlbfs页为True,对于匿名页或属于其他文件系统的页为False。

int folio_has_private(const struct folio *folio)

确定folio是否有私有数据

参数

const struct folio *folio

要检查的folio

描述

确定folio是否有私有数据,表示应在其上调用释放例程。

bool fault_flag_allow_retry_first(enum fault_flag flags)

第一次检查ALLOW_RETRY

参数

enum fault_flag flags

错误标志。

描述

这主要用于我们希望在等待另一个条件改变时尽量避免长时间持有mmap_lock的地方,在这种情况下,我们可以在第一轮中礼貌地释放mmap_lock,以避免其他进程可能出现的饥饿问题,这些进程也可能需要mmap_lock。

返回

如果页错误允许重试且这是错误处理的第一次尝试,则为true;否则为false。

unsigned int folio_order(const struct folio *folio)

folio的分配阶。

参数

const struct folio *folio

该 folio。

描述

一个folio由2^order个页组成。有关order的定义,请参见get_order()。

返回

folio的阶。

void folio_reset_order(struct folio *folio)

重置folio的阶和派生的_nr_pages

参数

struct folio *folio

该 folio。

描述

将阶和派生的_nr_pages重置为0。只能在拆分大型folios的过程中使用。

int folio_mapcount(const struct folio *folio)

此folio的映射数量。

参数

const struct folio *folio

该 folio。

描述

folio映射计数对应于引用folio任何部分的现有用户页表条目数量。每个此类现有用户页表条目必须与一个folio引用精确配对。

对于普通folios,每个用户页表条目(PTE/PMD/PUD/...)只计数一次。

对于hugetlb folios,每个引用整个folio的抽象“hugetlb”用户页表条目只计数一次,即使这些特殊页表条目由多个普通页表条目组成。

对于无法映射到用户空间的页,如slab、页表等,将报告0。

返回

此folio被映射的次数。

bool folio_mapped(const struct folio *folio)

此folio是否映射到用户空间?

参数

const struct folio *folio

该 folio。

返回

如果此folio中的任何页被用户页表引用,则为True。

unsigned int thp_order(struct page *page)

透明大页的阶。

参数

struct page *page

透明大页的头页。

unsigned long thp_size(struct page *page)

透明大页的大小。

参数

struct page *page

透明大页的头页。

返回

此页中的字节数。

void folio_get(struct folio *folio)

增加folio的引用计数。

参数

struct folio *folio

该 folio。

上下文

只要您知道自己持有folio的引用计数,就可以在任何上下文调用此函数。如果您尚未持有引用,folio_try_get()可能是适合您使用的接口。

void folio_put(struct folio *folio)

减少folio的引用计数。

参数

struct folio *folio

该 folio。

描述

如果folio的引用计数达到零,内存将被释放回页分配器,并可能立即被另一个分配使用。在调用folio_put()之后,请勿访问内存或struct folio,除非您能确定它不是最后一个引用。

上下文

可以在进程或中断上下文中调用,但不能在NMI上下文中调用。可以在持有自旋锁时调用。

void folio_put_refs(struct folio *folio, int refs)

减少folio的引用计数。

参数

struct folio *folio

该 folio。

int refs

从folio引用计数中减去的量。

描述

如果folio的引用计数达到零,内存将被释放回页分配器,并可能立即被另一个分配使用。在调用folio_put_refs()之后,请勿访问内存或struct folio,除非您能确定这些不是最后一个引用。

上下文

可以在进程或中断上下文中调用,但不能在NMI上下文中调用。可以在持有自旋锁时调用。

void folios_put(struct folio_batch *folios)

减少一组folios的引用计数。

参数

struct folio_batch *folios

folios。

描述

类似于folio_put(),但适用于一组folios。这比您自己编写循环更高效,因为它会优化在folios被释放时需要获取的锁。folios批次返回时为空,并准备好用于另一批;无需重新初始化。

上下文

可以在进程或中断上下文中调用,但不能在NMI上下文中调用。可以在持有自旋锁时调用。

unsigned long folio_pfn(const struct folio *folio)

返回folio的页帧号。

参数

const struct folio *folio

该 folio。

描述

一个folio可能包含多个页。这些页具有连续的页帧号。

返回

folio中第一个页的页帧号。

pte_t folio_mk_pte(struct folio *folio, pgprot_t pgprot)

为此folio创建一个PTE

参数

struct folio *folio

要为其创建PTE的folio

pgprot_t pgprot

要使用的页保护位

描述

为此folio的第一个页创建一个页表条目。这适合传递给set_ptes()。

返回

适合映射此folio的页表条目。

pmd_t folio_mk_pmd(struct folio *folio, pgprot_t pgprot)

为此folio创建一个PMD

参数

struct folio *folio

要为其创建PMD的folio

pgprot_t pgprot

要使用的页保护位

描述

为此folio的第一个页创建一个页表条目。这适合传递给set_pmd_at()。

返回

适合映射此folio的页表条目。

bool folio_maybe_dma_pinned(struct folio *folio)

报告folio是否可能被DMA锁定。

参数

struct folio *folio

该 folio。

描述

此函数检查folio是否已通过调用pin_user_pages()系列函数进行锁定。

对于小型folios,返回值为部分模糊:false不模糊,因为它表示“明确未被DMA锁定”,但true表示“可能被DMA锁定,但也可能是误报,因为它具有至少GUP_PIN_COUNTING_BIAS的普通folio引用”。

误报是可以的,因为:a) folio不太可能获得那么多引用计数,b) 此例程的所有调用者都应该能够优雅地处理误报。

对于大多数大型folios,结果将是完全正确的。这是因为我们有更多可用的跟踪数据:使用_pincount字段而不是GUP_PIN_COUNTING_BIAS方案。

更多信息,请参见pin_user_pages()及其相关调用

返回

如果folio可能已被“dma-pinned”,则为True。如果folio明确未被dma-pinned,则为False。

bool is_zero_page(const struct page *page)

查询页是否为零页

参数

const struct page *page

要查询的页

描述

如果page是永久零页之一,则返回true。

bool is_zero_folio(const struct folio *folio)

查询folio是否为零页

参数

const struct folio *folio

要查询的folio

描述

如果folio是永久零页之一,则返回true。

long folio_nr_pages(const struct folio *folio)

folio中的页数。

参数

const struct folio *folio

该 folio。

返回

一个正的2的幂。

struct folio *folio_next(struct folio *folio)

移动到下一个物理folio。

参数

struct folio *folio

我们当前正在操作的folio。

描述

如果您有物理连续内存,它可能跨越多个folio(例如struct bio_vec),请使用此函数从一个folio移动到下一个。如果内存仅是虚拟连续的,请勿使用此函数,因为folios几乎肯定不相邻。这相当于写入page++的folio版本。

上下文

我们假设folios在更高层已被引用计数和/或锁定,并且不调整引用计数。

返回

下一个struct folio

unsigned int folio_shift(const struct folio *folio)

此folio描述的内存大小。

参数

const struct folio *folio

该 folio。

描述

一个folio表示大小为2的幂的字节数。此函数告诉您folio是2的哪次幂。另请参见folio_size()folio_order()

上下文

调用者应持有folio的引用,以防止它被拆分。folio不需要被锁定。

返回

此folio大小的以2为底的对数。

size_t folio_size(const struct folio *folio)

folio中的字节数。

参数

const struct folio *folio

该 folio。

上下文

调用者应持有folio的引用,以防止它被拆分。folio不需要被锁定。

返回

此folio中的字节数。

bool folio_maybe_mapped_shared(struct folio *folio)

folio是否映射到多个MM的页表中

参数

struct folio *folio

该 folio。

描述

此函数检查folio当前是否可能映射到多个MM(“可能共享映射”),或者folio是否确定映射到单个MM(“独占映射”)。

对于KSM folios,当一个folio多次映射到同一个MM时,此函数也返回“共享映射”,因为单个页映射是独立的。

对于小型匿名folios和匿名hugetlb folios,返回值将完全正确:非KSM folios最多只能映射到MM一次,并且不能部分映射。KSM folios即使多次映射到同一个MM也被视为共享。

对于其他folios,结果可能模糊。
  1. 对于部分可映射的大folios (THP),如果一个folio在某个时间点被两个以上MM映射,返回值可能会错误地指示“共享映射”(误报)。

  2. 对于页缓存folios(包括hugetlb),当同一个MM中的两个VMA覆盖相同的文件范围时,返回值可能会错误地指示“共享映射”(误报)。

此外,此函数只考虑使用folio映射计数跟踪的当前页表映射。

此函数不考虑:
  1. folio是否可能在(不久的)将来被映射(例如,交换缓存、页缓存、迁移的临时取消映射)。

  2. folio是否以不同方式映射(VM_PFNMAP)。

  3. hugetlb页表共享是否适用。调用者可能需要检查hugetlb_pmd_shared()。

返回

folio是否估计映射到多个MM中。

int folio_expected_ref_count(const struct folio *folio)

计算预期的folio引用计数

参数

const struct folio *folio

该folio

描述

计算预期的folio引用计数,考虑来自页缓存、交换缓存、PG_private和页表映射的引用。与folio_ref_count()结合使用时,可用于检测意外引用(例如,GUP或其他临时引用)。

目前不考虑来自LRU缓存的引用。如果folio是从LRU中隔离的(迁移或拆分时就是这种情况),则LRU缓存不适用。

在未映射的folio上调用此函数——!folio_mapped()——并且该folio被锁定时,将返回稳定结果。

在映射的folio上调用此函数不会产生稳定结果,因为没有任何东西能阻止额外的页表映射出现(例如fork())或消失(例如munmap())。

在没有folio锁的情况下调用此函数也不会产生稳定结果:例如,folio可能同时从交换缓存中移除。

然而,即使在没有folio锁或在映射的folio上调用,此函数仍可用于及早检测意外引用(例如,如果锁定folio并取消映射有意义)。

调用者必须将自己可能持有的任何引用(例如,来自folio_try_get())添加到结果中。

返回预期的folio引用计数。

struct ptdesc *pagetable_alloc(gfp_t gfp, unsigned int order)

分配页表

参数

gfp_t gfp

GFP标志

unsigned int order

期望的页表阶

描述

pagetable_alloc分配页表内存以及描述该内存的页表描述符。

返回

描述已分配页表的ptdesc。

void pagetable_free(struct ptdesc *pt)

释放页表

参数

struct ptdesc *pt

页表描述符

描述

pagetable_free释放页表描述符描述的所有页表内存以及描述符本身的内存。

struct vm_area_struct *vma_lookup(struct mm_struct *mm, unsigned long addr)

在特定地址查找VMA

参数

struct mm_struct *mm

进程地址空间。

unsigned long addr

用户地址。

返回

在给定地址处的vm_area_struct,否则为NULL

bool vma_is_special_huge(const struct vm_area_struct *vma)

透明大页页表条目是否被视为特殊?

参数

const struct vm_area_struct *vma

指向要考虑的struct vm_area_struct的指针

描述

透明大页页表条目是否根据vm_normal_page()中的定义被视为“特殊”。

返回

如果透明大页页表条目应被视为特殊则为true,否则为false。

int folio_ref_count(const struct folio *folio)

此folio的引用计数。

参数

const struct folio *folio

该 folio。

描述

引用计数通常通过调用folio_get()增加,通过调用folio_put()减少。folio引用计数的一些典型使用者:

  • 页表中的每个引用

  • 页缓存

  • 文件系统私有数据

  • LRU列表

  • 管道

  • 引用进程地址空间中此页的直接I/O

返回

此folio的引用数量。

bool folio_try_get(struct folio *folio)

尝试增加folio的引用计数。

参数

struct folio *folio

该 folio。

描述

如果您尚未持有folio的引用,可以使用此函数尝试获取一个。它可能会失败,例如,如果自从您找到指向它的指针后folio已被释放,或者它为了拆分或迁移目的而被冻结。

返回

如果引用计数成功增加,则为True。

int is_highmem(struct zone *zone)

一个辅助函数,用于快速检查struct zone是否为高内存区域。这旨在将通用代码中对ZONE_{DMA/NORMAL/HIGHMEM/etc}的引用保持在最低限度。

参数

struct zone *zone

指向struct zone变量的指针

返回

对于高内存区域为1,否则为0

for_each_online_pgdat

for_each_online_pgdat (pgdat)

遍历所有在线节点的辅助宏

参数

pgdat

指向pg_data_t变量的指针

for_each_zone

for_each_zone (zone)

遍历所有内存区域的辅助宏

参数

zone

指向struct zone变量的指针

描述

用户只需声明zone变量,for_each_zone会填充它。

struct zoneref *next_zones_zonelist(struct zoneref *z, enum zone_type highest_zoneidx, nodemask_t *nodes)

使用zonelist中的游标作为起始点,返回允许节点掩码内最高highest_zoneidx处或其以下的下一个区域

参数

struct zoneref *z

用作搜索起始点的游标

enum zone_type highest_zoneidx

要返回的最高区域的区域索引

nodemask_t *nodes

用于过滤zonelist的可选节点掩码

描述

此函数使用游标作为搜索的起始点,返回给定区域索引处或其以下、且在允许节点掩码内的下一个区域。返回的zoneref是一个游标,表示当前正在检查的区域。在再次调用next_zones_zonelist之前,应将其前进一个。

返回

使用zonelist中的游标作为起始点,返回允许节点掩码内最高highest_zoneidx处或其以下的下一个区域

struct zoneref *first_zones_zonelist(struct zonelist *zonelist, enum zone_type highest_zoneidx, nodemask_t *nodes)

在zonelist中允许的节点掩码内,返回最高highest_zoneidx处或其以下的第一个区域

参数

struct zonelist *zonelist

要搜索合适区域的zonelist

enum zone_type highest_zoneidx

要返回的最高区域的区域索引

nodemask_t *nodes

用于过滤zonelist的可选节点掩码

描述

此函数返回给定区域索引处或其以下、且在允许节点掩码内的第一个区域。返回的zoneref是一个游标,可以通过在调用next_zones_zonelist之前将其前进一个来遍历zonelist。

当未找到符合条件的区域时,zoneref->zone为NULL(zoneref本身从不为NULL)。这可能是真实情况,也可能是由于cpuset修改导致并发nodemask更新。

返回

找到的第一个合适区域的Zoneref指针

for_each_zone_zonelist_nodemask

for_each_zone_zonelist_nodemask (zone, z, zlist, highidx, nodemask)

遍历zonelist中给定区域索引处或其以下、且在节点掩码内的有效区域的辅助宏

参数

zone

迭代器中的当前区域

z

正在迭代的zonelist->_zonerefs中的当前指针

zlist

正在迭代的zonelist

highidx

要返回的最高区域的区域索引

nodemask

分配器允许的节点掩码

描述

此迭代器遍历给定区域索引处或其以下、且在给定节点掩码内的所有区域

for_each_zone_zonelist

for_each_zone_zonelist (zone, z, zlist, highidx)

遍历zonelist中给定区域索引处或其以下的有效区域的辅助宏

参数

zone

迭代器中的当前区域

z

正在迭代的zonelist->zones中的当前指针

zlist

正在迭代的zonelist

highidx

要返回的最高区域的区域索引

描述

此迭代器遍历给定区域索引处或其以下的所有区域。

int pfn_valid(unsigned long pfn)

检查PFN是否有有效的内存映射条目

参数

unsigned long pfn

要检查的页帧号

描述

检查PFN是否有所谓的struct page的有效内存映射条目。请注意,内存映射条目的可用性并不意味着该PFN处有实际可用的内存。struct page可能代表一个空洞或一个不可用的页帧。

返回

对于有内存映射条目的PFN为1,否则为0

struct address_space *folio_mapping(struct folio *folio)

找到此 folio 存储所在的映射。

参数

struct folio *folio

该 folio。

描述

对于页面缓存中的 folio,返回此页面所属的映射。交换缓存中的 folio 返回存储此页面的交换映射(这与存储数据的交换文件或交换设备的映射不同)。

您可以为不在交换缓存或页面缓存中的 folio 调用此函数,它将返回 NULL。

int __anon_vma_prepare(struct vm_area_struct *vma)

将 anon_vma 附加到内存区域

参数

struct vm_area_struct *vma

所讨论的内存区域

描述

这确保了“vma”描述的内存映射附加了一个“anon_vma”,以便我们可以将映射到其中的匿名页面与该 anon_vma 关联起来。

常见情况是我们已经有了一个,这由 anon_vma_prepare() 内联处理。但如果没有,我们需要找到一个相邻的映射,从中可以重用 anon_vma(当拆分 vma 的唯一原因是 mprotect() 时非常常见),或者我们分配一个新的。

匿名 vma 的分配非常微妙,因为我们可能在 folio_lock_anon_vma_read() 中乐观地查找了 anon_vma,这甚至可能在新分配的 vma 中触及 rwsem(它依赖 RCU 确保 anon_vma 实际上没有被销毁)。

因此,即使对于新的分配,我们也需要进行适当的 anon_vma 锁定。同时,我们不希望在已经存在 anon_vma 的常见情况下进行任何锁定。

unsigned long page_address_in_vma(const struct folio *folio, const struct page *page, const struct vm_area_struct *vma)

该页面在此 VMA 中的虚拟地址。

参数

const struct folio *folio

包含该页面的 folio。

const struct page *page

folio 中的页面。

const struct vm_area_struct *vma

需要知道地址的 VMA。

描述

计算此页面在指定 VMA 中的用户虚拟地址。调用者有责任检查该页面是否实际在 VMA 内。目前可能没有指向此页面的 PTE,但如果在此地址发生页面错误,则将访问此页面。

上下文

调用者应持有对 folio 的引用。调用者应持有锁(例如 i_mmap_lock 或 mmap_lock),以防止 VMA 被修改。

返回

此页面在 VMA 中对应的虚拟地址。

int folio_referenced(struct folio *folio, int is_locked, struct mem_cgroup *memcg, unsigned long *vm_flags)

测试 folio 是否被引用。

参数

struct folio *folio

要测试的folio。

int is_locked

调用者持有 folio 上的锁。

struct mem_cgroup *memcg

目标内存 cgroup

unsigned long *vm_flags

所有引用 folio 的 vma->vm_flags 的组合。

描述

快速测试并清除 folio 的所有映射的引用,

返回

引用 folio 的映射数量。如果函数因 rmap 锁竞争而中止,则返回 -1。

int mapping_wrprotect_range(struct address_space *mapping, pgoff_t pgoff, unsigned long pfn, unsigned long nr_pages)

写保护指定范围内的所有映射。

参数

struct address_space *mapping

应遍历其反向映射的映射。

pgoff_t pgoff

pfnmapping 中映射的页面偏移量。

unsigned long pfn

mapping 中在 pgoff 处映射的页面的 PFN。

unsigned long nr_pages

跨越的物理连续基本页面的数量。

描述

遍历反向映射,查找包含 mapping 中指定范围内页面共享映射的所有 VMA,并对它们进行写保护(即,更新页表以将映射标记为只读,以便在写入映射时出现写保护错误)。

pfn 值不必引用 folio,而是可以引用映射到用户空间的内核分配。因此,我们不要求页面映射到具有有效映射或索引字段的 folio,而是由调用者在 mappingpgoff 中指定这些。

返回

写保护的 PTE 数量,或错误。

int pfn_mkclean_range(unsigned long pfn, unsigned long nr_pages, pgoff_t pgoff, struct vm_area_struct *vma)

清理指定 vma 内共享映射中 [pfn, pfn + nr_pages) 范围在特定偏移量 (pgoff) 处映射的 PTE(包括 PMD)。由于干净的 PTE 也应该是只读的,因此也对它们进行写保护。

参数

unsigned long pfn

起始 pfn。

unsigned long nr_pages

pfn 开始的物理连续页面的数量。

pgoff_t pgoff

pfn 映射的页面偏移量。

struct vm_area_struct *vma

pfn 映射所在的 vma。

描述

返回已清理的 PTE(包括 PMD)的数量。

void folio_move_anon_rmap(struct folio *folio, struct vm_area_struct *vma)

将 folio 移动到我们的 anon_vma

参数

struct folio *folio

要移动到我们的 anon_vma 的 folio

struct vm_area_struct *vma

folio 所属的 vma

描述

当一个 folio 在 COW 事件后独占地属于一个进程时,该 folio 可以被移动到仅属于该进程的 anon_vma 中,这样 rmap 代码就不会搜索父进程或兄弟进程。

void __folio_set_anon(struct folio *folio, struct vm_area_struct *vma, unsigned long address, bool exclusive)

为 folio 设置新的匿名 rmap。

参数

struct folio *folio

要为其设置新匿名 rmap 的 folio。

struct vm_area_struct *vma

要将 folio 添加到的 VM 区域。

unsigned long address

映射的用户虚拟地址。

bool exclusive

folio 是否对进程独占。

void __page_check_anon_rmap(const struct folio *folio, const struct page *page, struct vm_area_struct *vma, unsigned long address)

匿名 rmap 添加的完整性检查

参数

const struct folio *folio

包含 page 的 folio。

const struct page *page

要检查映射的页面

struct vm_area_struct *vma

添加映射的 vm 区域

unsigned long address

映射的用户虚拟地址

void folio_add_anon_rmap_ptes(struct folio *folio, struct page *page, int nr_pages, struct vm_area_struct *vma, unsigned long address, rmap_t flags)

将 PTE 映射添加到匿名 folio 的页面范围

参数

struct folio *folio

要添加映射的 folio

struct page *page

要添加的第一个页面

int nr_pages

将要映射的页面数量

struct vm_area_struct *vma

添加映射的 vm 区域

unsigned long address

要映射的第一个页面的用户虚拟地址

rmap_t flags

rmap 标志

描述

folio 的页面范围由 [first_page, first_page + nr_pages) 定义

调用者需要持有页表锁,并且在 anon_vma 情况下,页面必须被锁定:以在设置后序列化映射和索引检查,并确保匿名 folio 不会竞争性地升级为 KSM folio(但 KSM folio 永远不会降级)。

void folio_add_anon_rmap_pmd(struct folio *folio, struct page *page, struct vm_area_struct *vma, unsigned long address, rmap_t flags)

将 PMD 映射添加到匿名 folio 的页面范围

参数

struct folio *folio

要添加映射的 folio

struct page *page

要添加的第一个页面

struct vm_area_struct *vma

添加映射的 vm 区域

unsigned long address

要映射的第一个页面的用户虚拟地址

rmap_t flags

rmap 标志

描述

folio 的页面范围由 [first_page, first_page + HPAGE_PMD_NR) 定义

调用者需要持有页表锁,并且在 anon_vma 情况下,页面必须被锁定:以在设置后序列化映射和索引检查。

void folio_add_new_anon_rmap(struct folio *folio, struct vm_area_struct *vma, unsigned long address, rmap_t flags)

将映射添加到新的匿名 folio。

参数

struct folio *folio

要添加映射的 folio。

struct vm_area_struct *vma

添加映射的 vm 区域

unsigned long address

映射的用户虚拟地址

rmap_t flags

rmap 标志

描述

类似于 folio_add_anon_rmap_*(),但只能在新(new)的 folio 上调用。这意味着可以绕过增量和测试。当 folio 独占时,除非两个线程并发映射它,否则不必锁定。但是,如果 folio 是共享的,则必须锁定。

如果 folio 可通过 PMD 映射,则将其计为 THP。

void folio_add_file_rmap_ptes(struct folio *folio, struct page *page, int nr_pages, struct vm_area_struct *vma)

将 PTE 映射添加到 folio 的页面范围

参数

struct folio *folio

要添加映射的 folio

struct page *page

要添加的第一个页面

int nr_pages

将使用 PTE 映射的页面数量

struct vm_area_struct *vma

添加映射的 vm 区域

描述

folio 的页面范围由 [page, page + nr_pages) 定义

调用者需要持有页表锁。

void folio_add_file_rmap_pmd(struct folio *folio, struct page *page, struct vm_area_struct *vma)

将 PMD 映射添加到 folio 的页面范围

参数

struct folio *folio

要添加映射的 folio

struct page *page

要添加的第一个页面

struct vm_area_struct *vma

添加映射的 vm 区域

描述

folio 的页面范围由 [page, page + HPAGE_PMD_NR) 定义

调用者需要持有页表锁。

void folio_add_file_rmap_pud(struct folio *folio, struct page *page, struct vm_area_struct *vma)

将 PUD 映射添加到 folio 的页面范围

参数

struct folio *folio

要添加映射的 folio

struct page *page

要添加的第一个页面

struct vm_area_struct *vma

添加映射的 vm 区域

描述

folio 的页面范围由 [page, page + HPAGE_PUD_NR) 定义

调用者需要持有页表锁。

void folio_remove_rmap_ptes(struct folio *folio, struct page *page, int nr_pages, struct vm_area_struct *vma)

从 folio 的页面范围中移除 PTE 映射

参数

struct folio *folio

要从中移除映射的 folio

struct page *page

要移除的第一个页面

int nr_pages

将从映射中移除的页面数量

struct vm_area_struct *vma

移除映射的 vm 区域

描述

folio 的页面范围由 [page, page + nr_pages) 定义

调用者需要持有页表锁。

void folio_remove_rmap_pmd(struct folio *folio, struct page *page, struct vm_area_struct *vma)

从 folio 的页面范围中移除 PMD 映射

参数

struct folio *folio

要从中移除映射的 folio

struct page *page

要移除的第一个页面

struct vm_area_struct *vma

移除映射的 vm 区域

描述

folio 的页面范围由 [page, page + HPAGE_PMD_NR) 定义

调用者需要持有页表锁。

void folio_remove_rmap_pud(struct folio *folio, struct page *page, struct vm_area_struct *vma)

从 folio 的页面范围中移除 PUD 映射

参数

struct folio *folio

要从中移除映射的 folio

struct page *page

要移除的第一个页面

struct vm_area_struct *vma

移除映射的 vm 区域

描述

folio 的页面范围由 [page, page + HPAGE_PUD_NR) 定义

调用者需要持有页表锁。

void try_to_unmap(struct folio *folio, enum ttu_flags flags)

尝试移除 folio 的所有页表映射。

参数

struct folio *folio

要解除映射的 folio。

enum ttu_flags flags

动作和标志

描述

尝试移除所有映射此 folio 的页表项。如果需要(使用 TTU_SYNC 防止记账竞争),调用者有责任检查 folio 是否仍被映射。

上下文

调用者必须持有 folio 锁。

void try_to_migrate(struct folio *folio, enum ttu_flags flags)

尝试将所有页表映射替换为交换条目

参数

struct folio *folio

要替换页表项的 folio

enum ttu_flags flags

动作和标志

描述

尝试移除所有映射此 folio 的页表项,并将其替换为特殊的交换条目。调用者必须持有 folio 锁。

struct page *make_device_exclusive(struct mm_struct *mm, unsigned long addr, void *owner, struct folio **foliop)

将页面标记为设备独占使用

参数

struct mm_struct *mm

相关目标进程的 mm_struct

unsigned long addr

要标记为独占设备访问的虚拟地址

void *owner

传递给 MMU_NOTIFY_EXCLUSIVE 范围通知器以允许过滤

struct folio **foliop

成功时 folio 指针将存储在此处。

描述

此函数查找给定地址处映射的页面,获取 folio 引用,锁定 folio 并用特殊的设备独占 PFN 交换条目替换 PTE,从而阻止通过进程页表进行访问。函数返回时 folio 已被锁定和引用。

发生故障时,在调用 MMU 通知器后,设备独占条目在 folio 锁下被原始 PTE 替换。

仅支持匿名非 hugetlb folio,并且 VMA 必须具有写权限,以便我们可以将匿名页面以可写方式引入,从而将其标记为独占。调用者必须以读模式持有 mmap_lock。

使用此函数编程设备访问的驱动程序必须在编程期间使用 mmu notifier 临界区来持有设备特定锁。编程完成后,它应该释放 folio 锁和引用,此后 CPU 对页面的访问将撤销独占访问。

注意

  1. 此函数始终操作映射单个页面的 PTE。PMD 大小的 THP 在将 addr 对应的单个 PTE 进行转换之前,首先被重新映射为由 PTE 映射。

  2. 虽然阻止了通过进程页表的并发访问,但未处理和不支持通过其他页面引用(例如,早期 GUP 调用)的并发访问。

  3. 设备独占条目被核心 mm 视为“干净”和“旧”。设备驱动程序在收到 MMU 通知器通知时必须更新 folio 状态。

返回

成功时指向已映射页面的指针,否则为负错误码。

void __rmap_walk_file(struct folio *folio, struct address_space *mapping, pgoff_t pgoff_start, unsigned long nr_pages, struct rmap_walk_control *rwc, bool locked)

遍历文件支持映射的反向映射,该页面在指定页面缓存对象中以指定偏移量映射。

参数

struct folio *folio

要遍历其映射的 folio,如果为 NULL,则会配置 rwc 中指定的回调,以便能够正确查找映射。

struct address_space *mapping

我们打算遍历其映射 VMA 的页面缓存对象。如果 folio 非 NULL,则此值应等于 folio_mapping(folio)。

pgoff_t pgoff_start

我们正在查找的页面在 mapping 中的偏移量。如果 folio 非 NULL,则此值应等于 folio_pgoff(folio)。

unsigned long nr_pages

映射所映射的页面数量。如果 folio 非 NULL,则此值应等于 folio_nr_pages(folio)。

struct rmap_walk_control *rwc

描述遍历应如何进行的反向映射遍历控制对象。

bool locked

mapping 是否已锁定?如果未锁定,我们获取锁。

int migrate_folio(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode)

简单 folio 迁移。

参数

struct address_space *mapping

包含 folio 的 address_space。

struct folio *dst

要将数据迁移到的 folio。

struct folio *src

包含当前数据的 folio。

enum migrate_mode mode

如何迁移页面。

描述

直接迁移单个 LRU folio 的通用逻辑,适用于没有私有数据的 folio。

folio 在进入和退出时都被锁定。

int buffer_migrate_folio(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode)

带有缓冲区的 folio 迁移函数。

参数

struct address_space *mapping

包含 src 的地址空间。

struct folio *dst

要迁移到的 folio。

struct folio *src

要从中迁移的 folio。

enum migrate_mode mode

如何迁移 folio。

描述

仅当底层文件系统保证不存在对 src 的其他引用时,才能使用此函数。例如,附加的缓冲区头仅在 folio 锁下访问。如果您的文件系统无法提供此保证,则 buffer_migrate_folio_norefs() 可能更合适。

返回

成功时为 0,失败时为负 errno。

int buffer_migrate_folio_norefs(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode)

带有缓冲区的 folio 迁移函数。

参数

struct address_space *mapping

包含 src 的地址空间。

struct folio *dst

要迁移到的 folio。

struct folio *src

要从中迁移的 folio。

enum migrate_mode mode

如何迁移 folio。

描述

类似于 buffer_migrate_folio(),但此变体更小心,并检查是否也没有缓冲区头引用。对于缓冲区头直接查找和引用的映射(例如块设备映射),此函数是正确的选择。

返回

成功时为 0,失败时为负 errno。

unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, vm_flags_t vm_flags, unsigned long pgoff, unsigned long *populate, struct list_head *uf)

在当前进程地址空间中执行用户态内存映射,长度为 len,保护位为 prot,mmap 标志为 flags(由此推断出 VMA 标志),以及要应用的任何附加 VMA 标志 vm_flags。如果这是文件支持映射,则文件在 file 中指定,页面偏移量通过 pgoff 指定。

参数

struct file *file

可选的 struct file 指针,描述要映射的文件(如果为文件支持映射)。

unsigned long addr

如果非零,则提示(或如果 flags 设置了 MAP_FIXED,则指定)执行此映射的地址。有关详细信息,请参阅 mmap(2)。必须按页面对齐。

unsigned long len

映射的长度。将按页面对齐,且大小必须至少为 1 页。

unsigned long prot

描述映射所需访问的保护位。有关详细信息,请参阅 mmap(2)。

unsigned long flags

指定如何执行映射的标志,有关详细信息,请参阅 mmap(2)。

vm_flags_t vm_flags

默认应设置的 VMA 标志,否则为 0。

unsigned long pgoff

文件支持时文件中的页面偏移量,否则应为 0。

unsigned long *populate

指向一个值,如果不需要填充范围,则将其设置为 0;如果需要,则设置为要填充的字节数。必须为非 NULL。有关在何种情况下发生范围填充的详细信息,请参阅 mmap(2)。

struct list_head *uf

一个可选的列表头指针,用于跟踪 userfaultfd 解映射事件(如果发生)。如果提供,则由调用者管理。

描述

此函数不对文件执行安全检查,并假设如果 uf 非 NULL,调用者已提供一个列表头来跟踪 userfaultfd uf 的解映射事件。

它还简单地通过设置 populate(必须为非 NULL)来指示是否需要内存填充,并期望调用者在适当时实际执行此任务。

此函数将调用特定于体系结构(如果提供且相关,则为文件系统特定)的逻辑,以确定在不是 MAP_FIXED 的情况下放置映射的最合适的未映射区域。

需要用户态 mmap() 行为的调用者应调用 vm_mmap(),该函数也导出供模块使用。

那些需要此行为但安全性检查较少、用户态故障文件描述符和填充行为,并且自己处理 mmap 写锁的调用者,应调用此函数。

请注意,如果发生适当的合并,返回的地址可能位于合并的 VMA 内,因此它不一定指定 VMA 的开始,而只指定一个长度为 len 字节的有效映射范围的开始,向下舍入到最接近的页面大小。

调用者必须写锁定 current->mm->mmap_lock。

返回

错误,或者已执行请求映射的地址。

struct vm_area_struct *find_vma_intersection(struct mm_struct *mm, unsigned long start_addr, unsigned long end_addr)

查找与给定区间相交的第一个 VMA。

参数

struct mm_struct *mm

进程地址空间。

unsigned long start_addr

包含的起始用户地址。

unsigned long end_addr

排他的结束用户地址。

返回

所提供范围内的第一个 VMA,否则为 NULL。假设 start_addr < end_addr。

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)

查找给定地址的 VMA,或下一个 VMA。

参数

struct mm_struct *mm

要检查的 mm_struct

unsigned long addr

地址

返回

与 addr 关联的 VMA,或下一个 VMA。如果 addr 或更高地址处没有 VMA,则可能返回 NULL

struct vm_area_struct *find_vma_prev(struct mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev)

查找给定地址的 VMA,或下一个 vma,并将 pprev 设置为前一个 VMA(如果有)。

参数

struct mm_struct *mm

要检查的 mm_struct

unsigned long addr

地址

struct vm_area_struct **pprev

要设置为前一个 VMA 的指针

描述

请注意,此处缺少 RCU 锁,因为使用了外部 mmap_lock()。

返回

addr 关联的 VMA,或下一个 vma。如果 addr 或更高地址处没有 vma,则可能返回 NULL

void __ref kmemleak_alloc(const void *ptr, size_t size, int min_count, gfp_t gfp)

注册新分配的对象

参数

const void *ptr

指向对象开头的指针

size_t size

对象大小

int min_count

对此对象的最小引用数。如果在内存扫描期间发现的引用数少于 min_count,则该对象将被报告为内存泄漏。如果 min_count 为 0,则该对象永远不会被报告为泄漏。如果 min_count 为 -1,则该对象将被忽略(不扫描且不报告为泄漏)

gfp_t gfp

kmalloc() 标志,用于 kmemleak 内部内存分配

描述

当分配新对象(内存块)时(kmem_cache_alloc, kmalloc 等),此函数从内核分配器调用。

void __ref kmemleak_alloc_percpu(const void __percpu *ptr, size_t size, gfp_t gfp)

注册新分配的 __percpu 对象

参数

const void __percpu *ptr

指向对象开头的 __percpu 指针

size_t size

对象大小

gfp_t gfp

用于 kmemleak 内部内存分配的标志

描述

当分配新对象(内存块)时(alloc_percpu),此函数从内核 percpu 分配器调用。

void __ref kmemleak_vmalloc(const struct vm_struct *area, size_t size, gfp_t gfp)

注册新 vmalloc 分配的对象

参数

const struct vm_struct *area

指向 vm_struct 的指针

size_t size

对象大小

gfp_t gfp

用于 kmemleak 内部内存分配的 __vmalloc() 标志

描述

当分配新对象(内存块)时,此函数从 vmalloc() 内核分配器调用。

void __ref kmemleak_free(const void *ptr)

注销之前注册的对象

参数

const void *ptr

指向对象开头的指针

描述

当释放对象(内存块)时(kmem_cache_free、kfree、vfree 等),此函数从内核分配器调用。

void __ref kmemleak_free_part(const void *ptr, size_t size)

部分注销之前注册的对象

参数

const void *ptr

指向对象开头或内部的指针。这也表示要释放的范围的开始

size_t size

要注销的大小

描述

当只释放内存块的一部分时(通常从 bootmem 分配器),此函数被调用。

void __ref kmemleak_free_percpu(const void __percpu *ptr)

注销之前注册的 __percpu 对象

参数

const void __percpu *ptr

指向对象开头的 __percpu 指针

描述

当释放对象(内存块)时(free_percpu),此函数从内核 percpu 分配器调用。

void __ref kmemleak_update_trace(const void *ptr)

更新对象分配堆栈跟踪

参数

const void *ptr

指向对象开头的指针

描述

对于实际分配位置并非总是有用的情况,覆盖对象分配堆栈跟踪。

void __ref kmemleak_not_leak(const void *ptr)

将已分配对象标记为假阳性

参数

const void *ptr

指向对象开头的指针

描述

对对象调用此函数将导致内存块不再被报告为泄漏,并且始终被扫描。

void __ref kmemleak_transient_leak(const void *ptr)

将已分配对象标记为瞬态假阳性

参数

const void *ptr

指向对象开头的指针

描述

对对象调用此函数将导致内存块暂时不被报告为泄漏。例如,如果对象是单链表的一部分并且指向它的 ->next 引用已更改,则可能会发生这种情况。

void __ref kmemleak_ignore_percpu(const void __percpu *ptr)

类似于 kmemleak_ignore,但接受 percpu 地址参数

参数

const void __percpu *ptr

对象的 percpu 地址

void __ref kmemleak_ignore(const void *ptr)

忽略已分配对象

参数

const void *ptr

指向对象开头的指针

描述

对对象调用此函数将导致内存块被忽略(不扫描且不报告为泄漏)。通常在已知对应块不是泄漏且不包含对其他已分配内存块的任何引用时执行此操作。

void __ref kmemleak_scan_area(const void *ptr, size_t size, gfp_t gfp)

限制已分配对象的扫描范围

参数

const void *ptr

指向对象开头或内部的指针。这也表示扫描区域的开始

size_t size

扫描区域的大小

gfp_t gfp

kmalloc() 标志,用于 kmemleak 内部内存分配

描述

当已知对象只有某些部分包含对其他对象的引用时,使用此函数。Kmemleak 将仅扫描这些区域,从而减少误报的数量。

void __ref kmemleak_no_scan(const void *ptr)

不扫描已分配对象

参数

const void *ptr

指向对象开头的指针

描述

此函数通知 kmemleak 不扫描给定的内存块。在已知给定对象不包含对其他对象的任何引用的情况下非常有用。Kmemleak 将不扫描此类对象,从而减少误报的数量。

void __ref kmemleak_alloc_phys(phys_addr_t phys, size_t size, gfp_t gfp)

类似于 kmemleak_alloc,但接受物理地址参数

参数

phys_addr_t phys

对象的物理地址

size_t size

对象大小

gfp_t gfp

kmalloc() 标志,用于 kmemleak 内部内存分配

void __ref kmemleak_free_part_phys(phys_addr_t phys, size_t size)

类似于 kmemleak_free_part,但接受物理地址参数

参数

phys_addr_t phys

如果是对象开头或内部的物理地址。这也表示要释放的范围的开始

size_t size

要注销的大小

void __ref kmemleak_ignore_phys(phys_addr_t phys)

类似于 kmemleak_ignore,但接受物理地址参数

参数

phys_addr_t phys

对象的物理地址

void *devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap)

为给定资源重新映射并提供内存映射支持

参数

struct device *dev

**res** 的宿主设备

struct dev_pagemap *pgmap

指向 `struct dev_pagemap` 的指针

注意

1/ 在将其传递给此函数之前,调用者必须至少初始化 **pgmap** 的 `range` 和 `type` 成员。

(此行已与 P5 合并以提高可读性)

描述

2/ `altmap` 字段可以可选地进行初始化,在这种情况下,必须在 `pgmap->flags` 中设置 `PGMAP_ALTMAP_VALID`。

(此行已与 P7 合并以提高可读性)

3/ `ref` 字段可选择提供,在这种情况下,`pgmap->ref` 在进入时必须是“活的”,并将在 `devm_memremap_pages_release()` 时或此例程失败时被终止和回收。

(此行已与 P9 合并以提高可读性)

4/ `range` 预期是一个可以合理地被视为“系统 RAM”范围的主机内存范围,即不是设备 mmio 范围,但这不是强制性的。

(此行已与 P11 合并以提高可读性)

struct dev_pagemap *get_dev_pagemap(unsigned long pfn, struct dev_pagemap *pgmap)

为 **pfn** 获取 `dev_pagemap` 的新活跃引用

参数

unsigned long pfn

要查找 `page_map` 的页帧号

struct dev_pagemap *pgmap

可选的已知 `pgmap`,已有一个引用

描述

如果 **pgmap** 为非 NULL 且涵盖 **pfn**,则它将按原样返回。如果 **pgmap** 为非 NULL 但不涵盖 **pfn**,则对其的引用将被释放。

unsigned long vma_kernel_pagesize(struct vm_area_struct *vma)

此 VMA 的页大小粒度。

参数

struct vm_area_struct *vma

用户映射。

描述

此 VMA 中的 Folio 将对齐到此函数返回的字节数,并且至少为此大小。

返回

支持 VMA 时分配的 Folio 的默认大小。

bool folio_isolate_hugetlb(struct folio *folio, struct list_head *list)

尝试隔离一个已分配的 hugetlb Folio

参数

struct folio *folio

要隔离的 Folio

struct list_head *list

成功时将 Folio 添加到的列表

描述

隔离一个已分配(引用计数 > 0)的 hugetlb Folio,将其标记为已隔离/不可迁移,并将其从活跃列表移动到给定列表。

如果 **folio** 不是已分配的 hugetlb Folio,或者它已被隔离/不可迁移,则隔离将失败。

成功时,将额外获取一个 Folio 引用,该引用必须使用 folio_putback_hugetlb() 释放以撤销隔离。

返回

如果隔离成功则返回 True,否则返回 False。

void folio_putback_hugetlb(struct folio *folio)

解除 hugetlb Folio 的隔离

参数

struct folio *folio

已隔离的 hugetlb Folio

描述

回存/解除使用 folio_isolate_hugetlb() 隔离的 hugetlb Folio:将其标记为非隔离/可迁移,并将其放回活跃列表。

将释放通过 folio_isolate_hugetlb() 获得的额外 Folio 引用。

void folio_mark_accessed(struct folio *folio)

将 Folio 标记为已发生活动。

参数

struct folio *folio

要标记的 Folio。

描述

此函数将执行以下转换之一

  • 非活跃、未引用 -> 非活跃、已引用

  • 非活跃、已引用 -> 活跃、未引用

  • 活跃、未引用 -> 活跃、已引用

当新分配的 Folio 尚未可见,因此对于非原子操作是安全的时,可以使用 `__folio_set_referenced()` 替代 folio_mark_accessed()

void folio_add_lru(struct folio *folio)

将 Folio 添加到 LRU 列表。

参数

struct folio *folio

要添加到 LRU 的 Folio。

描述

将 Folio 排队以添加到 LRU。是将页添加到[非]活跃[文件|匿名]列表的决定被推迟到 `folio_batch` 被排空。这使得 folio_add_lru() 的调用者有机会使用 folio_mark_accessed() 将 Folio 添加到活跃列表。

void folio_add_lru_vma(struct folio *folio, struct vm_area_struct *vma)

将 Folio 添加到此 VMA 的适当 LRU 列表。

参数

struct folio *folio

要添加到 LRU 的 Folio。

struct vm_area_struct *vma

Folio 被映射到的 VMA。

描述

如果 VMA 是 `mlocked` 的,**folio** 将被添加到不可回收列表。否则,它将以与 folio_add_lru() 相同的方式处理。

void deactivate_file_folio(struct folio *folio)

去激活一个文件 Folio。

参数

struct folio *folio

要去激活的 Folio。

描述

此函数向 VM 提示 **folio** 是一个很好的回收候选项,例如,如果其失效由于 Folio 脏污或正在回写而失败。

上下文

调用者持有 Folio 的引用。

void folio_mark_lazyfree(struct folio *folio)

使匿名 Folio 惰性释放

参数

struct folio *folio

要去激活的 Folio

描述

folio_mark_lazyfree() 将 **folio** 移动到非活跃文件列表。这样做是为了加速 **folio** 的回收。

void folios_put_refs(struct folio_batch *folios, unsigned int *refs)

减少一批 Folio 的引用计数。

参数

struct folio_batch *folios

folios。

unsigned int *refs

要从每个 Folio 的引用计数中减去的引用数。

描述

类似于 folio_put(),但用于一批 Folio。这比自己编写循环更高效,因为它会优化在 Folio 被释放时需要获取的锁。Folio 批处理返回时为空且可供重复使用;无需重新初始化它。如果 **refs** 为 NULL,我们将从每个 Folio 引用计数中减去一。

上下文

可以在进程或中断上下文中调用,但不能在NMI上下文中调用。可以在持有自旋锁时调用。

void release_pages(release_pages_arg arg, int nr)

批处理 `put_page()`

参数

release_pages_arg arg

要释放的页数组

int nr

要映射的页数

描述

减少 **arg** 中所有页的引用计数。如果降至零,则将页从 LRU 中移除并释放。

请注意,参数可以是页数组、编码页或 Folio 指针。我们忽略任何编码位,并将它们中的任何一个转换为一个 Folio,然后释放它。

void folio_batch_remove_exceptionals(struct folio_batch *fbatch)

从批处理中移除非 Folio。

参数

struct folio_batch *fbatch

要修剪的批处理

描述

`find_get_entries()` 用 Folio 和影子/交换/DAX 条目填充批处理。此函数从 **fbatch** 中移除所有非 Folio 条目而不留下空洞,以便它可以传递给仅处理 Folio 的批处理操作。

void zpool_register_driver(struct zpool_driver *driver)

注册一个 zpool 实现。

参数

struct zpool_driver *driver

要注册的驱动程序

int zpool_unregister_driver(struct zpool_driver *driver)

注销一个 zpool 实现。

参数

struct zpool_driver *driver

要注销的驱动程序。

描述

模块使用计数用于防止在使用/卸载驱动程序时/之后继续使用,因此如果从模块退出函数调用此函数,则不应失败;如果从模块退出函数之外调用,并且此函数返回失败,则驱动程序正在使用中,必须保持可用。

bool zpool_has_pool(char *type)

检查池驱动程序是否可用

参数

char *type

要检查的 zpool 类型(例如 `zsmalloc`)

描述

此函数检查 **type** 池驱动程序是否可用。如有需要,它将尝试加载请求的模块,但不能保证调用后模块仍会立即加载和可用。如果此函数返回 `true`,调用者应假定池可用,但必须准备好处理 zpool_create_pool() 返回失败。但是,如果此函数返回 `false`,则调用者应假定请求的池类型不可用;要么请求的池类型模块不存在,要么无法加载,并且使用此池类型调用 zpool_create_pool() 将失败。

**type** 字符串必须以空字符结尾。

返回

如果 **type** 池可用则为 true,否则为 false。

struct zpool *zpool_create_pool(const char *type, const char *name, gfp_t gfp)

创建一个新的 zpool

参数

const char *type

要创建的 zpool 类型(例如 `zsmalloc`)

const char *name

zpool 的名称(例如 `zram0`,`zswap`)

gfp_t gfp

分配池时使用的 GFP 标志。

描述

此函数创建指定类型的新 zpool。如果实现支持,将在分配内存时使用 GFP 标志。如果 `ops` 参数为 NULL,则创建的 zpool 将不可回收。

实现必须保证此函数是线程安全的。

**type** 和 **name** 字符串必须以空字符结尾。

返回

成功时返回新的 zpool,失败时返回 NULL。

void zpool_destroy_pool(struct zpool *zpool)

销毁一个 zpool

参数

struct zpool *zpool

要销毁的 zpool。

描述

实现必须保证此函数是线程安全的,但仅限于销毁不同的池。同一个池只能销毁一次,并且在销毁后不应再使用。

此函数销毁一个现有的 zpool。该 zpool 不应在使用中。

const char *zpool_get_type(struct zpool *zpool)

获取 zpool 的类型

参数

struct zpool *zpool

要检查的 zpool

描述

此函数返回池的类型。

实现必须保证此函数是线程安全的。

返回

zpool 的类型。

int zpool_malloc(struct zpool *zpool, size_t size, gfp_t gfp, unsigned long *handle, const int nid)

分配内存

参数

struct zpool *zpool

要从中分配内存的 zpool。

size_t size

要分配的内存量。

gfp_t gfp

分配内存时使用的 GFP 标志。

unsigned long *handle

指向要设置的句柄的指针

const int nid

首选的节点 ID。

描述

此函数从池中分配请求的内存量。如果实现支持,将在分配内存时使用 GFP 标志。提供的 **handle** 将设置为已分配对象的句柄。分配将优先使用 **nid** 指定的 NUMA 节点。

实现必须保证此函数是线程安全的。

返回

成功时返回 0,错误时返回负值。

void zpool_free(struct zpool *zpool, unsigned long handle)

释放之前分配的内存

参数

struct zpool *zpool

分配内存的 zpool。

unsigned long handle

要释放的内存句柄。

描述

此函数释放之前分配的内存。它不保证池会实际释放内存,只保证池中的内存将可供池使用。

实现必须保证此函数是线程安全的,但仅限于释放不同的句柄。同一个句柄只能释放一次,并且在释放后不应再使用。

void *zpool_obj_read_begin(struct zpool *zpool, unsigned long handle, void *local_copy)

开始从之前分配的句柄读取。

参数

struct zpool *zpool

分配句柄的 zpool

unsigned long handle

要读取的句柄

void *local_copy

如果需要,使用的本地缓冲区。

描述

此函数开始对先前分配的句柄进行读取操作。如果需要,可以通过复制内存到 **local_copy** 缓冲区来使用它。读取完成后,必须调用 zpool_obj_read_end() 来撤销任何已执行的操作(例如释放锁)。

返回

指向要读取的句柄内存的指针,如果使用了 **local_copy**,则返回的指针是 **local_copy**。

void zpool_obj_read_end(struct zpool *zpool, unsigned long handle, void *handle_mem)

结束从之前分配的句柄读取。

参数

struct zpool *zpool

分配句柄的 zpool

unsigned long handle

要读取的句柄

void *handle_mem

zpool_obj_read_begin() 返回的指针

描述

完成由 zpool_obj_read_begin() 之前启动的读取操作。

void zpool_obj_write(struct zpool *zpool, unsigned long handle, void *handle_mem, size_t mem_len)

写入之前分配的句柄。

参数

struct zpool *zpool

分配句柄的 zpool

unsigned long handle

要读取的句柄

void *handle_mem

要从其中复制到句柄的内存。

size_t mem_len

要写入的内存长度。

u64 zpool_get_total_pages(struct zpool *zpool)

池的总大小

参数

struct zpool *zpool

要检查的 zpool

描述

此函数返回池的总大小(以页为单位)。

返回

zpool 的总大小(以页为单位)。

struct cgroup_subsys_state *mem_cgroup_css_from_folio(struct folio *folio)

与 Folio 关联的 memcg 的 css

参数

struct folio *folio

感兴趣的 Folio

描述

如果 `memcg` 绑定到默认层级,则返回与 **folio** 关联的 `memcg` 的 `css`。返回的 `css` 在 **folio** 释放之前一直与它关联。

如果 `memcg` 绑定到传统层级,则返回 `root_mem_cgroup` 的 `css`。

ino_t page_cgroup_ino(struct page *page)

返回页所计费到的 memcg 的 inode 号

参数

struct page *page

该页

描述

查找页所计费到的内存 cgroup 的最接近的在线祖先,并返回其 inode 号;如果页未计费到任何 cgroup,则返回 0。调用此函数时不需要持有页的引用。

请注意,此函数本质上是存在竞态的,因为无法阻止 cgroup inode 在 page_cgroup_ino() 返回后立即被拆除并可能重新分配,因此它只能由不关心此问题的调用者(例如 procfs 接口)使用。

void mod_memcg_state(struct mem_cgroup *memcg, enum memcg_stat_item idx, int val)

更新 cgroup 内存统计信息

参数

struct mem_cgroup *memcg

内存 cgroup

enum memcg_stat_item idx

统计项 - 可以是 `enum memcg_stat_item` 或 `enum node_stat_item`

int val

要添加到计数器的增量,可以是负数

void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx, int val)

更新 lruvec 内存统计信息

参数

struct lruvec *lruvec

lruvec

enum node_stat_item idx

统计项

int val

要添加到计数器的增量,可以是负数

描述

lruvec 是 NUMA 节点和 cgroup 的交集。此函数更新受此级别状态更改影响的所有三个计数器:每节点、每 cgroup、每 lruvec。

void count_memcg_events(struct mem_cgroup *memcg, enum vm_event_item idx, unsigned long count)

在 cgroup 中统计 VM 事件

参数

struct mem_cgroup *memcg

内存 cgroup

enum vm_event_item idx

事件项

unsigned long count

发生的事件数量

struct mem_cgroup *get_mem_cgroup_from_mm(struct mm_struct *mm)

获取给定 `mm_struct` 的 memcg 引用。

参数

struct mm_struct *mm

从中提取 memcg 的 mm。它可以是 NULL。

描述

获取 `mm->memcg` 的引用并在成功时返回它。如果 mm 为 NULL,则 memcg 的选择如下:1) 如果设置了活跃 memcg,则选择活跃 memcg。2) 如果可用,则选择 `current->mm->memcg`。3) 根 memcg。如果 mem_cgroup 被禁用,则返回 NULL。

struct mem_cgroup *get_mem_cgroup_from_current(void)

获取当前任务的 memcg 引用。

参数

void

无参数

struct mem_cgroup *get_mem_cgroup_from_folio(struct folio *folio)

获取给定 Folio 的 memcg 引用。

参数

struct folio *folio

从中提取 memcg 的 Folio。

struct mem_cgroup *mem_cgroup_iter(struct mem_cgroup *root, struct mem_cgroup *prev, struct mem_cgroup_reclaim_cookie *reclaim)

遍历内存 cgroup 层级

参数

struct mem_cgroup *root

层级根

struct mem_cgroup *prev

之前返回的 memcg,首次调用时为 NULL

struct mem_cgroup_reclaim_cookie *reclaim

用于共享回收遍历的 cookie,完整遍历时为 NULL

描述

返回 **root** 下层级子代的引用,或 **root** 本身,或在完整往返后返回 `NULL`。

调用者必须在后续调用中将返回值作为 **prev** 传递以进行引用计数,或者在往返完成之前使用 mem_cgroup_iter_break() 取消层级遍历。

回收器可以在 **reclaim** 中指定一个节点,以将层级中的 memcg 分配给所有在同一节点上同时操作的回收器。

void mem_cgroup_iter_break(struct mem_cgroup *root, struct mem_cgroup *prev)

提前中止层级遍历

参数

struct mem_cgroup *root

层级根

struct mem_cgroup *prev

mem_cgroup_iter() 返回的最后访问的层级成员

void mem_cgroup_scan_tasks(struct mem_cgroup *memcg, int (*fn)(struct task_struct*, void*), void *arg)

遍历内存 cgroup 层级的任务

参数

struct mem_cgroup *memcg

层级根

int (*fn)(struct task_struct *, void *)

为每个任务调用的函数

void *arg

传递给 **fn** 的参数

描述

此函数遍历附加到 **memcg** 或其任何后代的任务,并为每个任务调用 **fn**。如果 **fn** 返回非零值,则函数中断迭代循环。否则,它将遍历所有任务并返回 0。

此函数不得为根内存 cgroup 调用。

struct lruvec *folio_lruvec_lock(struct folio *folio)

锁定 Folio 的 lruvec。

参数

struct folio *folio

指向 Folio 的指针。

描述

这些函数在以下任何条件下使用都是安全的:- Folio 已锁定 - `folio_test_lru` 为假 - Folio 已冻结(引用计数为 0)

返回

此 Folio 所在的 lruvec 及其锁处于持有状态。

struct lruvec *folio_lruvec_lock_irq(struct folio *folio)

锁定 Folio 的 lruvec。

参数

struct folio *folio

指向 Folio 的指针。

描述

这些函数在以下任何条件下使用都是安全的:- Folio 已锁定 - `folio_test_lru` 为假 - Folio 已冻结(引用计数为 0)

返回

此 Folio 所在的 lruvec 及其锁处于持有状态且中断已禁用。

struct lruvec *folio_lruvec_lock_irqsave(struct folio *folio, unsigned long *flags)

锁定 Folio 的 lruvec。

参数

struct folio *folio

指向 Folio 的指针。

unsigned long *flags

指向 `irqsave` 标志的指针。

描述

这些函数在以下任何条件下使用都是安全的:- Folio 已锁定 - `folio_test_lru` 为假 - Folio 已冻结(引用计数为 0)

返回

此 Folio 所在的 lruvec 及其锁处于持有状态且中断已禁用。

void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru, int zid, int nr_pages)

添加或移除 LRU 页的计数

参数

struct lruvec *lruvec

mem_cgroup 每区域 LRU 向量

enum lru_list lru

页所在的 LRU 列表索引

int zid

计费页的区域 ID

int nr_pages

添加时为正值,移除时为负值

描述

此函数必须在 `lru_lock` 下调用,恰好在将页添加到 LRU 列表之前或从 LRU 列表移除页之后。

unsigned long mem_cgroup_margin(struct mem_cgroup *memcg)

计算内存 cgroup 的可计费空间

参数

struct mem_cgroup *memcg

内存 cgroup

描述

返回 **mem** 可被计费的最大内存量,以页为单位。

void mem_cgroup_print_oom_context(struct mem_cgroup *memcg, struct task_struct *p)

打印与内存控制器相关的 OOM 信息。

参数

struct mem_cgroup *memcg

超出限制的内存 cgroup

struct task_struct *p

将被杀死的任务

注意

当层级启用时,**memcg** 和 **p** 的 `mem_cgroup` 可能不同

void mem_cgroup_print_oom_meminfo(struct mem_cgroup *memcg)

打印与内存控制器相关的 OOM 内存信息。

参数

struct mem_cgroup *memcg

超出限制的内存 cgroup

struct mem_cgroup *mem_cgroup_get_oom_group(struct task_struct *victim, struct mem_cgroup *oom_domain)

获取内存 cgroup 以在 OOM 后清理

参数

struct task_struct *victim

将被 OOM killer 杀死的任务

struct mem_cgroup *oom_domain

memcg OOM 情况下的 memcg,系统范围 OOM 情况下的 NULL

描述

返回指向内存 cgroup 的指针,该 cgroup 必须通过杀死所有属于它的可被 OOM 杀死的任务来清理。

调用者必须对返回的非 NULL `memcg` 调用 `mem_cgroup_put()`。

bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages)

尝试消耗此 CPU 上的库存电荷。

参数

struct mem_cgroup *memcg

要从中消耗的 memcg。

unsigned int nr_pages

要计费的页数。

描述

如果存在足够的 `nr_pages`,则消耗缓存的电荷,否则返回失败。如果计费请求大于 `MEMCG_CHARGE_BATCH` 或本地锁已被占用,则也返回失败。

成功时返回 true,否则返回 false。

int __memcg_kmem_charge_page(struct page *page, gfp_t gfp, int order)

对当前内存 cgroup 计费一个 kmem 页

参数

struct page *page

要计费的页

gfp_t gfp

回收模式

int order

分配阶

描述

成功时返回 0,失败时返回错误码。

void __memcg_kmem_uncharge_page(struct page *page, int order)

解除 kmem 页的计费

参数

struct page *page

要解除计费的页

int order

分配阶

void mem_cgroup_wb_stats(struct bdi_writeback *wb, unsigned long *pfilepages, unsigned long *pheadroom, unsigned long *pdirty, unsigned long *pwriteback)

从其 memcg 中检索与回写相关的统计信息

参数

struct bdi_writeback *wb

所查询的 `bdi_writeback`

unsigned long *pfilepages

文件页数的输出参数

unsigned long *pheadroom

根据 memcg 可分配页数的输出参数

unsigned long *pdirty

脏页数的输出参数

unsigned long *pwriteback

正在回写页数的输出参数

描述

确定 **wb** 的 `memcg` 中文件页、余量、脏页和回写页的数量。文件页、脏页和回写页不言自明。余量则稍微复杂一些。

一个 `memcg` 的余量是“min(最大值, 高阈值) - 已使用”。在层级中,余量计算为自身和祖先的最低余量。请注意,这不考虑系统中实际可用的内存量。调用者应相应地进一步限制 **\*pheadroom**。

struct mem_cgroup *mem_cgroup_from_id(unsigned short id)

从 memcg ID 查找 memcg

参数

unsigned short id

要查找的 memcg ID

描述

调用者必须持有 rcu_read_lock()

void mem_cgroup_css_reset(struct cgroup_subsys_state *css)

重置 mem_cgroup 的状态

参数

struct cgroup_subsys_state *css

目标 css

描述

重置与 **css** 关联的 `mem_cgroup` 的状态。当用户空间请求禁用默认层级但 `memcg` 因依赖关系而被固定时,会调用此函数。`memcg` 应该停止应用策略,并恢复到原始状态,因为它可能会再次变得可见。

当前的实现只重置了基本配置。这需要扩展以覆盖所有可见部分。

void mem_cgroup_calculate_protection(struct mem_cgroup *root, struct mem_cgroup *memcg)

检查内存消耗是否在正常范围内

参数

struct mem_cgroup *root

正在检查的子树的顶层祖先

struct mem_cgroup *memcg

要检查的内存 cgroup

描述

警告:此函数并非无状态!它只能作为自上而下树迭代的一部分使用,不能用于独立查询。

(此行已与 P299 合并以提高可读性)

int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp)

对 hugetlb Folio 收费 memcg

参数

struct folio *folio

正在计费的 Folio

gfp_t gfp

回收模式

描述

此函数在分配大页 Folio 时调用,在页已获得并计费到适当的 hugetlb cgroup 控制器(如果已启用)之后。

如果 memcg 已满,返回 `ENOMEM`。如果计费成功或跳过计费,则返回 0。

int mem_cgroup_swapin_charge_folio(struct folio *folio, struct mm_struct *mm, gfp_t gfp, swp_entry_t entry)

为换入操作对新分配的 Folio 收费。

参数

struct folio *folio

要计费的 Folio。

struct mm_struct *mm

受害者的 mm 上下文

gfp_t gfp

回收模式

swp_entry_t entry

为之分配 Folio 的交换条目

描述

此函数对为换入分配的 Folio 进行计费。请在将 Folio 添加到交换缓存之前调用此函数。

成功时返回 0。否则,返回错误码。

void mem_cgroup_replace_folio(struct folio *old, struct folio *new)

对 Folio 的替换进行计费。

参数

struct folio *old

当前流通的 Folio。

struct folio *new

替换 Folio。

描述

将 **new** 作为 **old** 的替换 Folio 进行计费。**old** 将在释放时取消计费。

两个 Folio 都必须锁定,**new->mapping** 必须已设置。

void mem_cgroup_migrate(struct folio *old, struct folio *new)

将 memcg 数据从旧 Folio 传输到新 Folio。

参数

struct folio *old

当前流通的 Folio。

struct folio *new

替换 Folio。

描述

为迁移将 `memcg` 数据从旧 Folio 传输到新 Folio。旧 Folio 的数据信息将被清除。请注意,在此过程中内存计数器将保持不变。

两个 Folio 都必须锁定,**new->mapping** 必须已设置。

bool mem_cgroup_charge_skmem(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask)

计费套接字内存

参数

struct mem_cgroup *memcg

要计费的 memcg

unsigned int nr_pages

要计费的页数

gfp_t gfp_mask

回收模式

描述

将 **nr_pages** 计费到 **memcg**。如果计费符合 **memcg** 的配置限制,则返回 `true`,否则返回 `false`。

void mem_cgroup_uncharge_skmem(struct mem_cgroup *memcg, unsigned int nr_pages)

解除套接字内存计费

参数

struct mem_cgroup *memcg

要解除计费的 memcg

unsigned int nr_pages

要解除计费的页数

int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry)

尝试对 Folio 的交换空间收费

参数

struct folio *folio

正在添加到交换区的 Folio

swp_entry_t entry

要计费的交换条目

描述

尝试对 **folio** 的 `memcg` 计费 **entry** 处的交换空间。

成功时返回 0,失败时返回 `-ENOMEM`。

void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)

解除交换空间计费

参数

swp_entry_t entry

要解除计费的交换条目

unsigned int nr_pages

要解除计费的交换空间量

bool obj_cgroup_may_zswap(struct obj_cgroup *objcg)

检查此 cgroup 是否可以 `zswap`

参数

struct obj_cgroup *objcg

对象 cgroup

描述

检查是否已达到分层 `zswap` 限制。

此函数不检查具体的余量,也不是原子操作。但对于 `zswap`,只有在压缩发生后才知道分配的大小,这种乐观的预检查可以避免在已经没有空间或 `zswap` 在层级中完全禁用时在压缩上浪费周期。

void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)

计费压缩后端内存

参数

struct obj_cgroup *objcg

对象 cgroup

size_t size

压缩对象的大小

描述

obj_cgroup_may_zswap() 允许此 cgroup 进行压缩和 `zswap` 存储之后,此函数强制进行计费。

void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size)

解除压缩后端内存计费

参数

struct obj_cgroup *objcg

对象 cgroup

size_t size

压缩对象的大小

描述

在页换入时解除 `zswap` 内存的计费。

void shmem_recalc_inode(struct inode *inode, long alloced, long swapped)

重新计算 inode 的块使用情况

参数

struct inode *inode

要重新计算的 inode

long alloced

分配给 inode 的页数变化

long swapped

从 inode 交换出的页数变化

描述

我们必须计算空闲块,因为 `mm` 可以在我们不知情的情况下删除未脏化的空洞页。

但通常 `info->alloced == inode->i_mapping->nrpages + info->swapped` 所以 `mm` 释放的是 `info->alloced - (inode->i_mapping->nrpages + info->swapped)`

unsigned int shmem_mapping_size_orders(struct address_space *mapping, pgoff_t index, loff_t write_end)

获取给定文件大小允许的 Folio 阶。

参数

struct address_space *mapping

目标 `address_space`。

pgoff_t index

页索引。

loff_t write_end

写入结束位置,可能会扩展 inode 大小。

描述

此函数根据映射在给定索引处当前允许的文件大小返回 Folio 的大页阶(如果支持)。索引因映射可能具有的对齐考虑而相关。返回的阶可能小于传递的大小。

返回

页阶。

int shmem_writeout(struct folio *folio, struct writeback_control *wbc)

将 Folio 写入交换区

参数

struct folio *folio

要写入的 Folio

struct writeback_control *wbc

如何进行回写

描述

将 Folio 从页缓存移动到交换缓存。

int shmem_get_folio(struct inode *inode, pgoff_t index, loff_t write_end, struct folio **foliop, enum sgp_type sgp)

查找并锁定一个 `shmem` Folio。

参数

struct inode *inode

要搜索的 inode

pgoff_t index

页面索引。

loff_t write_end

写入结束,可以扩展inode大小

struct folio **foliop

如果找到,则指向folio的指针

enum sgp_type sgp

SGP_* 标志用于控制行为

描述

inodeindex处查找页面缓存条目。如果存在folio,则返回一个已锁定且引用计数增加的folio。

如果调用者修改了folio中的数据,它必须在解锁folio之前调用folio_mark_dirty(),以确保folio不会被回收。在调用folio_mark_dirty()之前无需保留空间。

当未找到folio时,行为取决于sgp
  • 对于SGP_READ,*foliopNULL,返回0

  • 对于SGP_NOALLOC,*foliopNULL,返回-ENOENT

  • 对于所有其他标志,将分配一个新的folio,插入页面缓存,并以锁定状态返回到foliop中。

上下文

可能休眠。

返回

成功返回0,否则返回负错误代码。

struct file *shmem_kernel_file_setup(const char *name, loff_t size, unsigned long flags)

获取一个驻留在tmpfs中且必须是内核内部的未链接文件。不会对底层inode进行LSM权限检查。因此,此接口的用户必须在更高层进行LSM检查。用户包括big_key和shm实现。LSM检查在key或shm级别提供,而非inode级别。

参数

const char *name

dentry的名称(将在/proc/<pid>/maps中可见)

loff_t size

要为文件设置的大小

unsigned long flags

VM_NORESERVE 禁止预先计算整个对象大小

struct file *shmem_file_setup(const char *name, loff_t size, unsigned long flags)

获取一个驻留在tmpfs中的未链接文件

参数

const char *name

dentry的名称(将在/proc/<pid>/maps中可见)

loff_t size

要为文件设置的大小

unsigned long flags

VM_NORESERVE 禁止预先计算整个对象大小

struct file *shmem_file_setup_with_mnt(struct vfsmount *mnt, const char *name, loff_t size, unsigned long flags)

获取一个驻留在tmpfs中的未链接文件

参数

struct vfsmount *mnt

文件将被创建的tmpfs挂载点

const char *name

dentry的名称(将在/proc/<pid>/maps中可见)

loff_t size

要为文件设置的大小

unsigned long flags

VM_NORESERVE 禁止预先计算整个对象大小

int shmem_zero_setup(struct vm_area_struct *vma)

设置共享匿名映射

参数

struct vm_area_struct *vma

待mmap的vma由do_mmap准备

struct folio *shmem_read_folio_gfp(struct address_space *mapping, pgoff_t index, gfp_t gfp)

读入页缓存,使用指定的页分配标志。

参数

struct address_space *mapping

folio的address_space

pgoff_t index

folio索引

gfp_t gfp

如果进行分配,使用的页分配器标志

描述

这行为类似于tmpfs的“read_cache_page_gfp(mapping, index, gfp)”,其中所有新的页面分配都使用指定的分配标志完成。但read_cache_page_gfp()使用->read_folio()方法:这不适合tmpfs,因为它可能在swapcache中有页面,并且需要自行找到这些页面;尽管drivers/gpu/drm i915和ttm依赖于此支持。

i915_gem_object_get_pages_gtt()将__GFP_NORETRY | __GFP_NOWARN与mapping_gfp_mask()混合使用,以避免不必要地导致机器OOM。

int migrate_vma_setup(struct migrate_vma *args)

准备迁移内存范围

参数

struct migrate_vma *args

包含vma、start和用于迁移的pfns数组

返回

失败时返回负数errno,0表示迁移了0个或更多页面且无错误。

描述

通过收集范围内的每个虚拟地址所支持的所有页面,并将它们保存在src数组中,从而准备迁移一段内存虚拟地址范围。然后锁定这些页面并取消映射它们。一旦页面被锁定和取消映射,检查每个页面是否被固定。未被固定的页面在相应的src数组条目中设置了MIGRATE_PFN_MIGRATE标志(由本函数设置)。然后通过重新映射和解锁这些页面来恢复任何被固定的页面。

调用者随后应为所有这些条目(即设置了MIGRATE_PFN_VALID和MIGRATE_PFN_MIGRATE标志的条目)分配目标内存并将源内存复制到其中。一旦这些被分配和复制,调用者必须使用目标页面的pfn值和MIGRATE_PFN_VALID更新dst数组中每个相应的条目。目标页面必须通过lock_page()锁定。

请注意,除非是从设备内存迁移到系统内存,否则调用者不必迁移src数组中标记有MIGRATE_PFN_MIGRATE标志的所有页面。如果调用者无法将设备页面迁移回系统内存,则必须返回VM_FAULT_SIGBUS,这会对用户空间进程造成严重后果,因此应尽可能避免。

对于CPU页表(pte_none()或pmd_none()为true)中的空条目,我们确实在相应的源数组中设置了MIGRATE_PFN_MIGRATE标志,从而允许调用者为那些未被支持的虚拟地址分配设备内存。为此,调用者只需分配设备内存并像常规迁移一样正确设置目标条目。请注意,这仍然可能失败,因此在设备驱动程序内部,您必须在调用migrate_vma_pages()之后检查这些条目的迁移是否成功,就像常规迁移一样。

之后,调用者必须调用migrate_vma_pages(),遍历src数组中所有设置了MIGRATE_PFN_VALID和MIGRATE_PFN_MIGRATE标志的条目。如果dst数组中的相应条目设置了MIGRATE_PFN_VALID标志,则migrate_vma_pages()会将struct page信息从源struct page迁移到目标struct page。如果迁移struct page信息失败,则清除src数组中的MIGRATE_PFN_MIGRATE标志。

此时,所有成功迁移的页面在src数组中都有一个设置了MIGRATE_PFN_VALID和MIGRATE_PFN_MIGRATE标志的条目,并且dst数组条目设置了MIGRATE_PFN_VALID标志。

一旦migrate_vma_pages()返回,调用者可以检查哪些页面成功迁移,哪些没有。成功迁移的页面将在其src数组条目中设置MIGRATE_PFN_MIGRATE标志。

migrate_vma_pages()之后更新设备页表是安全的,因为目标页面和源页面都仍然被锁定,并且mmap_lock以读模式持有(因此没有人可以取消映射正在迁移的范围)。

一旦调用者完成清理工作并更新其页表(如果选择这样做,这不是强制的),它最终会调用migrate_vma_finalize(),更新CPU页表以指向成功迁移页面的新页面,或者恢复CPU页表以指向原始源页面。

void migrate_device_pages(unsigned long *src_pfns, unsigned long *dst_pfns, unsigned long npages)

将元数据从源页迁移到目标页

参数

unsigned long *src_pfns

migrate_device_range()返回的src_pfns

unsigned long *dst_pfns

驱动程序为迁移内存而分配的pfns数组

unsigned long npages

范围内的页数

描述

相当于migrate_vma_pages()。这被调用以将struct page元数据从源struct page迁移到目标。struct page。

void migrate_vma_pages(struct migrate_vma *migrate)

将元数据从源页迁移到目标页

参数

struct migrate_vma *migrate

包含所有迁移信息的迁移结构体

描述

这会将struct page元数据从源struct page迁移到目标struct page。这有效地完成了从源页到目标页的迁移。

void migrate_vma_finalize(struct migrate_vma *migrate)

恢复CPU页表条目

参数

struct migrate_vma *migrate

包含所有迁移信息的迁移结构体

描述

如果该页迁移成功,则将其特殊的迁移pte条目替换为指向新页的映射;否则,恢复为指向原始源页的CPU页表。

这还会解锁页面并将其放回lru,或者对于设备页面,减少额外的引用计数。

int migrate_device_range(unsigned long *src_pfns, unsigned long start, unsigned long npages)

将设备私有pfns迁移到普通内存。

参数

unsigned long *src_pfns

足够大的数组,用于保存迁移的源设备私有pfns。

unsigned long start

要迁移范围中的起始pfn。

unsigned long npages

要迁移的页数。

描述

migrate_vma_setup()在概念上类似于migrate_vma_setup(),不同之处在于它不基于虚拟地址映射查找页面,而是使用一个应迁移到系统内存的设备pfns范围。

当驱动程序需要释放设备内存但不知道设备内存中可能存在的每个页面的虚拟映射时,这很有用。例如,当驱动程序正在卸载或从设备解绑时,通常会出现这种情况。

migrate_vma_setup()一样,此函数将在取消映射任何未空闲的迁移页面之前,获取其引用并锁定它们。然后,驱动程序可以分配目标页面并开始将数据从设备复制到CPU内存,然后再调用migrate_device_pages()

int migrate_device_pfns(unsigned long *src_pfns, unsigned long npages)

将设备私有pfns迁移到普通内存。

参数

unsigned long *src_pfns

预填充的要迁移的源设备私有pfns数组。

unsigned long npages

要迁移的页数。

描述

migrate_device_range()类似,但支持非连续的预填充设备页面数组进行迁移。

struct wp_walk

pagetable遍历回调的私有结构体

定义:

struct wp_walk {
    struct mmu_notifier_range range;
    unsigned long tlbflush_start;
    unsigned long tlbflush_end;
    unsigned long total;
};

成员

范围

mmu通知器的范围

tlbflush_start

第一个修改过的pte的地址

tlbflush_end

最后一个修改过的pte的地址 + 1

total

修改过的pte总数

int wp_pte(pte_t *pte, unsigned long addr, unsigned long end, struct mm_walk *walk)

写保护一个pte

参数

pte_t *pte

指向pte的指针

unsigned long addr

保护虚拟地址的起始

unsigned long end

保护虚拟地址的结束

struct mm_walk *walk

页表遍历回调参数

描述

该函数写保护一个pte并记录被触及pte在虚拟地址空间中的范围,以便进行高效的TLB刷新。

struct clean_walk

clean_record_pte 函数的私有结构体。

定义:

struct clean_walk {
    struct wp_walk base;
    pgoff_t bitmap_pgoff;
    unsigned long *bitmap;
    pgoff_t start;
    pgoff_t end;
};

成员

基址

我们从中派生的struct wp_walk

bitmap_pgoff

bitmap中第一个位的地址空间页面偏移量

位图

位图,地址空间覆盖范围内的每个页面偏移量对应一位。

起始

相对于bitmap_pgoff的第一个修改过的pte的address_space页面偏移量

结束

相对于bitmap_pgoff的最后一个修改过的pte的address_space页面偏移量

int clean_record_pte(pte_t *pte, unsigned long addr, unsigned long end, struct mm_walk *walk)

清除pte并在位图中记录其地址空间偏移量

参数

pte_t *pte

指向pte的指针

unsigned long addr

要清除的虚拟地址的起始

unsigned long end

要清除的虚拟地址的结束

struct mm_walk *walk

页表遍历回调参数

描述

该函数清除一个pte并记录被触及pte在虚拟地址空间中的范围,以便进行高效的TLB刷新。它还在位图中记录脏pte,位图表示address_space中的页面偏移量,以及被触及位的第一个和最后一个。

unsigned long wp_shared_mapping_range(struct address_space *mapping, pgoff_t first_index, pgoff_t nr)

写保护地址空间范围内的所有pte

参数

struct address_space *mapping

我们要写保护的address_space

pgoff_t first_index

范围中的第一个页面偏移量

pgoff_t nr

要覆盖的增量页面偏移量数量

注意

此函数当前跳过巨页表条目,因为它旨在用于PTE级别的脏跟踪。但是,它会在遇到启用写权限的巨页条目时发出警告,并且可以很容易地扩展以处理它们。

返回

实际写保护的pte数量。请注意,已经写保护的pte不计数在内。

unsigned long clean_record_shared_mapping_range(struct address_space *mapping, pgoff_t first_index, pgoff_t nr, pgoff_t bitmap_pgoff, unsigned long *bitmap, pgoff_t *start, pgoff_t *end)

清除并记录地址空间范围内的所有pte

参数

struct address_space *mapping

我们要清理的address_space

pgoff_t first_index

范围中的第一个页面偏移量

pgoff_t nr

要覆盖的增量页面偏移量数量

pgoff_t bitmap_pgoff

bitmap中第一个位的页面偏移量

unsigned long *bitmap

指向至少nr位的位图的指针。该位图需要覆盖整个范围first_index..**first_index** + nr

pgoff_t *start

指向bitmap中第一个设置位的编号的指针。当函数设置新位时,该值会被修改。

pgoff_t *end

指向bitmap中最后一个设置位的编号的指针。未设置。当函数设置新位时,该值会被修改。

描述

当此函数返回时,不能保证CPU尚未使新的pte变脏。但是它不会清除位图中未报告的任何pte。保证如下:

  • 函数开始执行时所有脏pte最终都将被记录在位图中。

  • 此后所有变脏的pte要么保持脏状态,要么被记录在位图中,或者两者兼有。

如果调用者需要确保所有脏pte都被捕获并且没有额外添加,它首先需要写保护地址空间范围,并确保在page_mkwrite()或pfn_mkwrite()中阻塞新的写入者。然后,在写保护之后的TLB刷新之后捕获所有脏位。

此函数当前跳过巨页表条目,因为它旨在用于PTE级别的脏跟踪。但是,它会在遇到巨页脏条目时发出警告,并且可以很容易地扩展以处理它们。

返回

实际清除的脏pte数量。

bool pcpu_addr_in_chunk(struct pcpu_chunk *chunk, void *addr)

检查地址是否由该块提供

参数

struct pcpu_chunk *chunk

感兴趣的块

void *addr

percpu地址

返回

如果地址由该块提供,则为真。

bool pcpu_check_block_hint(struct pcpu_block_md *block, int bits, size_t align)

根据连续性提示进行检查

参数

struct pcpu_block_md *block

感兴趣的块

int bits

分配大小

size_t align

区域对齐方式(最大PAGE_SIZE)

描述

检查分配是否能适应块的连续提示。请注意,一个chunk使用与块相同的提示,因此这也可以针对chunk的连续提示进行检查。

void pcpu_next_md_free_region(struct pcpu_chunk *chunk, int *bit_off, int *bits)

查找下一个提示空闲区域

参数

struct pcpu_chunk *chunk

感兴趣的块

int *bit_off

块偏移

int *bits

空闲区域大小

描述

pcpu_for_each_md_free_region 的辅助函数。它检查 block->contig_hint 并执行跨块聚合以找到下一个提示。它就地修改 bit_off 和 bits,以便在循环中使用。

void pcpu_next_fit_region(struct pcpu_chunk *chunk, int alloc_bits, int align, int *bit_off, int *bits)

为给定分配请求查找合适的区域

参数

struct pcpu_chunk *chunk

感兴趣的块

int alloc_bits

分配大小

int align

区域对齐方式(最大PAGE_SIZE)

int *bit_off

块偏移

int *bits

空闲区域大小

描述

查找下一个可用于给定大小和对齐的空闲区域。仅当存在可用于此分配的有效区域时才返回。如果分配请求适合该块,则返回 block->first_free,以查看请求是否可以在连续提示之前得到满足。

void *pcpu_mem_zalloc(size_t size, gfp_t gfp)

分配内存

参数

size_t size

要分配的字节数

gfp_t gfp

分配标志

描述

分配size字节。如果size小于PAGE_SIZE,则使用kzalloc();否则,使用vzalloc()的等效项。这是为了方便传递白名单标志。返回的内存总是被清零的。

返回

成功时返回指向已分配区域的指针,失败时返回NULL。

void pcpu_mem_free(void *ptr)

释放内存

参数

void *ptr

要释放的内存

描述

释放ptrptr应该已使用pcpu_mem_zalloc()分配。

void pcpu_chunk_relocate(struct pcpu_chunk *chunk, int oslot)

将chunk放置到适当的chunk槽位中

参数

struct pcpu_chunk *chunk

感兴趣的块

int oslot

它之前所在的槽位

描述

此函数在分配或释放改变了chunk之后被调用。根据改变后的状态确定新的槽位,并将chunk移动到该槽位。请注意,保留的chunk绝不会被放置在chunk槽位中。

上下文

pcpu_lock。

void pcpu_block_update(struct pcpu_block_md *block, int start, int end)

根据空闲区域更新块

参数

struct pcpu_block_md *block

感兴趣的块

int start

块内起始偏移量

int end

块内结束偏移量

描述

根据已知空闲区域更新块。区域[start, end)预期是块内空闲区域的全部。如果连续提示相等,则选择最佳起始偏移量。

void pcpu_chunk_refresh_hint(struct pcpu_chunk *chunk, bool full_scan)

更新块的元数据

参数

struct pcpu_chunk *chunk

感兴趣的块

bool full_scan

是否应该从头开始扫描

描述

遍历元数据块以找到最大的连续区域。在分配路径上可以避免完全扫描,因为这会在我们破坏连续提示时触发。这样做,scan_hint将在contig_hint之前,或者在scan_hint == contig_hint时在之后。在释放时无法阻止这种情况,因为我们希望找到可能跨块的最大区域。

void pcpu_block_refresh_hint(struct pcpu_chunk *chunk, int index)

参数

struct pcpu_chunk *chunk

感兴趣的块

int index

元数据块的索引

描述

从first_free开始扫描该块,并相应地更新块元数据。

void pcpu_block_update_hint_alloc(struct pcpu_chunk *chunk, int bit_off, int bits)

在分配路径上更新提示

参数

struct pcpu_chunk *chunk

感兴趣的块

int bit_off

块偏移

int bits

请求大小

描述

更新分配路径的元数据。仅当chunk的连续提示被破坏时,才需要通过完全扫描来刷新元数据。如果块的连续提示被破坏,则需要进行块级扫描。

void pcpu_block_update_hint_free(struct pcpu_chunk *chunk, int bit_off, int bits)

在释放路径上更新块提示

参数

struct pcpu_chunk *chunk

感兴趣的块

int bit_off

块偏移

int bits

请求大小

描述

更新分配路径的元数据。这通过利用块的连续提示来避免盲目块刷新。如果失败,它将向前和向后扫描以确定空闲区域的范围。这限制在块的边界。

如果一个页面变为空闲、一个块变为空闲或空闲区域跨越块,则会触发块更新。这种权衡是为了最大限度地减少遍历块元数据以更新 chunk_md->contig_hint。chunk_md->contig_hint 可能最多相差一个页面,但它永远不会超过可用空间。如果连续提示包含在一个块中,它将是准确的。

bool pcpu_is_populated(struct pcpu_chunk *chunk, int bit_off, int bits, int *next_off)

判断区域是否已填充

参数

struct pcpu_chunk *chunk

感兴趣的块

int bit_off

块偏移

int bits

区域大小

int *next_off

下一个开始搜索的偏移量的返回值

描述

对于原子分配,检查支持页面是否已填充。

返回

如果支持页面已填充,则为布尔值。next_index 用于跳过 pcpu_find_block_fit 中未填充的块。

int pcpu_find_block_fit(struct pcpu_chunk *chunk, int alloc_bits, size_t align, bool pop_only)

找到要开始搜索的块索引

参数

struct pcpu_chunk *chunk

感兴趣的块

int alloc_bits

分配单元中的请求大小

size_t align

区域对齐方式(最大PAGE_SIZE字节)

bool pop_only

仅使用已填充区域

描述

给定一个块和一个分配规范,找到开始搜索空闲区域的偏移量。这会遍历位图元数据块,以找到保证符合要求的偏移量。它并不完全是首次适应,因为如果分配不适合块或块的连续提示,它就会被跳过。这倾向于谨慎,以防止过度迭代。糟糕的对齐可能会导致分配器跳过具有有效空闲区域的块和块。

返回

位图中开始搜索的偏移量。如果未找到偏移量,则为-1。

int pcpu_alloc_area(struct pcpu_chunk *chunk, int alloc_bits, size_t align, int start)

从一个pcpu_chunk分配一个区域

参数

struct pcpu_chunk *chunk

感兴趣的块

int alloc_bits

分配单元中的请求大小

size_t align

区域对齐方式(最大PAGE_SIZE)

int start

bit_off 开始搜索

描述

此函数接收一个start偏移量,以便开始搜索以适应具有align对齐方式的alloc_bits分配。它需要扫描分配映射,因为如果它符合块的连续提示,start将是block->first_free。这是在破坏连续提示之前尝试填充分配。如果确认有效的空闲区域,分配和边界映射会相应更新。

返回

成功时返回chunk中已分配地址的偏移量。如果未找到匹配区域,则返回-1。

int pcpu_free_area(struct pcpu_chunk *chunk, int off)

释放相应的偏移量

参数

struct pcpu_chunk *chunk

感兴趣的块

int off

地址相对于chunk的偏移量

描述

此函数使用边界位图确定要释放的分配大小,并清除分配映射。

返回

已释放的字节数。

struct pcpu_chunk *pcpu_alloc_first_chunk(unsigned long tmp_addr, int map_size)

创建服务于第一个块的块

参数

unsigned long tmp_addr

服务区域的起始地址

int map_size

服务区域的大小

描述

此函数负责创建服务于第一个块的块。base_addr是tmp_addr页对齐向下舍入的地址,而区域结束是页对齐向上舍入的地址。跟踪偏移量以确定服务区域。所有这些都是为了满足位图分配器,避免部分块。

返回

服务于tmp_addrmap_size大小区域的块。

void pcpu_chunk_populated(struct pcpu_chunk *chunk, int page_start, int page_end)

填充后整理

参数

struct pcpu_chunk *chunk

被填充的pcpu_chunk

int page_start

起始页

int page_end

结束页

描述

范围[page_start,**page_end**)中的页面已填充到chunk中。相应地更新记账信息。每次成功填充后必须调用。

void pcpu_chunk_depopulated(struct pcpu_chunk *chunk, int page_start, int page_end)

取消填充后整理

参数

struct pcpu_chunk *chunk

被取消填充的pcpu_chunk

int page_start

起始页

int page_end

结束页

描述

范围[page_start,**page_end**)中的页面已从chunk中取消填充。相应地更新记账信息。每次成功取消填充后必须调用。

确定包含指定地址的块

参数

void *addr

需要确定其块的地址。

描述

这是一个内部函数,处理除静态分配之外的所有情况。静态percpu地址值绝不应传递给分配器。

返回

找到的块的地址。

void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved, gfp_t gfp)

percpu 分配器

参数

size_t size

要分配区域的字节大小

size_t align

区域对齐方式(最大PAGE_SIZE)

bool reserved

如果可用,从保留块分配

gfp_t gfp

分配标志

描述

分配大小为size字节、对齐方式为align的percpu区域。如果gfp不包含GFP_KERNEL,则分配是原子性的。如果gfp包含__GFP_NOWARN,则在无效或失败的分配请求上不会触发警告。

返回

成功时返回指向已分配区域的percpu指针,失败时返回NULL。

void pcpu_balance_free(bool empty_only)

管理空闲块的数量

参数

bool empty_only

仅当没有已填充页面时才释放块

描述

如果empty_only为false,则无论已填充页面的数量如何,都回收所有完全空闲的块。否则,仅回收没有已填充页面的块。

上下文

pcpu_lock(可暂时释放)

void pcpu_balance_populated(void)

管理已填充页面的数量

参数

void

无参数

描述

维护一定数量的已填充页面以满足原子分配。有可能会在物理内存不足时调用此函数,从而触发OOM killer。我们应该避免这样做,直到实际分配导致失败,因为请求可能从已支持的区域提供服务。

上下文

pcpu_lock(可暂时释放)

void pcpu_reclaim_populated(void)

扫描待取消填充的块并释放空闲页面

参数

void

无参数

描述

扫描待取消填充列表中的块,并尝试将未使用的已填充页面释放回系统。取消填充的块被搁置,以防止除非必要时才重新填充这些页面。完全空闲的块被重新集成并相应地释放(保留1个)。如果低于空闲已填充页面阈值,则如果块有空闲页面,则重新集成该块。每个块都以相反的顺序扫描,以使已填充页面靠近块的起始处。

上下文

pcpu_lock(可暂时释放)

void pcpu_balance_workfn(struct work_struct *work)

管理空闲块和已填充页面的数量

参数

struct work_struct *work

未使用

描述

对于每种块类型,管理完全空闲块的数量和已填充页面的数量。一个重要的考虑因素是页面何时被释放以及它们如何对全局计数做出贡献。

void free_percpu(void __percpu *ptr)

释放percpu区域

参数

void __percpu *ptr

指向要释放区域的指针

描述

释放percpu区域ptr

上下文

可以在原子上下文调用。

bool is_kernel_percpu_address(unsigned long addr)

测试地址是否来自静态percpu区域

参数

unsigned long addr

待测试地址

描述

测试addr是否属于内核静态percpu区域。模块静态percpu区域不考虑在内。对于那些,请使用is_module_percpu_address()。

返回

如果addr来自内核静态percpu区域,则为true,否则为false

phys_addr_t per_cpu_ptr_to_phys(void *addr)

将已翻译的percpu地址转换为物理地址

参数

void *addr

要转换为物理地址的地址

描述

给定通过percpu访问宏之一获取的addr(可解引用地址),此函数将其转换为物理地址。调用者负责确保在函数完成之前addr保持有效。

percpu分配器为第一个块进行了特殊设置,目前支持线性地址空间嵌入或vmalloc映射,从第二个块开始,由后端分配器(当前为vm或km)提供转换。

地址可以简单地翻译,而无需检查它是否落在第一个块中。但当前代码更好地反映了percpu分配器实际工作的方式,并且验证可以发现percpu分配器本身以及per_cpu_ptr_to_phys()调用者中的错误。因此我们保留了当前代码。

返回

addr的物理地址。

struct pcpu_alloc_info *pcpu_alloc_alloc_info(int nr_groups, int nr_units)

分配percpu分配信息

参数

int nr_groups

组的数量

int nr_units

单元的数量

描述

分配ai,它足够大,可容纳nr_groups个组,每个组包含nr_units个单元。返回的ai的groups[0].cpu_map指向cpu_map数组,该数组长度足够容纳nr_units,并用NR_CPUS填充。初始化其他组的cpu_map指针是调用者的责任。

返回

成功时返回指向已分配的pcpu_alloc_info的指针,失败时返回NULL。

void pcpu_free_alloc_info(struct pcpu_alloc_info *ai)

释放percpu分配信息

参数

struct pcpu_alloc_info *ai

要释放的pcpu_alloc_info

描述

释放由pcpu_alloc_alloc_info()分配的ai

void pcpu_dump_alloc_info(const char *lvl, const struct pcpu_alloc_info *ai)

打印pcpu_alloc_info的信息

参数

const char *lvl

日志级别

const struct pcpu_alloc_info *ai

要转储的分配信息

描述

使用日志级别lvl打印ai的信息。

void pcpu_setup_first_chunk(const struct pcpu_alloc_info *ai, void *base_addr)

初始化第一个percpu块

参数

const struct pcpu_alloc_info *ai

描述percpu区域形状的pcpu_alloc_info

void *base_addr

映射地址

描述

初始化包含内核静态percpu区域的第一个percpu块。此函数应从arch percpu区域设置路径中调用。

ai包含了初始化第一个块并启动动态percpu分配器所需的所有信息。

ai->static_size 是静态percpu区域的大小。

ai->reserved_size,如果非零,则指定在第一个块的静态区域之后保留的字节数。这会保留第一个块,使其仅通过保留的percpu分配可用。这主要用于在寻址模型对符号重定位的偏移范围有限的架构上为模块percpu静态区域提供服务,以保证模块percpu符号落在可重定位范围内。

ai->dyn_size 确定第一个块中可用于动态分配的字节数。ai->static_size + ai->reserved_size + ai->dyn_sizeai->unit_size 之间的区域未被使用。

ai->unit_size 指定单元大小,并且必须对齐到PAGE_SIZE,且大于或等于ai->static_size + ai->reserved_size + ai->dyn_size

ai->atom_size 是分配原子大小,用作vm区域的对齐方式。

ai->alloc_size 是分配大小,并且总是ai->atom_size的倍数。如果ai->unit_size大于ai->atom_size,则此值大于ai->atom_size

ai->nr_groupsai->groups 描述了 percpu 区域的虚拟内存布局。应该放在一起的单元被放在同一个组中。动态 VM 区域将根据这些分组进行分配。如果 ai->nr_groups 为零,则假定只有一个组包含所有单元。

调用者应该已经将第一个块映射到base_addr并复制静态数据到每个单元。

第一个块将始终包含一个静态区域和一个动态区域。然而,静态区域不受任何块的管理。如果第一个块还包含一个保留区域,则它由两个块提供服务——一个用于保留区域,一个用于动态区域。它们共享相同的VM,但在区域分配图中使用了偏移区域。服务于动态区域的块在块槽中循环,并且像任何其他块一样可用于动态分配。

struct pcpu_alloc_info *pcpu_build_alloc_info(size_t reserved_size, size_t dyn_size, size_t atom_size, pcpu_fc_cpu_distance_fn_t cpu_distance_fn)

构建alloc_info时考虑CPU之间的距离

参数

size_t reserved_size

保留percpu区域的字节大小

size_t dyn_size

动态分配的最小空闲字节大小

size_t atom_size

分配原子大小

pcpu_fc_cpu_distance_fn_t cpu_distance_fn

确定CPU之间距离的回调,可选

描述

此函数根据所需的percpu大小、分配原子大小以及CPU之间的距离,确定单元的分组、它们到CPU的映射以及其他参数。

组总是原子大小的倍数,并且双向 LOCAL_DISTANCE 的 CPU 会被分组在一起,并在同一个组中共享单元空间。返回的配置保证不同节点的 CPU 在不同的组中,并且已分配虚拟地址空间的使用率 >= 75%。

返回

成功时,返回指向新allocation_info的指针。失败时,返回ERR_PTR值。

int pcpu_embed_first_chunk(size_t reserved_size, size_t dyn_size, size_t atom_size, pcpu_fc_cpu_distance_fn_t cpu_distance_fn, pcpu_fc_cpu_to_node_fn_t cpu_to_nd_fn)

将第一个percpu块嵌入到bootmem中

参数

size_t reserved_size

保留percpu区域的字节大小

size_t dyn_size

动态分配的最小空闲字节大小

size_t atom_size

分配原子大小

pcpu_fc_cpu_distance_fn_t cpu_distance_fn

确定CPU之间距离的回调,可选

pcpu_fc_cpu_to_node_fn_t cpu_to_nd_fn

将CPU转换为其节点的 callback,可选

描述

这是一个有助于简化设置嵌入式第一个percpu块的辅助函数,可以在预期调用pcpu_setup_first_chunk()的地方调用。

如果此函数用于设置第一个数据块,它通过调用 pcpu_fc_alloc 进行分配并按原样使用,不映射到 vmalloc 区域。分配始终是 atom_size 的整数倍,并与 atom_size 对齐。

这使得第一个数据块可以利用线性物理映射(通常使用更大的页大小)。请注意,这可能导致 NUMA 机器上 cpu->unit 映射非常稀疏,从而需要较大的 vmalloc 地址空间。如果 vmalloc 空间没有比节点内存地址之间的距离大几个数量级(例如 32 位 NUMA 机器),请勿使用此分配器。

dyn_size 指定最小动态区域大小。

如果所需大小小于最小或指定单元大小,剩余部分将使用 pcpu_fc_free 返回。

返回

成功时返回 0,失败时返回 -errno。

int pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t cpu_to_nd_fn)

使用 PAGE_SIZE 页面映射第一个数据块

参数

size_t reserved_size

保留percpu区域的字节大小

pcpu_fc_cpu_to_node_fn_t cpu_to_nd_fn

将CPU转换为其节点的 callback,可选

描述

这是一个辅助函数,用于简化设置页面重新映射的第一个 percpu 数据块,可以在预期调用 pcpu_setup_first_chunk() 的地方调用。

这是基本的分配器。静态 percpu 区域逐页分配到 vmalloc 区域。

返回

成功时返回 0,失败时返回 -errno。

long copy_from_user_nofault(void *dst, const void __user *src, size_t size)

安全地尝试从用户空间位置读取

参数

void *dst

指向接收数据的缓冲区的指针

const void __user *src

要读取的地址。这必须是一个用户地址。

size_t size

数据块的大小

描述

安全地将数据从用户地址 src 读取到 **dst** 处的缓冲区。如果发生内核故障,请处理并返回 -EFAULT。

long copy_to_user_nofault(void __user *dst, const void *src, size_t size)

安全地尝试写入用户空间位置

参数

void __user *dst

要写入的地址

const void *src

指向要写入数据的指针

size_t size

数据块的大小

描述

安全地将数据从 **src** 处的缓冲区写入 **dst** 地址。如果发生内核故障,请处理并返回 -EFAULT。

long strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, long count)
  • 从不安全的用户地址复制以 NUL 结尾的字符串。

参数

char *dst

目标地址,位于内核空间。此缓冲区长度必须至少为 count 字节。

const void __user *unsafe_addr

不安全的用户地址。

long count

要复制的最大字节数,包括尾随的 NUL 字符。

描述

将以 NUL 结尾的字符串从不安全的用户地址复制到内核缓冲区。

成功时,返回字符串的长度,包括尾随的 NUL 字符。

如果访问失败,返回 -EFAULT(可能已复制部分数据并添加了尾随的 NUL 字符)。

如果 count 小于字符串的长度,则复制 count-1 字节,将 **dst** 缓冲区的最后一个字节设置为 NUL,并返回 count

long strnlen_user_nofault(const void __user *unsafe_addr, long count)
  • 获取用户字符串的大小,包括最后的 NUL 字符。

参数

const void __user *unsafe_addr

要测量的字符串。

long count

最大计数(包括 NUL)

描述

获取用户空间中以 NUL 结尾的字符串的大小,不会引发页面错误。

返回字符串的大小,包括终止 NUL 字符。

如果字符串过长,返回一个大于 count 的数字。用户必须检查返回值是否“大于 count”。发生异常(或无效计数)时,返回 0。

与 strnlen_user 不同,此函数可用于 IRQ 处理程序等,因为它禁用了页面错误。

bool writeback_throttling_sane(struct scan_control *sc)

常规的脏页限流机制是否可用?

参数

struct scan_control *sc

所涉及的 scan_control

描述

balance_dirty_pages() 中的常规脏页限流机制在旧版 memcg 下完全失效,而是使用 shrink_folio_list() 中的直接停滞进行限流,这缺少了公平性、自适应暂停、带宽比例分配和可配置性等所有优点。

此函数测试当前正在进行的 vmscan 是否可以假定正常的脏页限流机制正在运行。

unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx)

返回给定 LRU 列表上的页数。

参数

struct lruvec *lruvec

LRU 向量

enum lru_list lru

要使用的 LRU

int zone_idx

要考虑的区域(使用 MAX_NR_ZONES - 1 表示整个 LRU 列表)

long remove_mapping(struct address_space *mapping, struct folio *folio)

尝试从其映射中移除一个 folio。

参数

struct address_space *mapping

地址空间。

struct folio *folio

要移除的 folio。

描述

如果 folio 是脏的、正在回写中,或者有其他引用,则移除将失败。

返回

从映射中移除的页数。如果 folio 无法移除,则返回 0。

上下文

调用者应该对该 folio 有一个引用计数并持有其锁。

void folio_putback_lru(struct folio *folio)

将先前隔离的 folio 放回适当的 LRU 列表。

参数

struct folio *folio

要返回到 LRU 列表的 folio。

描述

将先前隔离的 folio 添加到适当的 LRU 列表。由于其他原因,该 folio 可能仍然无法被回收。

上下文

lru_lock 不得被持有,中断必须启用。

bool folio_isolate_lru(struct folio *folio)

尝试从 LRU 列表中隔离一个 folio。

参数

struct folio *folio

要从 LRU 列表中隔离的 folio。

描述

从 LRU 列表中隔离一个 folio 并调整与该 folio 所在的 LRU 列表相对应的 vmstat 统计信息。

该 folio 的 LRU 标志将被清除。如果它在活动列表中,其 Active 标志将被设置。如果它在不可回收列表中,其 Unevictable 标志将被设置。在释放页面之前,调用者可能需要清除这些标志。

  1. 调用时必须增加 folio 的引用计数。这与 isolate_lru_folios()(在没有稳定引用的情况下调用)存在根本区别。

  2. lru_lock 不得被持有。

  3. 中断必须启用。

上下文

返回

如果 folio 已从 LRU 列表中移除,则为 true。如果 folio 不在 LRU 列表中,则为 false。

void check_move_unevictable_folios(struct folio_batch *fbatch)

将可回收的 folios 移动到相应的区域 LRU 列表

参数

struct folio_batch *fbatch

要检查的 LRU folios 批次。

描述

检查 folios 的可回收性,如果可回收的 folio 位于不可回收 LRU 列表中,则将其移动到相应的可回收 LRU 列表。此函数仅应用于 LRU folios。

void __remove_pages(unsigned long pfn, unsigned long nr_pages, struct vmem_altmap *altmap)

移除页面段

参数

unsigned long pfn

起始页框(必须与节的起始地址对齐)

unsigned long nr_pages

要移除的页数(必须是节大小的倍数)

struct vmem_altmap *altmap

备用设备页面映射,如果使用默认 memmap,则为 NULL

描述

通用辅助函数,用于移除正在移除的内存部分的节映射和 sysfs 条目。调用者需要通过调用 offline_pages() 确保页面被标记为保留状态,并正确调整区域。

void try_offline_node(int nid)

参数

int nid

节点 ID

描述

如果节点的所有内存段和 CPU 都已移除,则使节点离线。

注意

调用者必须在此调用之前调用 lock_device_hotplug() 以串行化热插拔和在线/离线操作。

void __remove_memory(u64 start, u64 size)

如果每个内存块都处于离线状态,则移除内存

参数

u64 start

要移除区域的物理地址

u64 size

要移除区域的大小

注意

调用者必须在此调用之前调用 lock_device_hotplug() 以串行化热插拔和在线/离线操作,如 try_offline_node() 所要求的那样。

unsigned long mmu_interval_read_begin(struct mmu_interval_notifier *interval_sub)

针对 VA 范围开始一个读取侧临界区

参数

struct mmu_interval_notifier *interval_sub

间隔订阅

描述

mmu_iterval_read_begin()/mmu_iterval_read_retry() 为订阅的 VA 范围实现了类似于 seqcount 的碰撞重试机制。如果在临界区期间 mm 调用了无效化,则 mmu_interval_read_retry() 将返回 true。

这对于获取影子 PTE 很有用,其中 SPTE 的拆除或设置需要阻塞上下文。由此形成的临界区可以休眠,并且所需的“user_lock”也可以是休眠锁。

调用者需要提供一个“user_lock”来串行化拆除和设置操作。

返回值应传递给 mmu_interval_read_retry()。

int mmu_notifier_register(struct mmu_notifier *subscription, struct mm_struct *mm)

在 mm 上注册一个通知器

参数

struct mmu_notifier *subscription

要附加的通知器

struct mm_struct *mm

要附加通知器的 mm

描述

调用此注册函数时,不得持有 mmap_lock 或任何其他与 VM 相关的锁。还必须确保在运行时 mm_users 不会降至零,以避免与 mmu_notifier_release 发生竞争条件,因此 mm 必须是 current->mm,或者 mm 应该安全地固定(例如使用 get_task_mm())。如果 mm 不是 current->mm,则在 mmu_notifier_register 返回后,应通过调用 mmput 释放 mm_users 锁定。

必须始终调用 mmu_notifier_unregister() 或 mmu_notifier_put() 来注销通知器。

当调用者获取到 mmu_notifier 时,subscription->mm 指针将保持有效,并且可以通过 mmget_not_zero() 转换为活动的 mm 指针。

struct mmu_notifier *mmu_notifier_get_locked(const struct mmu_notifier_ops *ops, struct mm_struct *mm)

返回 mm 和操作的单一 struct mmu_notifier

参数

const struct mmu_notifier_ops *ops

用于订阅的操作结构

struct mm_struct *mm

也要附加通知器的 mm

描述

此函数通过 ops->alloc_notifier() 分配一个新的 mmu_notifier,或者返回列表中已存在的通知器。ops 指针的值用于确定两个通知器何时相同。

每次调用 mmu_notifier_get() 都必须与调用 mmu_notifier_put() 配对。调用者必须持有 mm->mmap_lock 的写入侧锁。

当调用者获取到 mmu_notifier 时,mm 指针将保持有效,并且可以通过 mmget_not_zero() 转换为活动的 mm 指针。

void mmu_notifier_put(struct mmu_notifier *subscription)

释放通知器上的引用

参数

struct mmu_notifier *subscription

要操作的通知器

描述

此函数必须与每个 mmu_notifier_get() 配对,它释放通过 get 获取的引用。如果这是最后一个引用,则释放通知器的过程将异步运行。

与 mmu_notifier_unregister() 不同,get/put 流程只在 mm_struct 被销毁时调用 ops->release。相反,free_notifier 总是被调用以释放用户持有的任何资源。

由于 ops->release 不保证被调用,用户必须确保所有 SPTE 都已丢弃,并且在调用 mmu_notifier_put() 之前无法建立新的 SPTE。

此函数可以从 ops->release 回调中调用,但是调用者仍然必须确保它与 mmu_notifier_get() 成对调用。

调用此函数的模块必须在其 __exit 函数中调用 mmu_notifier_synchronize(),以确保异步工作完成。

int mmu_interval_notifier_insert(struct mmu_interval_notifier *interval_sub, struct mm_struct *mm, unsigned long start, unsigned long length, const struct mmu_interval_notifier_ops *ops)

插入一个间隔通知器

参数

struct mmu_interval_notifier *interval_sub

要注册的间隔订阅

struct mm_struct *mm

要附加到的 mm_struct

unsigned long start

要监控的起始虚拟地址

unsigned long length

要监控范围的长度

const struct mmu_interval_notifier_ops *ops

在匹配事件发生时调用的间隔通知器操作

描述

此函数订阅间隔通知器以接收来自 mm 的通知。返回后,每当发生与给定范围相交的事件时,将调用与 mmu_interval_notifier 相关的操作。

返回时,range_notifier 可能尚未出现在间隔树中。调用者必须通过 mmu_interval_read_begin() 使用常规间隔通知器读取流程,为该范围建立 SPTE。

void mmu_interval_notifier_remove(struct mmu_interval_notifier *interval_sub)

移除一个间隔通知器

参数

struct mmu_interval_notifier *interval_sub

要注销的间隔订阅

描述

此函数必须与 mmu_interval_notifier_insert() 配对使用。它不能从任何 ops 回调中调用。

此函数返回后,ops 回调将不再在其他 CPU 上运行,将来也不会再被调用。

void mmu_notifier_synchronize(void)

确保所有 mmu_notifiers 都已释放

参数

void

无参数

描述

此函数确保所有从 mmu_notifier_put() 发出的未完成异步 SRU 工作都已完成。返回后,与未使用的 mmu_notifier 关联的任何 mmu_notifier_ops 将不再被调用。

在使用之前,调用者必须确保其所有 mmu_notifier 都已通过 mmu_notifier_put() 完全释放。

使用 mmu_notifier_put() API 的模块应在其 __exit 函数中调用此函数,以避免模块卸载时的竞争条件。

size_t balloon_page_list_enqueue(struct balloon_dev_info *b_dev_info, struct list_head *pages)

将页面列表插入到 balloon 页面列表中。

参数

struct balloon_dev_info *b_dev_info

将要插入新页面的 balloon 设备描述符

struct list_head *pages

要排队的页面 - 使用 balloon_page_alloc 分配。

描述

驱动程序必须在最终将 balloon 页面从客户机系统中移除之前,调用此函数以正确地将其入队。

返回

已入队的页数。

size_t balloon_page_list_dequeue(struct balloon_dev_info *b_dev_info, struct list_head *pages, size_t n_req_pages)

从 balloon 的页面列表中移除页面并返回页面列表。

参数

struct balloon_dev_info *b_dev_info

将要从中获取页面的 balloon 设备描述符。

struct list_head *pages

指向将返回给调用者的页面列表的指针。

size_t n_req_pages

请求的页数。

描述

驱动程序必须在最终将先前入队的 balloon 页面释放回客户机系统之前,调用此函数以正确地将其解除分配。此函数尝试从已 balloon 的页面中移除 n_req_pages 个页面,并在 **pages** 列表中将其返回给调用者。

请注意,即使 balloon 不为空,此函数也可能无法出队某些页面——因为页面列表可能由于隔离页面的压缩而暂时为空。

返回

已添加到 pages 列表中的页数。

vm_fault_t vmf_insert_pfn_pmd(struct vm_fault *vmf, pfn_t pfn, bool write)

插入一个 pmd 大小的 pfn

参数

struct vm_fault *vmf

描述故障的结构体

pfn_t pfn

要插入的 pfn

bool write

是否为写入故障

描述

插入一个 pmd 大小的 pfn。有关更多信息,请参阅 vmf_insert_pfn()

返回

vm_fault_t 值。

vm_fault_t vmf_insert_pfn_pud(struct vm_fault *vmf, pfn_t pfn, bool write)

插入一个 pud 大小的 pfn

参数

struct vm_fault *vmf

描述故障的结构体

pfn_t pfn

要插入的 pfn

bool write

是否为写入故障

描述

插入一个 pud 大小的 pfn。有关更多信息,请参阅 vmf_insert_pfn()

返回

vm_fault_t 值。

vm_fault_t vmf_insert_folio_pud(struct vm_fault *vmf, struct folio *folio, bool write)

插入一个由 pud 条目映射的 pud 大小 folio

参数

struct vm_fault *vmf

描述故障的结构体

struct folio *folio

要插入的 folio

bool write

是否为写入故障

返回

vm_fault_t 值。

int io_mapping_map_user(struct io_mapping *iomap, struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size)

将 I/O 映射重新映射到用户空间

参数

struct io_mapping *iomap

源 io_mapping

struct vm_area_struct *vma

要映射到的用户 VMA

unsigned long addr

要开始的目标用户地址

unsigned long pfn

内核内存的物理地址

unsigned long size

映射区域的大小

注意

仅在调用时持有 MM 信号量才安全。