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 並清除其標記。佔用計數保持不變。