网络文件系统缓存 API

Fscache 提供了一个 API,网络文件系统可以通过它使用本地缓存设施。该 API 围绕着一些原则进行组织

  1. 缓存逻辑上组织成卷和卷内的数据存储对象。

  2. 卷和数据存储对象由各种类型的 cookie 表示。

  3. Cookie 有键,用于区分它们与其对等项。

  4. Cookie 具有一致性数据,允许缓存确定缓存的数据是否仍然有效。

  5. I/O 尽可能异步完成。

此 API 由以下项目使用

#include <linux/fscache.h>.

概述

从网络文件系统的角度来看,fscache 层次结构分为两个级别。上层代表“卷”,下层代表“数据存储对象”。这些由两种类型的 cookie 表示,以下分别称为“卷 cookie”和“cookie”。

网络文件系统使用卷键获取卷的卷 cookie,卷键表示定义该卷的所有信息(例如,单元名称或服务器地址、卷 ID 或共享名称)。这必须呈现为可以用作目录名称的可打印字符串(即,没有“/”字符,并且不应以“.”开头)。最大名称长度比文件名组件的最大大小小 1(允许缓存后端一个字符用于其自己的目的)。

文件系统通常每个超级块都有一个卷 cookie。

然后,文件系统使用对象键获取该卷中每个文件的 cookie。对象键是二进制 blob,只需要在其父卷内是唯一的。缓存后端负责将二进制 blob 呈现为它可以使用的东西,并且可以使用哈希表、树或任何其他东西来提高其查找对象的能力。这对网络文件系统是透明的。

文件系统通常每个 inode 都有一个 cookie,并且会在 iget 中获取它,并在驱逐 cookie 时放弃它。

一旦拥有 cookie,文件系统就需要将该 cookie 标记为正在使用。这会导致 fscache 发送缓存后端,以便在后台查找/创建 cookie 的资源,检查其一致性,并在必要时将对象标记为正在修改。

文件系统通常在其文件打开例程中“使用”cookie,并在文件释放时取消使用它,并且需要在调用以在本地截断 cookie 时使用该 cookie。当 pagecache 变脏时,它*也*需要使用该 cookie,并在写回完成时取消使用它。这有点棘手,并且为此做出了规定。

当对 cookie 执行读取、写入或调整大小操作时,文件系统必须首先开始一个操作。这会将资源复制到保持结构中,并在缓存中放置额外的引脚,以防止缓存撤回破坏正在使用的结构。然后可以发出实际操作,并且可以在完成时检测到冲突的失效。

文件系统应该使用 netfslib 来访问缓存,但这实际上不是必需的,它可以直接使用 fscache I/O API。

卷注册

网络文件系统的第一步是为其想要访问的卷获取一个卷 cookie

struct fscache_volume *
fscache_acquire_volume(const char *volume_key,
                       const char *cache_name,
                       const void *coherency_data,
                       size_t coherency_len);

此函数创建一个卷 cookie,使用指定的卷键作为其名称,并记录一致性数据。

卷键必须是一个可打印的字符串,其中不包含“/”字符。它应以文件系统的名称开头,并且长度不应超过 254 个字符。它应唯一表示卷,并将与缓存中存储的内容匹配。

调用者还可以指定要使用的缓存的名称。如果指定,fscache 将查找或创建该名称的缓存 cookie,并且如果该缓存在线或上线,则将使用该名称的缓存。如果未指定缓存名称,它将使用手头第一个缓存并将名称设置为该名称。

指定的一致性数据存储在 cookie 中,并将与磁盘上存储的一致性数据匹配。如果没有提供数据,则数据指针可能为 NULL。如果一致性数据不匹配,则整个缓存卷将失效。

如果卷键已被获取的卷使用,或者如果发生分配失败,则此函数可能会返回 EBUSY 等错误。如果未启用 fscache,则它也可能返回 NULL 卷 cookie。将 NULL cookie 传递给任何需要卷 cookie 的函数是安全的。这将导致该函数不执行任何操作。

