網路檔案系統服務庫

概述

網路檔案系統服務庫(netfslib)是一組函式,旨在幫助網路檔案系統實現 VM/VFS API 操作。它接管了正常的緩衝讀取、預讀取、寫入和回寫,並處理非緩衝和直接 I/O。

該庫支援 I/O 大小的(重新)協商和失敗 I/O 的重試,以及本地快取,並且將來會提供內容加密。

它儘可能地將檔案系統與 VM 介面更改隔離開來,並處理 VM 功能,例如大型多頁 folio。檔案系統基本上只需要提供一種執行讀寫 RPC 呼叫的方法。

I/O 在 netfslib 內部的組織方式由多個物件組成

  • 一個請求。請求用於跟蹤 I/O 的整體進度並儲存資源。結果的收集在請求級別完成。請求內的 I/O 被分成多個並行的子請求流。

  • 一個。一系列不重疊的子請求。流內的子請求不必是連續的。

  • 一個子請求。這是 I/O 的基本單元。它表示單個 RPC 呼叫或單個快取 I/O 操作。該庫將這些傳遞給檔案系統和快取來執行。

請求和流

當實際執行 I/O 時(與僅複製到頁面快取相反),netfslib 將建立一個或多個請求來跟蹤 I/O 的進度並儲存資源。

讀取操作將有一個流,該流中的子請求可能來自不同的來源,例如混合 RPC 子請求和快取子請求。

另一方面,寫入操作可能有多個流,其中每個流都針對不同的目標。例如,可能有一個流寫入本地快取,另一個流寫入伺服器。目前,只允許兩個流,但如果需要並行寫入多個伺服器,則可以增加這個數量。

寫入流中的子請求不需要與另一個寫入流中的子請求對齊或大小匹配,並且 netfslib 獨立地在源緩衝區上執行每個流中子請求的平鋪。此外,每個流可能包含與其他流中孔洞不對應的孔洞。

此外,子請求不需要對應於源/目標緩衝區中 folio 或向量的邊界。該庫處理結果的收集以及 folio 標誌和引用的爭用。

子請求

子請求是 netfslib 與使用它的檔案系統之間互動的核心。每個子請求都應對應於單個讀取或寫入 RPC 或快取操作。該庫將把一組子請求的結果拼接在一起以提供更高級別的操作。

在設定子請求時,Netfslib 與檔案系統或快取有兩種互動。首先,有一個可選的準備步驟,允許檔案系統協商子請求的限制,包括最大位元組數和最大向量數(例如,對於 RDMA)。這可能涉及與伺服器的協商(例如,cifs 需要獲取信用)。

其次,是釋出步驟,其中子請求被移交給檔案系統執行。

請注意,這兩種步驟在讀取和寫入之間略有不同

  • 對於讀取,VM/VFS 事先告訴我們請求了多少,因此該庫可以預設最大值,然後快取和檔案系統可以減少該值。在諮詢檔案系統之前,還會首先諮詢快取是否要執行讀取。

  • 對於回寫,在遍歷頁面快取之前,不知道有多少要寫入,因此該庫不設定限制。

一旦子請求完成,檔案系統或快取就會通知該庫完成情況,然後呼叫收集。根據請求是同步還是非同步,結果的收集將在應用程式執行緒或工作佇列中完成。

結果收集和重試

隨著子請求的完成,結果由該庫收集和整理,並且逐步執行 folio 解鎖(如果適用)。請求完成後,將呼叫非同步完成(如果再次適用)。檔案系統可以向該庫提供臨時進度報告,以便在可能的情況下更早地進行 folio 解鎖。

如果任何子請求失敗,netfslib 可以重試它們。它將等待所有子請求完成,讓檔案系統有機會擺弄請求持有的資源/狀態,並在重新準備和重新發布子請求之前嘗試子請求。

這允許更改流中連續失敗的子請求集的平鋪,根據需要新增更多子請求或放棄多餘的子請求(例如,如果網路大小發生變化或伺服器決定需要更小的塊)。

此外,如果一個或多個連續的快取讀取子請求失敗,該庫將把它們傳遞給檔案系統來執行,根據需要重新協商和重新平鋪它們以適應檔案系統的引數而不是快取的引數。

本地快取

netfslib 透過 fscache 提供的服務之一是選擇在本地磁碟上快取從網路檔案系統獲取/寫入的資料的副本。如果 cookie 連線到 netfs_inode,該庫將自動代表檔案系統管理資料的儲存、檢索和一些失效。

請注意,本地快取過去使用 PG_private_2(別名為 PG_fscache)來跟蹤正在寫入快取的頁面,但現在已棄用,因為 PG_private_2 將被刪除。

相反,從伺服器讀取的 folio(快取中沒有資料)將被標記為髒頁,並將 folio->private 設定為特殊值 (NETFS_FOLIO_COPY_TO_CACHE) 並留給回寫寫入。如果 folio 在那之前被修改,則特殊值將被清除,並且寫入將變為正常髒頁。

