Folio 队列

作者:

David Howells <dhowells@redhat.com>

概述

folio_queue 结构体在 Folio 的分段列表中形成一个单独的段,可用于构成 I/O 缓冲区。因此,该列表可以使用 ITER_FOLIOQ iov_iter 类型进行迭代。

该结构体可公开访问的成员包括:

struct folio_queue {
        struct folio_queue *next;
        struct folio_queue *prev;
        ...
};

提供了 nextprev 一对指针,它们指向被访问段两侧的段。虽然这是一个双向链表,但它并非有意设计为循环列表;末端段的向外兄弟指针应为 NULL。

列表中的每个段还存储:

  • 有序的 Folio 指针序列,

  • 每个 Folio 的大小,以及

  • 每个 Folio 三个 1 位标记,

但这些不应直接访问,因为底层数据结构可能会改变,而应使用下面列出的访问函数。

此功能可以通过以下方式访问:

#include <linux/folio_queue.h>

并使用迭代器:

#include <linux/uio.h>

初始化

一个段应该通过调用以下函数进行初始化:

void folioq_init(struct folio_queue *folioq);

并传入要初始化段的指针。请注意,这不一定会初始化所有 Folio 指针,因此必须注意检查已添加 Folio 的数量。

添加和移除 Folio

可以通过调用以下函数之一,在段结构体的下一个未使用槽位中设置 Folio:

unsigned int folioq_append(struct folio_queue *folioq,
                           struct folio *folio);

unsigned int folioq_append_mark(struct folio_queue *folioq,
                                struct folio *folio);

这两个函数都会更新存储的 Folio 计数,存储 Folio 并记录其大小。第二个函数还会为添加的 Folio 设置第一个标记。这两个函数都返回所使用的槽位号。[!] 注意,未尝试检查容量是否溢出,列表也不会自动扩展。

可以通过调用以下函数移除 Folio:

void folioq_clear(struct folio_queue *folioq, unsigned int slot);

这会清除数组中的槽位,并清除该 Folio 的所有标记,但不会改变 Folio 计数——因此将来访问该槽位时必须检查该槽位是否被占用。

查询 Folio 信息

可以通过以下函数查询特定槽位中 Folio 的信息:

struct folio *folioq_folio(const struct folio_queue *folioq,
                           unsigned int slot);

如果该槽位尚未设置 Folio,这可能会产生未定义的指针。可以通过以下任一函数查询槽位中 Folio 的大小:

unsigned int folioq_folio_order(const struct folio_queue *folioq,
                                unsigned int slot);

size_t folioq_folio_size(const struct folio_queue *folioq,
                         unsigned int slot);

第一个函数以“阶”的形式返回大小,第二个函数以字节数的形式返回大小。

查询 folio_queue 信息

可以使用以下函数检索有关特定段的信息:

unsigned int folioq_nr_slots(const struct folio_queue *folioq);

unsigned int folioq_count(struct folio_queue *folioq);

bool folioq_full(struct folio_queue *folioq);

第一个函数返回段的最大容量。不能假定这在不同段之间不会变化。第二个函数返回已添加到段的 Folio 数量,第三个是表示段是否已达到容量的简写。

请注意,计数和满度不受从段中清除 Folio 的影响。这些更多是用来指示数组中有多少槽位已初始化,并且假设槽位不会被重复使用,而是随着队列的消耗,段将被丢弃。

Folio 标记

队列中的 Folio 也可以被分配标记。这些标记可以用来记录信息,例如 Folio 是否需要调用 folio_put()。每个 Folio 有三个可设置的标记。

标记可以设置为:

void folioq_mark(struct folio_queue *folioq, unsigned int slot);
void folioq_mark2(struct folio_queue *folioq, unsigned int slot);

清除标记:

void folioq_unmark(struct folio_queue *folioq, unsigned int slot);
void folioq_unmark2(struct folio_queue *folioq, unsigned int slot);

标记可以查询:

bool folioq_is_marked(const struct folio_queue *folioq, unsigned int slot);
bool folioq_is_marked2(const struct folio_queue *folioq, unsigned int slot);

这些标记可以用于任何目的,并且不会被此 API 解释。

Folio 队列迭代

可以使用 I/O 迭代器工具,通过类型为 ITER_FOLIOQiov_iter 迭代器来遍历段列表。迭代器可以使用以下函数初始化:

void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction,
                          const struct folio_queue *folioq,
                          unsigned int first_slot, unsigned int offset,
                          size_t count);

可以告知它从队列中的特定段、槽位和偏移量开始。iov 迭代器函数在前进时会遵循 next 指针,在回溯时会遵循 prev 指针。

无锁并发生产/消费问题

如果管理得当,生产者可以在头部扩展列表,消费者可以在尾部缩短列表,而无需加锁。ITER_FOLIOQ 迭代器会插入适当的屏障来协助实现这一点。

在同时生产和消费列表时必须小心。如果到达最后一个段,并且该段引用的 Folio 完全被 IOV 迭代器消耗,则 iov_iter 结构体将指向最后一个段,其槽位号等于该段的容量。如果再次使用迭代器时有另一个段可用,迭代器将尝试从此继续,但必须小心,以防在迭代器前进之前段被消费者移除并释放。

建议队列始终包含至少一个段,即使该段从未被填充或已完全耗尽。这可以防止头尾指针塌缩。

API 函数参考

void folioq_init(struct folio_queue *folioq, unsigned int rreq_id)