当网络文件系统完成卷操作时,应通过调用来放弃它

void fscache_relinquish_volume(struct fscache_volume *volume,
                               const void *coherency_data,
                               bool invalidate);

这将导致卷被提交或删除,如果密封,则一致性数据将设置为提供的值。一致性数据量必须与获取卷时指定的长度匹配。请注意,此卷中获得的所有数据 cookie 必须在放弃卷之前放弃。

数据文件注册

一旦有了卷 cookie,网络文件系统可以使用它来获取数据存储的 cookie

struct fscache_cookie *
fscache_acquire_cookie(struct fscache_volume *volume,
                       u8 advice,
                       const void *index_key,
                       size_t index_key_len,
                       const void *aux_data,
                       size_t aux_data_len,
                       loff_t object_size)

这将使用指定的索引键在卷中创建 cookie。索引键是给定长度的二进制 blob,对于卷必须是唯一的。这会保存到 cookie 中。对内容没有限制,但其长度不应超过最大文件名长度的约四分之三,以便进行编码。

调用者还应在 aux_data 中传入一段一致性数据。将分配大小为 aux_data_len 的缓冲区,并将一致性数据复制到其中。假定该大小在一段时间内是不变的。一致性数据用于检查缓存中数据的有效性。提供了可以更新一致性数据的函数。

还应提供要缓存的对象的​​文件大小。这可以用于修剪数据,并将与一致性数据一起存储。

此函数永远不会返回错误,但如果分配失败或未启用 fscache,则可能会返回 NULL cookie。传入 NULL 卷 cookie 并将返回的 NULL cookie 传递给任何需要它的函数是安全的。这将导致该函数不执行任何操作。

当网络文件系统完成 cookie 操作时,应通过调用来放弃它

void fscache_relinquish_cookie(struct fscache_cookie *cookie,
                               bool retire);

这将导致 fscache 提交支持 cookie 的存储或删除它。

调整数据文件大小(截断)

如果网络文件系统文件在本地通过截断调整大小,则应调用以下内容来通知缓存

void fscache_resize_cookie(struct fscache_cookie *cookie,
                           loff_t new_size);

调用者必须首先将 cookie 标记为正在使用。cookie 和新大小被传入,缓存会同步调整大小。这应该从 inode 锁下的 ->setattr() inode 操作中调用。

数据 I/O API

要通过 cookie 直接执行数据 I/O 操作,可以使用以下函数

int fscache_begin_read_operation(struct netfs_cache_resources *cres,
                                 struct fscache_cookie *cookie);
int fscache_read(struct netfs_cache_resources *cres,
                 loff_t start_pos,
                 struct iov_iter *iter,
                 enum netfs_read_from_hole read_hole,
                 netfs_io_terminated_t term_func,
                 void *term_func_priv);
int fscache_write(struct netfs_cache_resources *cres,
                  loff_t start_pos,
                  struct iov_iter *iter,
                  netfs_io_terminated_t term_func,
                  void *term_func_priv);

begin 函数设置一个操作,将所需的资源从 cookie 连接到缓存资源块。假设它没有返回错误(例如,如果给定 NULL cookie,它将返回 -ENOBUFS,否则不执行任何操作),则可以发出其他两个函数之一。

readwrite 函数启动直接 I/O 操作。两者都采用先前设置的缓存资源块、起始文件位置的指示以及描述缓冲区并指示数据量的 I/O 迭代器。

read 函数还采用一个参数来指示它应如何处理磁盘内容中的部分填充区域(孔)。这可以是忽略它、跳过初始孔并在缓冲区中放置零或给出错误。

read 和 write 函数可以给定一个可选的终止函数,该函数将在完成时运行

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

如果给定了终止函数,则操作将异步运行,并且将在完成时调用终止函数。如果未给出,则操作将同步运行。请注意,在异步情况下,操作可能会在函数返回之前完成。

read 和 write 函数在完成时都会结束操作,并分离任何固定的资源。

如果在操作进行时发生失效,则读取操作将失败并返回 ESTALE。