發生回寫時,如此標記的 folio 將僅寫入快取,而不寫入伺服器。回寫透過使用兩個流來處理混合的僅快取寫入以及伺服器和快取寫入,一個流傳送到快取,另一個流傳送到伺服器。伺服器流將存在對應於這些 folio 的間隙。

內容加密 (fscrypt)

儘管尚未這樣做,但在某個時候,netfslib 將獲得代表網路檔案系統(例如,Ceph)執行客戶端內容加密的能力。如果適用,可以使用 fscrypt 來實現此目的(可能不適用 - 例如,cifs)。

資料將以加密方式儲存在本地快取中,加密方式與寫入伺服器的資料相同,並且該庫將根據需要強制執行反彈緩衝和 RMW 週期。

每個 Inode 的上下文

網路檔案系統助手庫需要一個地方來儲存一些狀態,供其在每個正在幫助管理的 netfs inode 上使用。為此,定義了一個上下文結構

struct netfs_inode {
        struct inode inode;
        const struct netfs_request_ops *ops;
        struct fscache_cookie * cache;
        loff_t remote_i_size;
        unsigned long flags;
        ...
};

想要使用 netfslib 的網路檔案系統必須將其中一個放在其 inode 包裝器結構中,而不是 VFS struct inode。這可以用類似於以下方式完成

struct my_inode {
        struct netfs_inode netfs; /* Netfslib context and vfs inode */
        ...
};

這允許 netfslib 透過使用來自 inode 指標的 container_of() 來查詢其狀態,從而允許 VFS/VM 操作表直接指向 netfslib 助手函式。

該結構包含以下檔案系統感興趣的欄位

  • inode

    VFS inode 結構。

  • ops

    網路檔案系統提供給 netfslib 的操作集。

  • cache

    本地快取 cookie,如果未啟用快取,則為 NULL。如果停用 fscache,則此欄位不存在。

  • remote_i_size

    伺服器上檔案的大小。如果進行了本地修改但尚未寫回,則這與 inode->i_size 不同。

  • flags

    一組標誌,其中一些檔案系統可能感興趣

    • NETFS_ICTX_MODIFIED_ATTR

      如果 netfslib 修改了 mtime/ctime,則設定。檔案系統可以隨意忽略或清除它。

    • NETFS_ICTX_UNBUFFERED

      對檔案執行非緩衝 I/O。類似於直接 I/O,但沒有對齊限制。如果需要,將執行 RMW。除非也使用 mmap(),否則不會使用頁面快取。

    • NETFS_ICTX_WRITETHROUGH

      對檔案執行直寫快取。I/O 將設定為並且在對頁面快取進行緩衝寫入時分派。mmap() 執行正常的回寫操作。

    • NETFS_ICTX_SINGLE_NO_UPLOAD

      如果檔案具有必須以單次讀取方式完全讀取且不得寫回伺服器的整體內容,但可以快取(例如,AFS 目錄),則設定。

Inode 上下文助手函式

為了幫助處理每個 inode 的上下文,提供了一些助手函式。首先,是一個對上下文執行基本初始化並設定操作表指標的函式

void netfs_inode_init(struct netfs_inode *ctx,
                      const struct netfs_request_ops *ops);

然後是一個從 VFS inode 結構轉換為 netfs 上下文的函式

struct netfs_inode *netfs_inode(struct inode *inode);

最後,是一個從附加到 inode 的上下文中獲取快取 cookie 指標的函式(如果停用 fscache,則為 NULL)

struct fscache_cookie *netfs_i_cookie(struct netfs_inode *ctx);

Inode 鎖定

提供了一些函式來管理 I/O 的 i_rwsem 鎖定,並有效地擴充套件它以提供更多獨立的排除類

int netfs_start_io_read(struct inode *inode);
void netfs_end_io_read(struct inode *inode);
int netfs_start_io_write(struct inode *inode);
void netfs_end_io_write(struct inode *inode);
int netfs_start_io_direct(struct inode *inode);
void netfs_end_io_direct(struct inode *inode);

排除分為四個獨立的類

  1. 緩衝讀取和寫入。

    緩衝讀取可以相互併發執行,也可以與緩衝寫入併發執行,但緩衝寫入不能相互併發執行。

  2. 直接讀取和寫入。

    直接(和非緩衝)讀取和寫入可以併發執行,因為它們不共享本地緩衝(即頁面快取),並且在網路檔案系統中,預計會在伺服器上管理排除(儘管對於 Ceph 來說可能並非如此)。

  3. 其他主要 inode 修改操作(例如,truncate、fallocate)。

    這些應該只直接訪問 i_rwsem。

  4. mmap()。

    mmap 訪問可能會與其他任何類併發操作。它們可能構成檔案內環回 DIO 讀/寫的緩衝區。可能允許在非緩衝檔案上使用它們。