初始化一个 Folio 队列段

参数

struct folio_queue *folioq

要初始化的段

unsigned int rreq_id

在跟踪行中使用的请求标识符。

描述

初始化一个 Folio 队列段并设置用于跟踪的标识符。

请注意,Folio 指针保持未初始化状态。

unsigned int folioq_nr_slots(const struct folio_queue *folioq)

查询 Folio 队列段的容量

参数

const struct folio_queue *folioq

要查询的段

描述

查询特定 Folio 队列段可能容纳的 Folio 数量。[!] 注意:不能假设这对于每个段都是相同的!

unsigned int folioq_count(struct folio_queue *folioq)

查询 Folio 队列段的占用率

参数

struct folio_queue *folioq

要查询的段

描述

查询已添加到 Folio 队列段的 Folio 数量。请注意,当 Folio 从段中移除时,此计数不会减少。

bool folioq_full(struct folio_queue *folioq)

查询 Folio 队列段是否已满

参数

struct folio_queue *folioq

要查询的段

描述

查询 Folio 队列段是否已完全占用。请注意,即使 Folio 从段中移除,此状态也不会改变。

bool folioq_is_marked(const struct folio_queue *folioq, unsigned int slot)

检查 Folio 队列段中的第一个 Folio 标记

参数

const struct folio_queue *folioq

要查询的段

unsigned int slot

要查询的 Folio 的槽位号

描述

确定 Folio 队列段中指定槽位中的 Folio 的第一个标记是否已设置。

void folioq_mark(struct folio_queue *folioq, unsigned int slot)

在 Folio 队列段中的 Folio 上设置第一个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的 Folio 的槽位号

描述

在 Folio 队列段中指定槽位中的 Folio 上设置第一个标记。

void folioq_unmark(struct folio_queue *folioq, unsigned int slot)

清除 Folio 队列段中的 Folio 上的第一个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的 Folio 的槽位号

描述

清除 Folio 队列段中指定槽位中的 Folio 的第一个标记。

bool folioq_is_marked2(const struct folio_queue *folioq, unsigned int slot)

检查 Folio 队列段中的第二个 Folio 标记

参数

const struct folio_queue *folioq

要查询的段

unsigned int slot

要查询的 Folio 的槽位号

描述

确定 Folio 队列段中指定槽位中的 Folio 的第二个标记是否已设置。

void folioq_mark2(struct folio_queue *folioq, unsigned int slot)

在 Folio 队列段中的 Folio 上设置第二个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的 Folio 的槽位号

描述

在 Folio 队列段中指定槽位中的 Folio 上设置第二个标记。

void folioq_unmark2(struct folio_queue *folioq, unsigned int slot)

清除 Folio 队列段中的 Folio 上的第二个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的 Folio 的槽位号

描述

清除 Folio 队列段中指定槽位中的 Folio 的第二个标记。

unsigned int folioq_append(struct folio_queue *folioq, struct folio *folio)

将 Folio 添加到 Folio 队列段

参数

struct folio_queue *folioq

要添加到的段

struct folio *folio

要添加的 Folio

描述

将 Folio 添加到 Folio 队列段中序列的尾部,增加占用计数并返回刚添加 Folio 的槽位号。Folio 大小会被提取并存储在队列中,标记保持不变。

请注意,由调用者负责检查段容量是否会超出并扩展队列。

unsigned int folioq_append_mark(struct folio_queue *folioq, struct folio *folio)

将 Folio 添加到 Folio 队列段

参数

struct folio_queue *folioq

要添加到的段

struct folio *folio

要添加的 Folio

描述

将 Folio 添加到 Folio 队列段中序列的尾部,增加占用计数并返回刚添加 Folio 的槽位号。Folio 大小会被提取并存储在队列中,第一个标记被设置,第二个和第三个标记保持不变。

请注意,由调用者负责检查段容量是否会超出并扩展队列。

struct folio *folioq_folio(const struct folio_queue *folioq, unsigned int slot)

从 Folio 队列段获取 Folio

参数

const struct folio_queue *folioq

要访问的段

unsigned int slot

要访问的 Folio 槽位

描述

从 Folio 队列段中检索指定槽位中的 Folio。请注意,不进行边界检查,如果槽位尚未添加 Folio,则指针将未定义。如果槽位已被清除,则返回 NULL。

unsigned int folioq_folio_order(const struct folio_queue *folioq, unsigned int slot)

从 Folio 队列段获取 Folio 的阶

参数

const struct folio_queue *folioq

要访问的段

unsigned int slot

要访问的 Folio 槽位

描述

从 Folio 队列段中检索指定槽位中 Folio 的阶。请注意,不进行边界检查,如果槽位尚未添加 Folio,则返回的阶将为 0。

size_t folioq_folio_size(const struct folio_queue *folioq, unsigned int slot)

从 Folio 队列段获取 Folio 的大小

参数

const struct folio_queue *folioq

要访问的段

unsigned int slot

要访问的 Folio 槽位

描述

从 Folio 队列段中检索指定槽位中 Folio 的大小。请注意,不进行边界检查,如果槽位尚未添加 Folio,则返回的大小将为 PAGE_SIZE。

void folioq_clear(struct folio_queue *folioq, unsigned int slot)

从 Folio 队列段中清除 Folio

参数

struct folio_queue *folioq

要清除的段

unsigned int slot

要清除的 Folio 槽位

描述

从 Folio 队列段中的序列中清除 Folio 并清除其标记。占用计数保持不变。