数据文件一致性

要请求更新 cookie 上的一致性数据和文件大小,应调用以下内容

void fscache_update_cookie(struct fscache_cookie *cookie,
                           const void *aux_data,
                           const loff_t *object_size);

这将更新 cookie 的一致性数据和/或文件大小。

数据文件失效

有时需要使包含数据的对象失效。通常,当服务器通知网络文件系统远程第三方更改时,就需要这样做——此时,文件系统必须丢弃它拥有的文件状态和缓存数据,并从服务器重新加载。

要指示应使缓存对象失效,应调用以下方法

void fscache_invalidate(struct fscache_cookie *cookie,
                        const void *aux_data,
                        loff_t size,
                        unsigned int flags);

这会增加 cookie 中的失效计数器,导致未完成的读取失败并返回 -ESTALE,根据提供的信息设置一致性数据和文件大小,阻止 cookie 上的新 I/O,并调度缓存以清除旧数据。

失效操作在工作线程中异步运行,因此不会过度阻塞。

回写资源管理

要从网络文件系统的回写将数据写入缓存,需要在修改发生时(例如,当页面被标记为脏页时)锁定所需的缓存资源,因为无法在正在退出的线程中打开文件。

提供了以下工具来管理此过程

  • 提供了一个 inode 标志 I_PINNING_FSCACHE_WB,用于指示此 inode 的 cookie 上有一个正在使用的资源。只有在持有 inode 锁时才能更改它。

  • 如果 __writeback_single_inode() 清除了 I_PINNING_FSCACHE_WB,因为所有脏页都被清除,则会将标志 unpinned_fscache_wb 放置在 writeback_control 结构中。

为了支持这一点,提供了以下函数

bool fscache_dirty_folio(struct address_space *mapping,
                         struct folio *folio,
                         struct fscache_cookie *cookie);
void fscache_unpin_writeback(struct writeback_control *wbc,
                             struct fscache_cookie *cookie);
void fscache_clear_inode_writeback(struct fscache_cookie *cookie,
                                   struct inode *inode,
                                   const void *aux);

set 函数旨在从文件系统的 dirty_folio 地址空间操作中调用。如果未设置 I_PINNING_FSCACHE_WB,它会设置该标志并增加 cookie 的使用计数(调用方必须已调用 fscache_use_cookie())。

unpin 函数旨在从文件系统的 write_inode 超级块操作中调用。如果 writeback_control 结构中设置了 unpinned_fscache_wb,它会在写入后通过释放 cookie 来清理。

clear 函数旨在从 netfs 的 evict_inode 超级块操作中调用。必须在 truncate_inode_pages_final() 之后,但在 clear_inode() 之前调用。这将清除任何挂起的 I_PINNING_FSCACHE_WB。它还允许更新一致性数据。

本地修改的缓存

如果网络文件系统有本地修改的数据想要写入缓存,它需要标记页面以指示正在进行写入操作,如果已存在标记,则需要等待其首先被移除(可能是由于已经正在进行的操作)。这可以防止多个竞争的 DIO 写入到缓存中的同一存储位置。

首先,netfs 应通过执行如下操作来确定缓存是否可用

bool caching = fscache_cookie_enabled(cookie);

如果要尝试缓存,则应等待页面,然后使用 netfs 帮助程序库提供的以下函数进行标记

void set_page_fscache(struct page *page);
void wait_on_page_fscache(struct page *page);
int wait_on_page_fscache_killable(struct page *page);

一旦跨度中的所有页面都被标记,netfs 就可以要求 fscache 调度该区域的写入

void fscache_write_to_cache(struct fscache_cookie *cookie,
                            struct address_space *mapping,
                            loff_t start, size_t len, loff_t i_size,
                            netfs_io_terminated_t term_func,
                            void *term_func_priv,
                            bool caching)

如果在到达该点之前发生错误,可以通过调用以下方法移除标记

void fscache_clear_page_bits(struct address_space *mapping,
                             loff_t start, size_t len,
                             bool caching)