Inode 回寫

當 inode 變髒時,Netfslib 會在 inode 上固定資源以供將來回寫(例如,固定 fscache cookie 的使用)。但是,此固定需要仔細管理。為了管理固定,發生以下序列

  1. 當固定開始時(例如,當 folio 變髒時),如果快取處於活動狀態以阻止丟棄快取結構並從快取空間中剔除,netfslib 會設定 inode 狀態標誌 I_PINNING_NETFS_WB。如果已設定該標誌,這也可以防止重新獲取快取資源。

  2. 然後在 VM 中的 inode 回寫期間,在 inode 鎖內清除此標誌 - 並且設定它的事實被轉移到 struct writeback_control 中的 ->unpinned_netfs_wb

  3. 如果現在設定了 ->unpinned_netfs_wb,則強制執行 write_inode 過程。

  4. 呼叫檔案系統的 ->write_inode() 函式來執行清理。

  5. 檔案系統呼叫 netfs 來執行其清理。

為了執行清理,netfslib 提供了一個執行資源取消固定的函式

int netfs_unpin_writeback(struct inode *inode, struct writeback_control *wbc);

如果檔案系統不需要執行任何其他操作,則可以將其設定為其 .write_inode 方法。

此外,如果刪除 inode,則可能不會呼叫檔案系統的 write_inode 方法,因此

void netfs_clear_inode_writeback(struct inode *inode, const void *aux);

必須在呼叫 clear_inode() 之前->evict_inode() 呼叫。

高階 VFS API

Netfslib 為檔案系統提供許多 API 呼叫集,用於將 VFS 操作委託給它。Netfslib 反過來將回調檔案系統和快取以協商 I/O 大小、發出 RPC 並提供在各個時間點進行干預的位置。

未鎖定讀/寫迭代

第一個 API 集用於在透過標準 VFS read/write_iter 方法呼叫檔案系統時將操作委託給 netfslib

ssize_t netfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter);
ssize_t netfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from);
ssize_t netfs_buffered_read_iter(struct kiocb *iocb, struct iov_iter *iter);
ssize_t netfs_unbuffered_read_iter(struct kiocb *iocb, struct iov_iter *iter);
ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from);

可以將它們直接分配給 .read_iter.write_iter。它們自己執行 inode 鎖定,並且前兩個將在緩衝 I/O 和 DIO 之間適當地切換。

預鎖定讀/寫迭代

第二個 API 集用於在透過標準 VFS 方法呼叫檔案系統時將操作委託給 netfslib,但在仍然在鎖定部分內時,需要在呼叫 netfslib 之前或之後執行一些其他操作(例如,Ceph 協商上限)。非緩衝讀取函式是

ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_iter *iter);

不得將此直接分配給 .read_iter,並且檔案系統負責在呼叫它之前執行 inode 鎖定。對於緩衝讀取,檔案系統應使用 filemap_read()

有三個用於寫入的函式

ssize_t netfs_buffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *from,
                                         struct netfs_group *netfs_group);
ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
                            struct netfs_group *netfs_group);
ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *iter,
                                           struct netfs_group *netfs_group);

不得將這些直接分配給 .write_iter,並且檔案系統負責在呼叫它們之前執行 inode 鎖定。

前兩個函式用於緩衝寫入;第一個只是新增一些標準寫入檢查並跳轉到第二個,但是如果檔案系統想要自己進行檢查,它可以直接使用第二個。第三個函式用於非緩衝或 DIO 寫入。

在所有三個寫入函式上,都有一個回寫組指標(如果檔案系統未使用此指標,則應為 NULL)。回寫組在 folio 修改時設定。如果要修改的 folio 已經用不同的組標記,則首先重新整理它。回寫 API 允許回寫特定組。

記憶體對映 I/O API

提供了一個用於支援 mmap()’d I/O 的 API

vm_fault_t netfs_page_mkwrite(struct vm_fault *vmf, struct netfs_group *netfs_group);

這允許檔案系統將 .page_mkwrite 委託給 netfslib。檔案系統在呼叫它之前不應獲取 inode 鎖,但是,與上面的鎖定寫入函式一樣,這確實採用了回寫組指標。如果要使頁面可寫入的頁面屬於不同的組,則首先重新整理它。

整體檔案 API

還有一個特殊的 API 集,用於必須在單個 RPC 中讀取(並且不寫回)內容的檔案,並且作為整體 blob 維護(例如,AFS 目錄),儘管它可以儲存在本地快取中並進行更新

ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_iter *iter);
void netfs_single_mark_inode_dirty(struct inode *inode);
int netfs_writeback_single(struct address_space *mapping,
                           struct writeback_control *wbc,
                           struct iov_iter *iter);

第一個函式從檔案讀取到給定緩衝區中,如果資料快取在那裡,則優先從快取讀取;第二個函式允許將 inode 標記為髒,從而導致稍後的回寫;第三個函式可以從回寫程式碼中呼叫,以將資料寫入快取(如果有)。

