网络文件系统缓存 API¶
Fscache 提供了一个 API,网络文件系统可以通过它使用本地缓存设施。该 API 围绕着一些原则进行组织
缓存逻辑上组织成卷和卷内的数据存储对象。
卷和数据存储对象由各种类型的 cookie 表示。
Cookie 有键,用于区分它们与其对等项。
Cookie 具有一致性数据,允许缓存确定缓存的数据是否仍然有效。
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,否则不执行任何操作),则可以发出其他两个函数之一。
read 和 write 函数启动直接 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。将更新存储的一致性数据。
-
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
参数
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。
-
void fscache_use_cookie(struct fscache_cookie *cookie, bool will_modify)¶
请求使用附加到对象的 cookie
参数
struct fscache_cookie *cookie
表示缓存对象的 cookie
bool will_modify
如果预计本地会修改缓存
描述
请求使用附加到对象的 cookie。调用者应该告知缓存是否即将本地修改对象的内容,然后缓存可以应用已设置的策略来处理这种情况。
-
void fscache_unuse_cookie(struct fscache_cookie *cookie, const void *aux_data, const loff_t *object_size)¶
停止使用附加到对象的 cookie
参数
struct fscache_cookie *cookie
表示缓存对象的 cookie
const void *aux_data
更新的辅助数据(或 NULL)
const loff_t *object_size
对象的修改后大小(或 NULL)
描述
停止使用附加到对象的 cookie。当用户计数达到零时,才允许继续放弃 cookie。
-
void fscache_relinquish_cookie(struct fscache_cookie *cookie, bool retire)¶
将 cookie 返回给缓存,可能会丢弃它
参数
struct fscache_cookie *cookie
正在返回的 cookie
bool retire
如果 cookie 表示的缓存对象将被丢弃,则为 True
描述
此函数将 cookie 返回给缓存,如果 retire 设置为 true,则强制丢弃关联的缓存对象。
有关完整说明,请参阅网络文件系统缓存 API。
-
void fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_data, const loff_t *object_size)¶
请求更新缓存对象
参数
struct fscache_cookie *cookie
表示缓存对象的 cookie
const void *aux_data
cookie 的更新的辅助数据(可能为 NULL)
const loff_t *object_size
对象的当前大小(可能为 NULL)
描述
请求更新与 cookie 关联的缓存对象的索引数据。如果设置了 **aux_data**,则将首先更新 cookie 上的辅助数据;如果设置了 **object_size**,则将更新对象大小,并可能对对象进行修剪。
有关完整说明,请参阅网络文件系统缓存 API。
-
void fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size)¶
请求调整缓存对象的大小
参数
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 中的脏数据写入支持它的缓存对象。
start 和 len 描述了数据的范围。这不需要是页面对齐的,但为了满足 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
描述
注意,已复制到缓存的页面已释放。这意味着未来的读取将需要查看缓存中是否存在该页面。