在这些函数中,传入一个指向源页面附加到的映射的指针,并且 start 和 len 指示要写入的区域的大小(不一定与页面边界对齐,但必须与后备文件系统上的 DIO 边界对齐)。 caching 参数指示是否应跳过缓存,如果为 false,则这些函数不执行任何操作。

write 函数采用一些额外的参数:cookie 代表要写入的缓存对象,i_size 指示 netfs 文件的大小,term_func 指示可选的完成函数,以及 term_func_priv 将与错误或写入量一起传递给该函数。

请注意,write 函数始终以异步方式运行,并且在调用 term_func 之前,会在完成时取消标记所有页面。

页面释放和失效

Fscache 会跟踪我们是否在刚创建的缓存对象的缓存中已经有任何数据。它知道在执行写入操作并且写入的页面被 VM 释放之前,它不必执行任何读取操作,之后它必须查看缓存。

要通知 fscache 页面可能现在在缓存中,应从 release_folio 地址空间操作中调用以下函数

void fscache_note_page_release(struct fscache_cookie *cookie);

如果页面已释放(即,release_folio 返回 true)。

页面释放和页面失效还应等待页面上留下的任何标记,以表示该页面正在进行 DIO 写入操作

void wait_on_page_fscache(struct page *page);
int wait_on_page_fscache_killable(struct page *page);

API 函数参考

struct fscache_volume *fscache_acquire_volume(const char *volume_key, const char *cache_name, const void *coherency_data, size_t coherency_len)

注册一个需要缓存服务的卷

参数

const char *volume_key

卷的标识字符串

const char *cache_name

要使用的缓存名称(如果为 NULL,则使用默认缓存)

const void *coherency_data

要检查的任意一致性数据片段(如果为 NULL,则表示没有一致性数据)

size_t coherency_len

一致性数据的大小

描述

如果缓存服务可用,则将卷注册为需要缓存服务。调用者必须为该卷提供一个标识符,并且还可以指示该卷应位于哪个缓存中。如果在缓存中找到预先存在的卷条目,则一致性数据必须匹配,否则条目将失效。

成功时返回 cookie 指针,如果内存不足则返回 -ENOMEM,如果已获取同名的缓存卷,则返回 -EBUSY。请注意,“NULL”是一个有效的 cookie 指针,如果拒绝缓存,则可以返回它。

void fscache_relinquish_volume(struct fscache_volume *volume, const void *coherency_data, bool invalidate)

停止缓存卷

参数

struct fscache_volume *volume

卷 cookie

const void *coherency_data

要设置的任意一致性数据片段(如果为 NULL,则表示没有一致性数据)

bool invalidate

如果应使卷失效,则为 True

描述

指示文件系统不再需要卷的缓存服务。调用者必须在调用此方法之前释放所有文件 cookie。将更新存储的一致性数据。

获取代表缓存对象的 cookie

参数

struct fscache_volume *volume

要在其中查找/创建此 cookie 的卷

u8 advice

建议标志 (FSCACHE_COOKIE_ADV_*)

const void *index_key

此 cookie 的索引键

size_t index_key_len

索引键的大小

const void *aux_data

cookie 的辅助数据(可能为 NULL)

size_t aux_data_len

辅助数据缓冲区的大小

loff_t object_size

对象的初始大小

描述

获取一个 cookie 来表示给定缓存卷中的数据文件。

有关完整说明,请参阅网络文件系统缓存 API

请求使用附加到对象的 cookie

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

bool will_modify

如果预计本地会修改缓存

描述

请求使用附加到对象的 cookie。调用者应该告知缓存是否即将本地修改对象的内容,然后缓存可以应用已设置的策略来处理这种情况。

停止使用附加到对象的 cookie

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

const void *aux_data

更新的辅助数据(或 NULL)

const loff_t *object_size

对象的修改后大小(或 NULL)

描述

停止使用附加到对象的 cookie。当用户计数达到零时,才允许继续放弃 cookie。

将 cookie 返回给缓存,可能会丢弃它