如果要使用此 API,則應將 inode 標記為 NETFS_ICTX_SINGLE_NO_UPLOAD。回寫函式需要緩衝區為 ITER_FOLIOQ 型別。

高階 VM API

Netfslib 還為檔案系統提供許多 API 呼叫集,用於將 VM 操作委託給它。同樣,Netfslib 反過來將回調檔案系統和快取以協商 I/O 大小、發出 RPC 並提供在各個時間點進行干預的位置

void netfs_readahead(struct readahead_control *);
int netfs_read_folio(struct file *, struct folio *);
int netfs_writepages(struct address_space *mapping,
                     struct writeback_control *wbc);
bool netfs_dirty_folio(struct address_space *mapping, struct folio *folio);
void netfs_invalidate_folio(struct folio *folio, size_t offset, size_t length);
bool netfs_release_folio(struct folio *folio, gfp_t gfp);

這些是 address_space_operations 方法,可以直接在操作表中設定。

已棄用的 PG_private_2 API

還有一個用於仍然使用 ->write_begin 方法的檔案系統的已棄用函式

int netfs_write_begin(struct netfs_inode *inode, struct file *file,
                      struct address_space *mapping, loff_t pos, unsigned int len,
                      struct folio **_folio, void **_fsdata);

它使用已棄用的 PG_private_2 標誌,因此不應使用。

I/O 請求 API

I/O 請求 API 包含檔案系統可能需要使用的一些結構和一些函式。

請求結構

請求結構管理整個請求,代表檔案系統儲存一些資源和狀態,並跟蹤結果的收集

struct netfs_io_request {
        enum netfs_io_origin    origin;
        struct inode            *inode;
        struct address_space    *mapping;
        struct netfs_group      *group;
        struct netfs_io_stream  io_streams[];
        void                    *netfs_priv;
        void                    *netfs_priv2;
        unsigned long long      start;
        unsigned long long      len;
        unsigned long long      i_size;
        unsigned int            debug_id;
        unsigned long           flags;
        ...
};

許多欄位供內部使用,但此處顯示的欄位是檔案系統感興趣的

  • origin

    請求的來源(預讀取、read_folio、DIO 讀取、回寫等)。

  • inode

  • mapping

    正在從中讀取的檔案的 inode 和地址空間。對映可能會或可能不會指向 inode->i_data。

  • group

    此請求正在處理的回寫組,如果為 NULL。這儲存了對組的引用。

  • io_streams

    可用於請求的並行子請求流。目前有兩個可用,但將來可能會使其可擴充套件。NR_IO_STREAMS 指示陣列的大小。

  • netfs_priv

  • netfs_priv2

    網路檔案系統的私有資料。此值可以傳遞給助手函式,也可以在請求期間設定。

  • start

  • len

    讀取請求的開始位置的檔案位置和長度。這些可能會被 ->expand_readahead() 操作更改。

  • i_size

    請求開始時檔案的大小。

  • debug_id

    分配給此操作的數字,可以在跟蹤行中顯示以供參考。

  • flags

    用於管理和控制請求操作的標誌。其中一些可能是檔案系統感興趣的

    • NETFS_RREQ_RETRYING

      Netfslib 在生成重試時設定此標誌。

    • NETFS_RREQ_PAUSE

      檔案系統可以設定此標誌以請求暫停庫的子請求釋出迴圈 - 但需要小心,因為 netfslib 也可能設定它。

    • NETFS_RREQ_NONBLOCK

    • NETFS_RREQ_BLOCKED

      Netfslib 設定第一個以指示呼叫者設定了非阻塞模式,檔案系統可以設定第二個以指示它本必須阻塞。

    • NETFS_RREQ_USE_PGPRIV2

      如果檔案系統想要使用 PG_private_2 來跟蹤 folio 是否正在寫入快取,則可以設定此標誌。這已棄用,因為 PG_private_2 即將消失。

如果檔案系統想要比此結構提供的更多的私有資料,那麼它應該包裝它並提供自己的分配器。

流結構

請求由一個或多個並行流組成,每個流可能都針對不同的目標。

對於讀取請求,僅使用流 0。這可以包含針對不同來源的子請求的混合。對於寫入請求,流 0 用於伺服器,流 1 用於快取。對於緩衝回寫,除非遇到正常的髒 folio,否則不會啟用流 0,此時將呼叫 ->begin_writeback(),檔案系統可以標記流可用。

流結構如下所示

struct netfs_io_stream {
        unsigned char           stream_nr;
        bool                    avail;
        size_t                  sreq_max_len;
        unsigned int            sreq_max_segs;
        unsigned int            submit_extendable_to;
        ...
};

