页队列

作者:

David Howells <dhowells@redhat.com>

概述

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

该结构的公开访问成员是

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

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

列表中的每个段还存储

  • 一个有序的页指针序列,

  • 每个页的大小和

  • 每个页三个 1 位标记,

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

可以通过以下方式使该功能可访问

#include <linux/folio_queue.h>

并使用迭代器

#include <linux/uio.h>

初始化

应通过调用以下函数来初始化段

void folioq_init(struct folio_queue *folioq);

并使用指向要初始化的段的指针。请注意,这不一定会初始化所有页指针,因此必须小心检查添加的页数。

添加和删除页

可以通过调用以下函数之一在段结构中的下一个未使用槽中设置页

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

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

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

可以通过调用以下函数来删除页

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

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

查询有关页的信息

可以使用以下函数查询特定槽中页的信息

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

如果尚未在该槽中设置页,则可能会产生未定义的指针。可以使用以下两个函数之一查询槽中页的大小

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_put()。每个页有三个标记可以设置。

可以通过以下方式设置标记

void folioq_mark(struct folio_queue *folioq, unsigned int slot);
void folioq_mark2(struct folio_queue *folioq, unsigned int slot);
void folioq_mark3(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);
void folioq_unmark3(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);
bool folioq_is_marked3(const struct folio_queue *folioq, unsigned int slot);

这些标记可以用于任何目的,此 API 不会对其进行解释。

页队列迭代

可以使用 I/O 迭代器工具使用 ITER_FOLIOQ 类型的 iov_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 迭代器函数将在前进时跟随下一个指针,在还原时跟随前一个指针。

无锁同时生产/消费问题

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

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

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

API 函数参考

void folioq_init(struct folio_queue *folioq)

初始化页队列段

参数

struct folio_queue *folioq

要初始化的段

描述

初始化页队列段。请注意,页指针保持未初始化状态。

unsigned int folioq_nr_slots(const struct folio_queue *folioq)

查询页队列段的容量

参数

const struct folio_queue *folioq

要查询的段

描述

查询特定页队列段可以容纳的页数。[!] 注意:不得假设每个段都相同!

unsigned int folioq_count(struct folio_queue *folioq)

查询页队列段的占用率

参数

struct folio_queue *folioq

要查询的段

描述

查询已添加到页队列段的页数。请注意,当从段中删除页时,该值不会减少。

bool folioq_full(struct folio_queue *folioq)

查询页队列段是否已满

参数

struct folio_queue *folioq

要查询的段

描述

查询页队列段是否已完全占用。请注意,如果从段中删除页,则该值不会更改。

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

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

参数

const struct folio_queue *folioq

要查询的段

unsigned int slot

要查询的页的槽位号

描述

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

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

设置页队列段中页的第一个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

为页队列段中指定槽位中的页设置第一个标记。

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

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

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

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

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

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

参数

const struct folio_queue *folioq

要查询的段

unsigned int slot

要查询的页的槽位号

描述

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

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

设置页队列段中页的第二个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

为页队列段中指定槽位中的页设置第二个标记。

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

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

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

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

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

检查页队列段中的第三个页标记

参数

const struct folio_queue *folioq

要查询的段

unsigned int slot

要查询的页的槽位号

描述

确定是否为页队列段中指定槽位中的页设置了第三个标记。

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

设置页队列段中页的第三个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

为页队列段中指定槽位中的页设置第三个标记。

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

清除页队列段中页的第三个标记

参数

struct folio_queue *folioq

要修改的段

unsigned int slot

要修改的页的槽位号

描述

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

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

将页添加到页队列段

参数

struct folio_queue *folioq

要添加到的段

struct folio *folio

要添加的页

描述

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

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

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

将页添加到页队列段

参数

struct folio_queue *folioq

要添加到的段

struct folio *folio

要添加的页

描述

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

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

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

从页队列段获取一个页

参数

const struct folio_queue *folioq

要访问的段

unsigned int slot

要访问的页槽位

描述

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

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

从页队列段获取页的顺序

参数

const struct folio_queue *folioq

要访问的段

unsigned int slot

要访问的页槽位

描述

从 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 的大小。请注意,不执行边界检查,如果该槽位尚未添加,则返回的大小将为 PAGE_SIZE。

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

从 folio 队列段清除 folio

参数

struct folio_queue *folioq

要清除的段

unsigned int slot

要清除的 folio 槽位

描述

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