参数

struct fscache_cookie *cookie

正在返回的 cookie

bool retire

如果 cookie 表示的缓存对象将被丢弃,则为 True

描述

此函数将 cookie 返回给缓存,如果 retire 设置为 true,则强制丢弃关联的缓存对象。

有关完整说明,请参阅网络文件系统缓存 API

请求更新缓存对象

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

const void *aux_data

cookie 的更新的辅助数据(可能为 NULL)

const loff_t *object_size

对象的当前大小(可能为 NULL)

描述

请求更新与 cookie 关联的缓存对象的索引数据。如果设置了 **aux_data**,则将首先更新 cookie 上的辅助数据;如果设置了 **object_size**,则将更新对象大小,并可能对对象进行修剪。

有关完整说明,请参阅网络文件系统缓存 API

请求调整缓存对象的大小

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

loff_t new_size

对象的新大小(可能为 NULL)

描述

请求更改对象的大小。

有关完整说明,请参阅网络文件系统缓存 API

void fscache_invalidate(struct fscache_cookie *cookie, const void *aux_data, loff_t size, unsigned int flags)

通知缓存某个对象需要失效

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

const void *aux_data

cookie 的更新的辅助数据(可能为 NULL)

loff_t size

对象的修改后大小。

unsigned int flags

失效标志 (FSCACHE_INVAL_*)

描述

通知缓存某个对象需要失效,并且应该中止它正在对缓存执行的任何检索或存储。这会增加 cookie 上的 inval_counter,调用者可以使用它在 I/O 请求完成时重新考虑它们。

如果 **flags** 设置了 FSCACHE_INVAL_DIO_WRITE,则表示这是由于直接 I/O 写入造成的,并且将导致在此 cookie 完全未使用之前禁用缓存。

有关完整说明,请参阅网络文件系统缓存 API

const struct netfs_cache_ops *fscache_operation_valid(const struct netfs_cache_resources *cres)

如果操作资源可用,则返回 true

参数

const struct netfs_cache_resources *cres

要检查的资源。

描述

如果可用,则返回指向操作表的指针;如果不可用,则返回 NULL。

int fscache_begin_read_operation(struct netfs_cache_resources *cres, struct fscache_cookie *cookie)

为 netfs 库开始读取操作

参数

struct netfs_cache_resources *cres

要执行的读取操作的缓存资源

struct fscache_cookie *cookie

表示缓存对象的 cookie

描述

代表 netfs 帮助程序库开始读取操作。**cres** 指示应该附加操作状态的缓存资源; **cookie** 指示将要访问的缓存对象。

**cres->inval_counter** 是从 **cookie->inval_counter** 设置的,以便在操作结束时进行比较。这允许调用者检测操作期间的失效。

返回

  • 0 - 成功

  • -ENOBUFS
    • 没有可用的缓存

  • 来自缓存的其他错误代码,例如 -ENOMEM。

void fscache_end_operation(struct netfs_cache_resources *cres)

结束 netfs 库的读取操作

参数

struct netfs_cache_resources *cres

读取操作的缓存资源

描述

在读取请求结束时清理资源。

int fscache_read(struct netfs_cache_resources *cres, loff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, void *term_func_priv)

开始从缓存读取。

参数

struct netfs_cache_resources *cres

要使用的缓存资源

loff_t start_pos

缓存文件中的起始文件偏移量

struct iov_iter *iter

要填充的缓冲区 - 以及长度

enum netfs_read_from_hole read_hole

如何处理数据中的空洞。

netfs_io_terminated_t term_func

完成时要调用的函数

void *term_func_priv

**term_func** 的私有数据

描述

开始从缓存读取数据。cres 表示要从中读取的缓存对象,必须事先通过调用 fscache_begin_operation() 获取。

数据被读取到迭代器 iter 中,这也表示操作的大小。start_pos 是文件中的起始位置,但如果 seek_data 设置正确,缓存可以使用 SEEK_DATA 来查找下一段数据,并将空洞写入迭代器,用零填充。