一些成員可供檔案系統訪問/使用

  • stream_nr

    請求中流的編號。

  • avail

    如果流可用,則為 True。如果在 ->begin_writeback() 中,檔案系統應在流零上設定此標誌。

  • sreq_max_len

  • sreq_max_segs

    這些由檔案系統或快取在每個子請求的 ->prepare_read() 或 ->prepare_write() 中設定,以指示該子請求可以支援的最大位元組數和可選的最大段數(如果不為 0)。

  • submit_extendable_to

    考慮到可用的緩衝區,子請求可以向上舍入到 EOF 之外的大小。這允許快取確定它是否可以執行跨越 EOF 標記的 DIO 讀取或寫入。

子請求結構

I/O 的單個單元由子請求結構管理。這些表示整體請求的切片並獨立執行

struct netfs_io_subrequest {
        struct netfs_io_request *rreq;
        struct iov_iter         io_iter;
        unsigned long long      start;
        size_t                  len;
        size_t                  transferred;
        unsigned long           flags;
        short                   error;
        unsigned short          debug_index;
        unsigned char           stream_nr;
        ...
};

每個子請求都應訪問單個源,儘管該庫將處理從一種源型別回退到另一種源型別。成員是

  • rreq

    指向讀取請求的指標。

  • io_iter

    一個 I/O 迭代器,表示要讀取或寫入的緩衝區的切片。

  • start

  • len

    此讀取請求切片的開始位置的檔案位置和長度。

  • transferred

    到目前為止為此子請求傳輸的資料量。這應新增到此子請求的此釋出所進行的傳輸的長度中。如果這小於 len,則可以重新發布子請求以繼續。

  • flags

    用於管理子請求的標誌。檔案系統或快取對此感興趣

    • NETFS_SREQ_MADE_PROGRESS

      由檔案系統設定以指示已讀取或寫入至少一個位元組的資料。

    • NETFS_SREQ_HIT_EOF

      如果讀取命中了檔案的 EOF,則檔案系統應設定此標誌(在這種情況下,transferred 應在 EOF 處停止)。Netfslib 可能會將子請求擴充套件到包含 EOF 的 folio 的大小,以防發生第三方更改或 DIO 讀取可能要求的比可用的更多。該庫將清除任何多餘的頁面快取。

    • NETFS_SREQ_CLEAR_TAIL

      檔案系統可以設定此標誌以指示應清除切片的其餘部分,從傳輸到 len。如果設定了 HIT_EOF,則不要設定。

    • NETFS_SREQ_NEED_RETRY

      檔案系統可以設定此標誌以告訴 netfslib 重試子請求。

    • NETFS_SREQ_BOUNDARY

      檔案系統可以在子請求上設定此標誌,以指示它在檔案系統結構的邊界處結束(例如,在 Ceph 物件的末尾)。它告訴 netfslib 不要跨它重新平鋪子請求。

  • error

    這用於檔案系統儲存子請求的結果。如果成功,應設定為 0,否則設定為負錯誤程式碼。

  • debug_index

  • stream_nr

    分配給此切片的數字,可以在跟蹤行中顯示以供參考,以及它所屬的請求流的編號。

如果需要,檔案系統可以獲取並放置給定子請求上的額外引用

void netfs_get_subrequest(struct netfs_io_subrequest *subreq,
                          enum netfs_sreq_ref_trace what);
void netfs_put_subrequest(struct netfs_io_subrequest *subreq,
                          enum netfs_sreq_ref_trace what);

使用 netfs 跟蹤程式碼來指示原因。但是,必須小心,因為一旦子請求的控制權返回給 netfslib,就可以重新發布/重試相同的子請求。

檔案系統方法

檔案系統在 netfs_inode 中設定一個操作表供 netfslib 使用

struct netfs_request_ops {
        mempool_t *request_pool;
        mempool_t *subrequest_pool;
        int (*init_request)(struct netfs_io_request *rreq, struct file *file);
        void (*free_request)(struct netfs_io_request *rreq);
        void (*free_subrequest)(struct netfs_io_subrequest *rreq);
        void (*expand_readahead)(struct netfs_io_request *rreq);
        int (*prepare_read)(struct netfs_io_subrequest *subreq);
        void (*issue_read)(struct netfs_io_subrequest *subreq);
        void (*done)(struct netfs_io_request *rreq);
        void (*update_i_size)(struct inode *inode, loff_t i_size);
        void (*post_modify)(struct inode *inode);
        void (*begin_writeback)(struct netfs_io_request *wreq);
        void (*prepare_write)(struct netfs_io_subrequest *subreq);
        void (*issue_write)(struct netfs_io_subrequest *subreq);
        void (*retry_request)(struct netfs_io_request *wreq,
                              struct netfs_io_stream *stream);
        void (*invalidate_cache)(struct netfs_io_request *wreq);
};

該表以指向記憶體池的一對可選指標開頭,可以從記憶體池中分配請求和子請求。如果未給出這些指標,netfslib 將使用其預設池。如果檔案系統將其自己的較大結構包裝在 netfs 結構中,則它需要使用其自己的池。Netfslib 將直接從池中分配。

