网络文件系统服务库

概述

网络文件系统服务库(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)