操作终止时,将调用 term_func,并提供 term_func_priv 以及写入的数据量(如果成功),否则提供错误代码。

read_hole 指示如何处理缓存中部分填充的区域。它可以是以下几种设置之一

NETFS_READ_HOLE_IGNORE - 尝试直接读取(可能返回短读取)。

NETFS_READ_HOLE_CLEAR - 查找数据,清除缓冲区中跳过的部分,然后执行与 IGNORE 相同的操作。

跳过的部分,然后执行与 IGNORE 相同的操作。

NETFS_READ_HOLE_FAIL - 如果遇到空洞,则给出 ENODATA。

int fscache_begin_write_operation(struct netfs_cache_resources *cres, struct fscache_cookie *cookie)

为 netfs 库开始写入操作

参数

struct netfs_cache_resources *cres

正在执行的写入操作的缓存资源

struct fscache_cookie *cookie

表示缓存对象的 cookie

描述

代表 netfs 辅助库开始写入操作。cres 表示应将操作状态附加到的缓存资源;cookie 表示将要访问的缓存对象。

**cres->inval_counter** 是从 **cookie->inval_counter** 设置的,以便在操作结束时进行比较。这允许调用者检测操作期间的失效。

返回

  • 0 - 成功

  • -ENOBUFS
    • 没有可用的缓存

  • 来自缓存的其他错误代码,例如 -ENOMEM。

int fscache_write(struct netfs_cache_resources *cres, loff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv)

开始写入缓存。

参数

struct netfs_cache_resources *cres

要使用的缓存资源

loff_t start_pos

缓存文件中的起始文件偏移量

struct iov_iter *iter

要写入的数据 - 以及长度

netfs_io_terminated_t term_func

完成时要调用的函数

void *term_func_priv

**term_func** 的私有数据

描述

开始写入缓存。cres 表示要写入的缓存对象,必须事先通过调用 fscache_begin_operation() 获取。

要写入的数据从迭代器 iter 中获取,这也表示操作的大小。start_pos 是文件中的起始位置。

操作终止时,将调用 term_func,并提供 term_func_priv 以及写入的数据量(如果成功),否则提供错误代码。

void fscache_clear_page_bits(struct address_space *mapping, loff_t start, size_t len, bool caching)

从一组页面清除 PG_fscache 位

参数

struct address_space *mapping

要用作来源的 netfs inode

loff_t start

mapping 中的起始位置

size_t len

要解锁的数据量

bool caching

如果已设置 PG_fscache

描述

从一系列页面清除 PG_fscache 标志,并唤醒任何正在等待的人。

void fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, loff_t start, size_t len, loff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool caching)

保存写入到缓存并清除 PG_fscache

参数

struct fscache_cookie *cookie

表示缓存对象的 cookie

struct address_space *mapping

要用作来源的 netfs inode

loff_t start

mapping 中的起始位置

size_t len

要写回的数据量

loff_t i_size

inode 的新大小

netfs_io_terminated_t term_func

完成时要调用的函数

void *term_func_priv

**term_func** 的私有数据

bool using_pgpriv2

如果我们正在使用 PG_private_2 来标记正在进行的写入

bool caching

如果我们真的想要进行缓存

描述

netfs 的辅助函数,用于将 inode 中的脏数据写入支持它的缓存对象。

startlen 描述了数据的范围。这不需要是页面对齐的,但为了满足 DIO 要求,缓存可能会将其扩展到两端的页面边界。覆盖该范围的所有页面都必须标记为 PG_fscache。

如果给定 term_func,则在完成时调用,并提供 term_func_priv。请注意,如果设置了 using_pgpriv2,则此时 PG_private_2 标志将被清除,因此 netfs 必须保留其自身对映射的固定。

void fscache_note_page_release(struct fscache_cookie *cookie)

注意 netfs 页面已释放

参数

struct fscache_cookie *cookie

与文件对应的 cookie

描述

注意,已复制到缓存的页面已释放。这意味着未来的读取将需要查看缓存中是否存在该页面。