表中定義的方法是

  • init_request()

  • free_request()

  • free_subrequest()

    [可選] 檔案系統可以實現這些方法來初始化或清理它附加到請求或子請求的任何資源。

  • expand_readahead()

    [可選] 呼叫此方法允許檔案系統擴充套件預讀取請求的大小。檔案系統可以在兩個方向上擴充套件請求,儘管它必須保留初始區域,因為該區域可能表示已經進行的分配。如果啟用了本地快取,則首先可以擴充套件請求。

    擴充套件透過更改請求結構中的 ->start 和 ->len 來傳遞。請注意,如果進行任何更改,則 ->len 必須至少增加與 ->start 減少的量一樣多。

  • prepare_read()

    [可選] 呼叫此方法允許檔案系統限制子請求的大小。它還可以限制迭代器中各個區域的數量,例如 RDMA 所需的數量。此資訊應在流零中設定

    rreq->io_streams[0].sreq_max_len
    rreq->io_streams[0].sreq_max_segs
    

    例如,檔案系統可以使用此方法來拆分必須跨多個伺服器拆分的請求,或將多個讀取放入飛行中。

    成功時應返回零,否則返回錯誤程式碼。

  • issue_read()

    [必需] Netfslib 呼叫此方法以將子請求分派到伺服器進行讀取。在子請求中,->start、->len 和 ->transferred 指示應從伺服器讀取哪些資料,->io_iter 指示要使用的緩衝區。

    沒有返回值;應呼叫 netfs_read_subreq_terminated() 函式以指示子請求已完成。應在完成之前更新 ->error、->transferred 和 ->flags。終止可以非同步完成。

    注意:檔案系統不得處理設定 folio uptodate、解鎖它們或刪除它們的引用 - 該庫處理此問題,因為它可能必須拼接多個子請求的結果,這些子請求以不同方式重疊 folio 集。

  • done()

    [可選] 在讀取請求中的所有 folio 都已解鎖(並且已標記為 uptodate,如果適用)後呼叫此方法。

  • update_i_size()

    [可選] Netfslib 在寫入路徑中的各個點呼叫此方法,以要求檔案系統更新其檔案大小概念。如果未給出,netfslib 將設定 i_size 和 i_blocks 並更新本地快取 cookie。

  • post_modify()

    [可選] 在 netfslib 寫入頁面快取之後,或者當它允許將 mmap’d 頁面標記為可寫入時呼叫此方法。

  • begin_writeback()

    [可選] 如果 Netfslib 發現未簡單標記為 NETFS_FOLIO_COPY_TO_CACHE 的髒頁面,則在處理回寫請求時呼叫此方法,指示必須將其寫入伺服器。這允許檔案系統僅在知道必須執行寫入時才設定回寫資源。

  • prepare_write()

    [可選] 呼叫此方法允許檔案系統限制子請求的大小。它還可以限制迭代器中各個區域的數量,例如 RDMA 所需的數量。此資訊應在子請求所屬的流中設定

    rreq->io_streams[subreq->stream_nr].sreq_max_len
    rreq->io_streams[subreq->stream_nr].sreq_max_segs
    

    例如,檔案系統可以使用此方法來拆分必須跨多個伺服器拆分的請求,或將多個寫入放入飛行中。

    不允許返回錯誤。相反,如果發生故障,必須呼叫 netfs_prepare_write_failed()

  • issue_write()

    [必需] 用於將子請求分派到伺服器進行寫入。在子請求中,->start、->len 和 ->transferred 指示應寫入伺服器哪些資料,->io_iter 指示要使用的緩衝區。

    沒有返回值;應呼叫 netfs_write_subreq_terminated() 函式以指示子請求已完成。應在完成之前更新 ->error、->transferred 和 ->flags。終止可以非同步完成。

    注意:檔案系統不得處理刪除操作中涉及的 folio 上的髒或回寫標記,並且不應獲取它們的引用或固定,而應將保留交給 netfslib。

  • retry_request()

    [可選] Netfslib 在重試周期的開始時呼叫此方法。這允許檔案系統檢查請求的狀態、指示流中的子請求以及其自身的資料,並進行調整或重新協商資源。

  • invalidate_cache()

    [可選] 如果寫入本地快取失敗,Netfslib 會呼叫此方法以使儲存在本地快取中的資料失效,從而提供 netfs 無法提供的更新的連貫性資料。

終止子請求

當子請求完成時,快取或子請求可以呼叫一些函式來通知 netfslib 狀態更改。提供了一個函式在準備階段同步終止寫入子請求

  • void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq);

    指示 ->prepare_write() 呼叫失敗。 error 欄位應該已經被更新。

請注意, ->prepare_read() 可能會返回一個錯誤,因為讀取可以簡單地被中止。處理寫回失敗則比較棘手。

