网络文件系统缓存 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。当页面缓存变脏时,它也需要使用 cookie,并在回写完成后取消使用它。这有点棘手,并且为此做出了规定。
在对 cookie 执行读取、写入或调整大小操作时,文件系统必须首先开始操作。这会将资源复制到 holding 结构中,并将额外的 pin 放入缓存中,以阻止缓存撤回拆除正在使用的结构。然后可以发出实际操作,并且可以在完成后检测到冲突的失效。
预计文件系统将使用 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 函数还采用一个参数来指示它应如何处理磁盘内容中部分填充的区域(孔)。这可能是忽略它、跳过初始孔并在缓冲区中放置零或给出错误。
可以为读取和写入函数提供一个可选的终止函数,该函数将在完成后运行
typedef
void (*netfs_io_terminated_t)(void *priv, ssize_t transferred_or_error,
bool was_async);
如果给出了终止函数,则操作将异步运行,并且终止函数将在完成后被调用。如果未给出,则操作将同步运行。请注意,在异步情况下,操作有可能在函数返回之前完成。
读取和写入函数都在完成时结束操作,分离任何固定的资源。
如果在操作进行时发生失效,则读取操作将失败,并显示 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);
标记 span 中的所有页面后,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_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 必须保留其自身在映射上的 pin。
-
void fscache_note_page_release(struct fscache_cookie *cookie)¶
注意 netfs 页面已释放
参数
struct fscache_cookie *cookie
与文件对应的 cookie
描述
请注意,已复制到缓存的页面已发布。 这意味着将来的读取需要查看缓存以查看它是否存在。