网络文件系统辅助库¶
概述¶
网络文件系统辅助库是一组旨在帮助网络文件系统实现 VM/VFS 操作的函数。目前,它仅包括将各种 VM 缓冲读取操作转换为从服务器读取的请求。但是,辅助库还可以介入其他服务,例如本地缓存或本地数据加密。
请注意,库模块不直接链接本地缓存,因此必须由 netfs 提供访问权限。
每个 Inode 上下文¶
网络文件系统辅助库需要一个地方来存储其在每个要管理的 netfs inode 上使用的一小部分状态。为此,定义了一个上下文结构
struct netfs_inode {
struct inode inode;
const struct netfs_request_ops *ops;
struct fscache_cookie *cache;
};
想要使用 netfs lib 的网络文件系统必须将其放置在其 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,则此字段不存在。
Inode 上下文辅助函数¶
为了帮助处理每个 inode 的上下文,提供了一些辅助函数。首先,一个函数对上下文执行基本初始化并设置操作表指针
void netfs_inode_init(struct netfs_inode *ctx,
const struct netfs_request_ops *ops);
然后是一个从 VFS inode 结构转换为 netfs 上下文的函数
struct netfs_inode *netfs_node(struct inode *inode);
最后,一个从附加到 inode 的上下文中获取缓存 cookie 指针的函数(如果禁用 fscache,则为 NULL)
struct fscache_cookie *netfs_i_cookie(struct netfs_inode *ctx);
缓冲读取辅助函数¶
该库提供了一组读取辅助函数,用于处理 ->read_folio()、->readahead() 和大部分 ->write_begin() VM 操作,并将它们转换为通用调用框架。
提供以下服务
处理跨越多个页面的 folio。
使 netfs 免受 VM 接口更改的影响。
允许 netfs 将读取任意拆分为多个片段,即使这些片段与 folio 大小或 folio 对齐方式不匹配,并且可能会跨越 folio。
允许 netfs 在两个方向上扩展预读请求以满足其需求。
允许 netfs 部分满足读取请求,然后将重新提交。
处理本地缓存,允许缓存数据和服务器读取的数据为单个请求交织在一起。
处理清除不在服务器上的缓冲数据。
处理失败的读取重试,并在必要时将读取从缓存切换到服务器。
将来,这是可以执行其他服务的地方,例如对要远程存储或在缓存中存储的数据进行本地加密。
从网络文件系统来看,辅助函数需要一个操作表。这包括一个用于发出读取操作的强制方法以及许多可选方法。
读取辅助函数¶
提供三个读取辅助函数
void netfs_readahead(struct readahead_control *ractl);
int netfs_read_folio(struct file *file,
struct folio *folio);
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);
每个函数都对应一个 VM 地址空间操作。这些操作使用每个 inode 上下文中的状态。
对于 ->readahead() 和 ->read_folio(),网络文件系统直接指向相应的读取辅助函数;而对于 ->write_begin(),它可能稍微复杂一些,因为网络文件系统可能希望刷新冲突的写入或跟踪脏数据,并且如果调用辅助函数后发生错误,则需要放置获取的 folio。
辅助函数管理读取请求,通过提供的操作表回调到网络文件系统。在为同步辅助函数返回之前,将根据需要执行等待。
如果发生错误,将调用 ->free_request() 来清理分配的 netfs_io_request 结构。如果在发生错误时请求的某些部分正在进行中,如果读取了足够的数据,则将部分完成该请求。
此外,还有
* void netfs_subreq_terminated(struct netfs_io_subrequest *subreq,
ssize_t transferred_or_error,
bool was_async);
应调用该函数来完成读取子请求。该函数给定传输的字节数或负错误代码,以及一个标志,指示操作是否是异步的(即,鉴于这可能涉及休眠,后续处理是否可以在当前上下文中完成)。
读取辅助结构¶
读取辅助函数使用几个结构来维护读取的状态。第一个是管理整个读取请求的结构
struct netfs_io_request {
struct inode *inode;
struct address_space *mapping;
struct netfs_cache_resources cache_resources;
void *netfs_priv;
loff_t start;
size_t len;
loff_t i_size;
const struct netfs_request_ops *netfs_ops;
unsigned int debug_id;
...
};
以上字段是 netfs 可以使用的字段。它们是
inode
mapping
正在读取的文件的 inode 和地址空间。mapping 可能指向也可能不指向 inode->i_data。
cache_resources
本地缓存使用的资源(如果存在)。
netfs_priv
网络文件系统的私有数据。可以将此值传递给辅助函数或在请求期间设置。
start
len
读取请求的起始文件位置和长度。这些可能会被 ->expand_readahead() 操作更改。
i_size
请求开始时文件的大小。
netfs_ops
指向操作表的指针。此值将传递给辅助函数。
debug_id
分配给此操作的数字,可以在跟踪行中显示以供参考。
第二个结构用于管理整个读取请求的各个切片
struct netfs_io_subrequest {
struct netfs_io_request *rreq;
loff_t start;
size_t len;
size_t transferred;
unsigned long flags;
unsigned short debug_index;
...
};
每个子请求都应访问单个源,但辅助函数将处理从一种源类型回退到另一种源类型。成员是
rreq
指向读取请求的指针。
start
len
此读取请求切片的起始文件位置和长度。
transferred
到目前为止,此切片的长度已传输的数据量。网络文件系统或缓存应在此切片中的位置开始操作。如果发生短读取,辅助函数将再次调用,并在更新此值以反映到目前为止已读取的数量。
flags
与读取相关的标志。文件系统或缓存需要关注以下两个标志
NETFS_SREQ_CLEAR_TAIL
可以设置此标志以指示应清除切片的其余部分(从传输到 len)。
NETFS_SREQ_SEEK_DATA_READ
这是对缓存的提示,提示它可能希望尝试跳到下一个数据(即使用 SEEK_DATA)。
debug_index
分配给此切片的数字,可以在跟踪行中显示以供参考。
读取辅助操作¶
网络文件系统必须向读取辅助函数提供一个操作表,通过该表它可以发出请求并协商
struct netfs_request_ops {
void (*init_request)(struct netfs_io_request *rreq, struct file *file);
void (*free_request)(struct netfs_io_request *rreq);
void (*expand_readahead)(struct netfs_io_request *rreq);
bool (*clamp_length)(struct netfs_io_subrequest *subreq);
void (*issue_read)(struct netfs_io_subrequest *subreq);
bool (*is_still_valid)(struct netfs_io_request *rreq);
int (*check_write_begin)(struct file *file, loff_t pos, unsigned len,
struct folio **foliop, void **_fsdata);
void (*done)(struct netfs_io_request *rreq);
};
操作如下
init_request()
[可选] 调用此函数来初始化请求结构。它会给出要引用的文件。
free_request()
[可选] 在释放请求时调用此函数,以便文件系统可以清理其附加的任何状态。
expand_readahead()
[可选] 调用此函数以允许文件系统扩展预读读取请求的大小。文件系统可以在两个方向上扩展请求,但它不允许减少请求,因为这些数字可能表示已经进行的分配。如果启用了本地缓存,它将首先扩展请求。
通过更改请求结构中的 ->start 和 ->len 来传达扩展。请注意,如果进行任何更改,则 ->len 必须至少增加与 ->start 减少的量相同。
clamp_length()
[可选] 调用此函数以允许文件系统减小子请求的大小。例如,文件系统可以使用此函数来拆分必须跨多个服务器拆分的请求,或放入多个正在进行的读取。
成功时应返回 0,错误时返回错误代码。
issue_read()
[必需] 辅助函数使用此函数将子请求分派到服务器进行读取。在子请求中,->start、->len 和 ->transferred 指示应从服务器读取哪些数据。
没有返回值;应调用 netfs_subreq_terminated() 函数以指示操作是否成功以及传输了多少数据。文件系统也不应处理设置 folios uptodate、解锁它们或删除它们的引用 - 辅助函数需要处理此问题,因为它们必须与复制到本地缓存的操作协调。
请注意,辅助函数已锁定但未固定 folios。可以使用 ITER_XARRAY iov 迭代器来引用正在操作的 inode 范围,而无需分配大型 bvec 表。
is_still_valid()
[可选] 调用此函数以查找刚刚从本地缓存读取的数据是否仍然有效。如果仍然有效,则应返回 true,否则应返回 false。如果它仍然无效,它将从服务器重新读取。
check_write_begin()
[可选] 在
netfs_write_begin()
辅助函数分配/获取要修改的 folio 后调用此函数,以允许文件系统在允许修改之前刷新冲突状态。它可以解锁并丢弃给它的 folio,并将调用者的 folio 指针设置为 NULL。如果一切正常(
*foliop
保持设置),则应返回 0,或者应重试操作(清除*foliop
),并返回任何其他错误代码来中止操作。
done
[可选] 在请求中的所有 folios 都被解锁(并在适用时标记为 uptodate)后调用此函数。
读取辅助过程¶
读取辅助函数按以下一般过程工作
设置请求。
对于预读,允许本地缓存和网络文件系统建议扩展读取请求。然后将其提供给虚拟机。如果虚拟机无法完全执行扩展,则将执行部分扩展的读取,但这可能不会完整写入缓存。
循环遍历,从请求中切分出块以形成子请求。
如果存在本地缓存,则由它进行切分;否则,助手只会尝试生成最大切片。
如果网络文件系统是数据源,则可以限制每个切片的大小。这样可以实现 rsize 和分块。
助手从缓存或服务器发出读取请求,或者根据需要清除切片。
下一个切片从上一个切片的末尾开始。
当切片读取完成时,它们会终止。
当所有子请求都终止后,会评估子请求,并且重新发出任何短请求或失败的请求。
失败的缓存请求将改为向服务器发出请求。
失败的服务器请求只会失败。
如果从任一来源读取的数据不足,并且传输了更多数据,则会向该来源重新发出请求。
缓存可能需要跳过无法执行 DIO 的空洞。
如果设置了 NETFS_SREQ_CLEAR_TAIL,则短读取将被清除到切片的末尾,而不是重新发出请求。
读取数据后,已完全读取/清除的页框
将被标记为 uptodate。
如果存在缓存,则会被标记为 PG_fscache。
已解锁
任何需要写入缓存的页框都将发出 DIO 写入。
同步操作将等待读取完成。
写入缓存的操作将异步进行,并且当写入完成时,页框的 PG_fscache 标记将被删除。
当所有操作都完成后,请求结构将被清理。
读取助手缓存 API¶
在实现供读取助手使用的本地缓存时,需要两件事:一种是网络文件系统初始化读取请求的缓存的方法,另一种是助手调用的操作表。
要开始在 fscache 对象上进行缓存操作,请调用以下函数
int fscache_begin_read_operation(struct netfs_io_request *rreq,
struct fscache_cookie *cookie);
传入请求指针和与文件对应的 cookie。这将填充下面提到的缓存资源。
netfs_io_request 对象包含一个缓存挂起其状态的位置
struct netfs_cache_resources {
const struct netfs_cache_ops *ops;
void *cache_priv;
void *cache_priv2;
};
其中包含一个操作表指针和两个私有指针。操作表如下所示
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);
int (*prepare_write)(struct netfs_cache_resources *cres,
loff_t *_start, size_t *_len, loff_t i_size,
bool no_space_allocated_yet);
int (*write)(struct netfs_cache_resources *cres,
loff_t start_pos,
struct iov_iter *iter,
netfs_io_terminated_t term_func,
void *term_func_priv);
int (*query_occupancy)(struct netfs_cache_resources *cres,
loff_t start, size_t len, size_t granularity,
loff_t *_data_start, size_t *_data_len);
};
带有一个终止处理程序函数指针
typedef void (*netfs_io_terminated_t)(void *priv,
ssize_t transferred_or_error,
bool was_async);
表中定义的方法包括
end_operation()
[必需] 在读取请求结束时调用以清理资源。
expand_readahead()
[可选] 在
netfs_readahead()
操作开始时调用,以允许缓存在任一方向上扩展请求。这允许缓存根据缓存粒度调整请求大小。该函数在其参数中传递指向起始位置和长度的指针,以及文件的大小作为参考,并适当地调整起始位置和长度。它应该返回以下值之一
NETFS_FILL_WITH_ZEROES
NETFS_DOWNLOAD_FROM_SERVER
NETFS_READ_FROM_CACHE
NETFS_INVALID_READ
以指示是否应该仅清除切片,是否应该从服务器下载或从缓存读取,或者是否应该在当前点放弃切片。
prepare_read()
[必需] 调用以配置请求的下一个切片。子请求中的 ->start 和 ->len 指示下一个切片的位置和大小;缓存可以减小长度以匹配其粒度要求。
read()
[必需] 调用以从缓存读取。给出起始文件偏移量以及要读取到的迭代器,该迭代器也给出了长度。可以给出一个提示,请求它从该起始位置向前查找数据。
还提供了指向终止处理程序函数的指针和传递给该函数的私有数据。应使用传输的字节数或错误代码以及一个标志(指示终止是否肯定在调用者的上下文中发生)来调用终止函数。
prepare_write()
[必需] 调用以准备进行写入缓存的操作。这包括检查缓存是否有足够的空间来满足写入需求。
*_start
和*_len
指示要写入的区域;可以根据需要缩小该区域或将其扩展到页面边界,以对齐直接 I/O。i_size 保存对象的大小并提供参考。如果调用者确定尚未将任何数据写入该区域,则将 no_space_allocated_yet 设置为 true,例如,如果它尝试从该区域读取数据。
write()
[必需] 调用以写入缓存。给出起始文件偏移量以及要写入的迭代器,该迭代器也给出了长度。
还提供了指向终止处理程序函数的指针和传递给该函数的私有数据。应使用传输的字节数或错误代码以及一个标志(指示终止是否肯定在调用者的上下文中发生)来调用终止函数。
query_occupancy()
[必需] 调用以查找缓存特定区域内下一个数据块的位置。传入要查询的区域的起始位置和长度,以及答案需要对齐的粒度。该函数会返回该区域内可用的数据的起始位置和长度(如果有)。请注意,前面可能存在一个空洞。
如果在该区域中找到一些数据,则返回 0;如果该区域中没有可用的数据,则返回 -ENODATA;如果此文件上没有缓存,则返回 -ENOBUFS。
请注意,这些方法会传递指向缓存资源结构的指针,而不是读取请求结构的指针,因为它们也可以在其他没有读取请求结构的情况下使用,例如将脏数据写入缓存。
API 函数参考¶
参数
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 文件已调整大小,以便它可以调整其状态。
-
struct fscache_cookie *netfs_i_cookie(struct netfs_inode *ctx)¶
从 inode 获取缓存 cookie
参数
struct netfs_inode *ctx
要查询的 netfs inode
说明
从网络文件系统的 inode 获取缓存 cookie(如果已启用)。
参数
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 上下文。
无论是否启用缓存,此函数都可用。
参数
struct file *file
要读取的文件
struct folio *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
写入的长度(可能会超出所选 folio 的末尾)
struct folio **_folio
放置结果 folio 的位置
void **_fsdata
供 netfs 存储 cookie 的位置
说明
通过从缓存中提取数据(如果可能),或者从 netfs 中提取数据(如果不可能),预读取数据以进行写开始请求。超出 EOF 的空间将填充为零。来自不同来源的多个 I/O 请求将混合在一起。
调用 netfs 必须提供一个操作表,其中只有一个 issue_read 是强制性的。
可以提供 check_write_begin() 操作来检查和刷新冲突的写入,一旦 folio 被获取并锁定。它被传递一个指向 fsdata cookie 的指针,该指针被返回到 VM 以传递给 write_end。它允许睡眠。如果请求应该继续进行,则应返回 0,或者可以返回错误。它还可以解锁并放置 folio,前提是将 *foliop
设置为 NULL,在这种情况下,返回 0 将导致 folio 被重新获取并且该过程被重试。
调用 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)