其他函式用於已被髮出的子請求

  • void netfs_read_subreq_terminated(struct netfs_io_subrequest *subreq);

    告知 netfslib 一個讀取子請求已經終止。 errorflagstransferred 欄位應該已經被更新。

  • void netfs_write_subrequest_terminated(void *\_op, ssize_t transferred_or_error);

    告知 netfslib 一個寫入子請求已經終止。可以傳入已處理的資料量或負的錯誤程式碼。這可以用作 kiocb 完成函式。

  • void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq);

    這個函式用於選擇性地更新 netfslib 關於讀取的增量進度,允許一些檔案頁提前解鎖,並且實際上不會終止子請求。 transferred 欄位應該已經被更新。

本地快取 API

Netfslib 為本地快取的實現提供了一個單獨的 API,儘管它提供了一些與檔案系統請求 API 有些相似的例程。

首先, netfs_io_request 物件包含一個供快取掛載其狀態的位置

struct netfs_cache_resources {
        const struct netfs_cache_ops    *ops;
        void                            *cache_priv;
        void                            *cache_priv2;
        unsigned int                    debug_id;
        unsigned int                    inval_counter;
};

這包含一個操作表指標和兩個私有指標,以及用於跟蹤目的的 fscache cookie 的除錯 ID 和一個透過呼叫 fscache_invalidate() 來啟動的失效計數器,允許在完成後使快取子請求失效。

快取操作表如下所示

struct netfs_cache_ops {
        void (*end_operation)(struct netfs_cache_resources *cres);
        void (*expand_readahead)(struct netfs_cache_resources *cres,
                                 loff_t *_start, size_t *_len, loff_t i_size);
        enum netfs_io_source (*prepare_read)(struct netfs_io_subrequest *subreq,
                                             loff_t i_size);
        int (*read)(struct netfs_cache_resources *cres,
                    loff_t start_pos,
                    struct iov_iter *iter,
                    bool seek_data,
                    netfs_io_terminated_t term_func,
                    void *term_func_priv);
        void (*prepare_write_subreq)(struct netfs_io_subrequest *subreq);
        void (*issue_write)(struct netfs_io_subrequest *subreq);
};

帶有一個終止處理函式指標

typedef void (*netfs_io_terminated_t)(void *priv,
                                      ssize_t transferred_or_error,
                                      bool was_async);

表中定義的方法是

  • end_operation()

    [必需] 在讀取請求結束時被呼叫以清理資源。

  • expand_readahead()

    [可選] 在預讀操作開始時被呼叫,以允許快取在任一方向擴充套件請求。這允許快取根據快取粒度適當地調整請求大小。

  • prepare_read()

    [必需] 被呼叫以配置請求的下一個切片。子請求中的 ->start 和 ->len 指示下一個切片的位置和大小;快取可以減小長度以匹配其粒度要求。

    該函式被傳遞指向起始位置和長度的指標作為引數,以及檔案大小以供參考,並適當地調整起始位置和長度。它應該返回以下之一

    • NETFS_FILL_WITH_ZEROES

    • NETFS_DOWNLOAD_FROM_SERVER

    • NETFS_READ_FROM_CACHE

    • NETFS_INVALID_READ

    以指示切片應該被清除,還是應該從伺服器下載或從快取讀取 - 或者是否應該在當前點放棄切片。

  • read()

    [必需] 被呼叫以從快取讀取。起始檔案偏移量與一個讀取到的迭代器一起給出,該迭代器也給出了長度。可以給出一個提示,要求它從起始位置向前查詢資料。

    還提供了一個指向終止處理函式和私有資料的指標,以傳遞給該函式。終止函式應該被呼叫,並帶有傳輸的位元組數或錯誤程式碼,以及一個標誌,指示終止是否肯定發生在呼叫者的上下文中。

  • prepare_write_subreq()

    [必需] 呼叫此函式以允許快取限制子請求的大小。它還可以限制迭代器中單個區域的數量,例如 DIO/DMA 所需的數量。此資訊應設定在子請求所屬的流上

    rreq->io_streams[subreq->stream_nr].sreq_max_len
    rreq->io_streams[subreq->stream_nr].sreq_max_segs
    

    例如,檔案系統可以使用此方法來拆分必須跨多個伺服器拆分的請求,或將多個寫入放入飛行中。

    不允許返回錯誤。如果發生故障,則必須呼叫 netfs_prepare_write_failed()

  • issue_write()

    [必需] 這用於將子請求分派到快取以進行寫入。在子請求中, ->start、 ->len 和 ->transferred 指示應寫入快取的資料,並且 ->io_iter 指示要使用的緩衝區。

    沒有返回值;應呼叫 netfs_write_subreq_terminated() 函式以指示子請求已完成。應在完成之前更新 ->error、->transferred 和 ->flags。終止可以非同步完成。

API 函式參考

void folio_start_private_2(struct folio *folio)

開始在檔案頁上進行 fscache 寫入。[已棄用]

引數

struct folio *folio

檔案頁。

描述

在將檔案頁寫入本地快取之前,請呼叫此函式。不允許在第一個寫入完成之前啟動第二個寫入。

請注意,不再應使用此功能。

struct netfs_inode *netfs_inode(struct inode *inode)

從 inode 獲取 netfs inode 上下文

引數

struct inode *inode

要查詢的 inode

描述

從網路檔案系統的 inode 獲取 netfs lib inode 上下文。上下文結構應直接跟在 VFS inode 結構之後。

void netfs_inode_init(struct netfs_inode *ctx, const struct netfs_request_ops *ops, bool use_zero_point)

初始化 netfslib inode 上下文

引數

struct netfs_inode *ctx

要初始化的 netfs inode

const struct netfs_request_ops *ops

netfs 的操作列表

bool use_zero_point

如果為 True,則使用 zero_point 讀取最佳化

描述

初始化 netfs 庫上下文結構。這應直接跟在 VFS inode 結構之後。

void netfs_resize_file(struct netfs_inode *ctx, loff_t new_i_size, bool changed_on_server)

注意,檔案已調整大小

引數

struct netfs_inode *ctx

正在調整大小的 netfs inode

loff_t new_i_size

新的檔案大小

bool changed_on_server

已將更改應用於伺服器

描述

通知 netfs lib 檔案已調整大小,以便它可以調整其狀態。

從 inode 獲取快取 cookie

引數

struct netfs_inode *ctx

要查詢的 netfs inode

描述

從網路檔案系統的 inode 獲取快取 cookie(如果已啟用)。

void netfs_wait_for_outstanding_io(struct inode *inode)

等待未完成的 I/O 完成

引數

struct inode *inode

要等待的 netfs inode

描述

等待任何型別的未完成 I/O 請求完成。這旨在從 inode 回收例程中呼叫。這確保了在允許 inode 被清理之前,這些請求持有的任何資源都將被清理。

void netfs_readahead(struct readahead_control *ractl)

用於管理讀取請求的助手

引數

struct readahead_control *ractl

預讀請求的描述

描述

透過儘可能從快取中提取資料(如果可能),否則從 netfs 中提取資料來滿足預讀請求。超出 EOF 的空間將填充為零。來自不同來源的多個 I/O 請求將被合併在一起。如有必要,可以向任一方向擴充套件預讀視窗,以實現更方便的 RPC 對齊或使快取在快取中儲存可行。

呼叫 netfs 必須在呼叫此函式之前初始化與 vfs inode 相鄰的 netfs 上下文。

無論是否啟用快取,這都是可用的。

int netfs_read_folio(struct file *file, struct folio *folio)

用於管理 read_folio 請求的助手

引數

struct file *file

要讀取的檔案

struct folio *folio

要讀取的檔案頁

描述

透過儘可能從快取中提取資料(如果可能),否則從 netfs 中提取資料來滿足 read_folio 請求。超出 EOF 的空間將填充為零。來自不同來源的多個 I/O 請求將被合併在一起。

呼叫 netfs 必須在呼叫此函式之前初始化與 vfs inode 相鄰的 netfs 上下文。

無論是否啟用快取,這都是可用的。

int netfs_write_begin(struct netfs_inode *ctx, struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, struct folio **_folio, void **_fsdata)

用於準備寫入的助手 [已棄用]

引數

struct netfs_inode *ctx

netfs 上下文

struct file *file

要讀取的檔案

struct address_space *mapping

要從中讀取的對映

loff_t pos

寫入將開始的檔案位置

unsigned int len

寫入的長度(可能會超出所選檔案頁的末尾)

struct folio **_folio

將結果檔案頁放置在哪裡

void **_fsdata

netfs 儲存 cookie 的位置

描述

透過儘可能從快取中提取資料(如果可能),否則從 netfs 中提取資料來預先讀取用於寫入開始請求的資料。超出 EOF 的空間將填充為零。來自不同來源的多個 I/O 請求將被合併在一起。

呼叫 netfs 必須提供一個操作表,其中只有一個操作 issue_read 是強制性的。

可以提供 check_write_begin() 操作,以便在獲取和鎖定檔案頁後檢查和重新整理衝突的寫入。它會傳遞一個指向 fsdata cookie 的指標,該指標會返回到 VM 並傳遞給 write_end。允許休眠。如果請求應該繼續,則應返回 0,否則可能會返回一個錯誤。它也可以解鎖並放置檔案頁,前提是它將 *foliop 設定為 NULL,在這種情況下,返回 0 將導致重新獲取檔案頁並重試該過程。

呼叫 netfs 必須在呼叫此函式之前初始化與 vfs inode 相鄰的 netfs 上下文。

無論是否啟用快取,這都是可用的。

請注意,這應被視為已棄用,應改為使用 netfs_perform_write()。

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

檔案系統緩衝 I/O 讀取例程

引數

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 netfs_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)