Linux 虚拟文件系统概述¶
原始作者:Richard Gooch <rgooch@atnf.csiro.au>
版权所有 (C) 1999 Richard Gooch
版权所有 (C) 2005 Pekka Enberg
简介¶
虚拟文件系统(也称为虚拟文件系统切换)是内核中为用户空间程序提供文件系统接口的软件层。它还在内核中提供了一种抽象,允许不同的文件系统实现共存。
VFS 系统调用 open(2)、stat(2)、read(2)、write(2)、chmod(2) 等是从进程上下文中调用的。文件系统锁定在文档 锁定 中描述。
目录项缓存(dcache)¶
VFS 实现 open(2)、stat(2)、chmod(2) 和类似的系统调用。传递给它们的路径名参数被 VFS 用来搜索目录项缓存(也称为 dentry 缓存或 dcache)。这提供了一种非常快速的查找机制,可以将路径名(文件名)转换为特定的 dentry。Dentries 驻留在 RAM 中,永远不会保存到磁盘:它们的存在只是为了提高性能。
dentry 缓存旨在成为对整个文件空间的视图。由于大多数计算机无法同时将所有 dentry 放入 RAM 中,因此缓存的某些位会丢失。为了将路径名解析为 dentry,VFS 可能不得不沿途创建 dentry,然后加载 inode。这是通过查找 inode 来完成的。
Inode 对象¶
单个 dentry 通常有一个指向 inode 的指针。Inode 是文件系统对象,如常规文件、目录、FIFO 和其他类型的文件。它们要么驻留在磁盘上(对于块设备文件系统),要么驻留在内存中(对于伪文件系统)。驻留在磁盘上的 inode 在需要时复制到内存中,对 inode 的更改会写回磁盘。一个 inode 可以被多个 dentry 指向(例如,硬链接就是这样做的)。
要查找 inode,需要 VFS 调用父目录 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系统实现安装。一旦 VFS 拥有所需的 dentry(因此也拥有 inode),我们就可以执行所有那些无聊的事情,如 open(2) 文件,或 stat(2) 它以查看 inode 数据。stat(2) 操作相当简单:一旦 VFS 拥有 dentry,它就会查看 inode 数据并将其中一些数据传递回用户空间。
文件对象¶
打开文件需要另一个操作:分配文件结构(这是文件描述符的内核端实现)。新分配的文件结构使用指向 dentry 的指针和一组文件操作成员函数进行初始化。这些是从 inode 数据中获取的。然后调用 open() 文件方法,以便特定的文件系统实现可以执行其工作。您可以看到这是 VFS 执行的另一个切换。文件结构被放入进程的文件描述符表中。
读取、写入和关闭文件(以及其他各种 VFS 操作)是通过使用用户空间文件描述符来获取适当的文件结构,然后调用所需的文件结构方法来完成的。只要文件处于打开状态,它就会保持 dentry 的使用,这反过来意味着 VFS inode 仍在使用中。
注册和挂载文件系统¶
要注册和注销文件系统,请使用以下 API 函数
#include <linux/fs.h>
extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);
传递的 struct file_system_type 描述了您的文件系统。当请求将文件系统挂载到命名空间中的目录时,VFS 将调用特定文件系统的适当 mount() 方法。新的 vfsmount 引用 ->mount() 返回的树将被附加到挂载点,以便当路径名解析到达挂载点时,它将跳转到该 vfsmount 的根目录。
您可以在文件 /proc/filesystems 中看到所有已注册到内核的文件系统。
struct file_system_type¶
这描述了文件系统。定义了以下成员
struct file_system_type {
const char *name;
int fs_flags;
int (*init_fs_context)(struct fs_context *);
const struct fs_parameter_spec *parameters;
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct hlist_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key invalidate_lock_key;
struct lock_class_key i_mutex_dir_key;
};
name
文件系统类型的名称,如“ext2”、“iso9660”、“msdos”等
fs_flags
各种标志(例如,FS_REQUIRES_DEV、FS_NO_DCACHE 等)
init_fs_context
使用文件系统特定数据初始化 'struct fs_context' ->ops 和 ->fs_private 字段。
parameters
指向文件系统参数描述符数组 'struct fs_parameter_spec' 的指针。更多信息请参见 文件系统挂载 API。
mount
当应挂载此文件系统的新实例时要调用的方法
kill_sb
当应关闭此文件系统的实例时要调用的方法
owner
供 VFS 内部使用:在大多数情况下,您应该将其初始化为 THIS_MODULE。
next
供 VFS 内部使用:您应该将其初始化为 NULL
fs_supers
供 VFS 内部使用:文件系统实例(超级块)的 hlist
s_lock_key、s_umount_key、s_vfs_rename_key、s_writers_key、i_lock_key、i_mutex_key、invalidate_lock_key、i_mutex_dir_key:特定于 lockdep
mount() 方法具有以下参数
struct file_system_type *fs_type
描述文件系统,部分由特定文件系统代码初始化
int flags
挂载标志
const char *dev_name
我们要挂载的设备名称。
void *data
任意挂载选项,通常以 ASCII 字符串形式出现(请参阅“挂载选项”部分)
mount() 方法必须返回调用者请求的树的根 dentry。必须获取对其超级块的活动引用,并且必须锁定超级块。如果失败,它应该返回 ERR_PTR(错误)。
参数与 mount(2) 的参数匹配,它们的解释取决于文件系统类型。例如,对于块文件系统,dev_name 被解释为块设备名称,该设备被打开,如果它包含合适的文件系统映像,则该方法会相应地创建和初始化 struct super_block,并将其根 dentry 返回给调用者。
->mount() 可以选择返回现有文件系统的子树 - 它不必创建新的子树。从调用者的角度来看,主要结果是对要附加的(子)树的根处的 dentry 的引用;创建新的超级块是一个常见的副作用。
mount() 方法填充的超级块结构中最有趣的成员是“s_op”字段。这是一个指向“struct super_operations”的指针,它描述了文件系统实现的下一级。
通常,文件系统使用其中一个通用 mount() 实现,并提供 fill_super() 回调函数。通用变体有
mount_bdev
挂载驻留在块设备上的文件系统
mount_nodev
挂载不由设备支持的文件系统
mount_single
挂载在所有挂载之间共享实例的文件系统
fill_super() 回调函数实现具有以下参数
struct super_block *sb
超级块结构。回调函数必须正确初始化此结构。
void *data
任意挂载选项,通常以 ASCII 字符串形式出现(请参阅“挂载选项”部分)
int silent
是否在错误时保持静默
超级块对象¶
超级块对象表示已挂载的文件系统。
struct super_operations¶
这描述了 VFS 如何操作文件系统的超级块。定义了以下成员
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*free_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *sb,
enum freeze_holder who);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *sb,
enum freeze_wholder who);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
所有方法都调用时不会持有任何锁,除非另有说明。这意味着大多数方法可以安全地阻塞。所有方法都仅从进程上下文中调用(即,不是从中断处理程序或下半部调用)。
alloc_inode
此方法由 alloc_inode() 调用,为 struct inode 分配内存并初始化它。如果未定义此函数,则会分配一个简单的 ‘struct inode’。通常,alloc_inode 将用于分配一个更大的结构,其中包含嵌入在其中的 ‘struct inode’。
destroy_inode
此方法由 destroy_inode() 调用,以释放为 struct inode 分配的资源。仅当定义了 ->alloc_inode 时才需要它,并且只是撤消 ->alloc_inode 所做的任何事情。
free_inode
此方法从 RCU 回调中调用。 如果您在 ->destroy_inode 中使用
call_rcu()
来释放 ‘struct inode’ 内存,那么最好在此方法中释放内存。dirty_inode
当 inode 被标记为脏时,VFS 会调用此方法。 这专门用于 inode 本身被标记为脏,而不是其数据。 如果更新需要通过 fdatasync() 持久化,则 I_DIRTY_DATASYNC 将在 flags 参数中设置。 如果启用了 lazytime 并且 struct inode 的时间自上次 ->dirty_inode 调用以来已更新,则 I_DIRTY_TIME 将在 flags 中设置。
write_inode
当 VFS 需要将 inode 写入磁盘时,会调用此方法。 第二个参数指示写入是否应该是同步的,并非所有文件系统都会检查此标志。
drop_inode
当对 inode 的最后一次访问被删除时调用,并持有 inode->i_lock 自旋锁。
此方法应为 NULL(正常的 UNIX 文件系统语义)或“generic_delete_inode”(对于不想缓存 inode 的文件系统 - 导致“delete_inode”始终被调用,而不管 i_nlink 的值如何)
“generic_delete_inode()” 的行为等同于过去在 put_inode() 情况中使用 “force_delete” 的做法,但没有 “force_delete()” 方法存在的竞争条件。
evict_inode
当 VFS 要逐出 inode 时调用。 调用者*不*逐出页面缓存或与 inode 关联的元数据缓冲区; 该方法必须使用
truncate_inode_pages_final()
来摆脱它们。 调用者确保在调用 ->evict_inode() 时(或之后),异步写回不能为 inode 运行。 可选。put_super
当 VFS 希望释放超级块(即卸载)时调用。 这是在持有超级块锁的情况下调用的
sync_fs
当 VFS 写入与超级块关联的所有脏数据时调用。 第二个参数指示该方法是否应等待直到写入完成。 可选。
freeze_super
如果提供,则调用此函数而不是 ->freeze_fs 回调。 主要区别在于调用 ->freeze_super 时不执行 down_write(&sb->s_umount)。 如果文件系统实现了它并且也希望调用 ->freeze_fs,那么它必须从此回调中显式调用 ->freeze_fs。 可选。
freeze_fs
当 VFS 锁定文件系统并强制其进入一致状态时调用。 此方法目前由逻辑卷管理器 (LVM) 和 ioctl(FIFREEZE) 使用。 可选。
thaw_super
当 VFS 在 ->freeze_super 之后解锁文件系统并使其再次可写时调用。 可选。
unfreeze_fs
当 VFS 在 ->freeze_fs 之后解锁文件系统并使其再次可写时调用。 可选。
statfs
当 VFS 需要获取文件系统统计信息时调用。
remount_fs
当重新挂载文件系统时调用。 这是在持有内核锁的情况下调用的
umount_begin
当 VFS 正在卸载文件系统时调用。
show_options
由 VFS 调用,以显示 /proc/<pid>/mounts 和 /proc/<pid>/mountinfo 的挂载选项。(请参阅“挂载选项”部分)
show_devname
可选。 由 VFS 调用以显示 /proc/<pid>/{mounts,mountinfo,mountstats} 的设备名称。 如果未提供,则将使用 '(struct mount).mnt_devname'。
show_path
可选。 由 VFS 调用(对于 /proc/<pid>/mountinfo)以显示相对于文件系统根的挂载根目录项路径。
show_stats
可选。 由 VFS 调用(对于 /proc/<pid>/mountstats)以显示特定于文件系统的挂载统计信息。
quota_read
由 VFS 调用以从文件系统配额文件读取。
quota_write
由 VFS 调用以写入文件系统配额文件。
get_dquots
由配额调用以获取特定 inode 的“struct dquot”数组。 可选。
nr_cached_objects
由文件系统的 sb 缓存收缩函数调用,以返回它包含的可释放缓存对象数。 可选。
free_cache_objects
由文件系统的 sb 缓存收缩函数调用,以扫描指示的对象数量,尝试释放它们。 可选,但任何实现此方法的文件系统也需要实现 ->nr_cached_objects 才能正确调用它。
我们无法处理文件系统可能遇到的任何错误,因此返回类型为 void。 如果 VM 尝试在 GFP_NOFS 条件下回收,则永远不会调用此方法,因此此方法不需要自己处理这种情况。
实现必须在任何已完成的扫描循环内包含有条件的重新调度调用。 这允许 VFS 确定适当的扫描批处理大小,而无需担心实现是否会因大型扫描批处理大小而导致保持问题。
设置 inode 的人负责填写“i_op”字段。 这是指向“struct inode_operations”的指针,它描述了可以在单个 inode 上执行的方法。
struct xattr_handler¶
在支持扩展属性 (xattr) 的文件系统上,s_xattr 超级块字段指向以 NULL 结尾的 xattr 处理程序数组。 扩展属性是名称:值对。
name
指示处理程序匹配具有指定名称的属性(例如“system.posix_acl_access”);prefix 字段必须为 NULL。
prefix
指示处理程序匹配具有指定名称前缀的所有属性(例如“user.”);name 字段必须为 NULL。
list
确定是否应为特定目录项列出与此 xattr 处理程序匹配的属性。 由一些 listxattr 实现(如 generic_listxattr)使用。
get
由 VFS 调用以获取特定扩展属性的值。 此方法由 getxattr(2) 系统调用调用。
set
由 VFS 调用以设置特定扩展属性的值。 当新值为 NULL 时,调用以删除特定的扩展属性。 此方法由 setxattr(2) 和 removexattr(2) 系统调用调用。
当文件系统的 xattr 处理程序都与指定的属性名称不匹配时,或者当文件系统不支持扩展属性时,各种 *xattr(2)
系统调用返回 -EOPNOTSUPP。
Inode 对象¶
inode 对象表示文件系统中的一个对象。
struct inode_operations¶
这描述了 VFS 如何操作文件系统中的 inode。 从内核 2.6.22 开始,定义了以下成员
struct inode_operations {
int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, umode_t, bool);
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *);
int (*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*readlink) (struct dentry *, char __user *,int);
const char *(*get_link) (struct dentry *, struct inode *,
struct delayed_call *);
int (*permission) (struct mnt_idmap *, struct inode *, int);
struct posix_acl * (*get_inode_acl)(struct inode *, int, bool);
int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
void (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *, struct file *,
unsigned open_flag, umode_t create_mode);
int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int);
int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int);
int (*fileattr_set)(struct mnt_idmap *idmap,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
};
同样,除非另有说明,否则所有方法都无需持有任何锁即可调用。
create
由 open(2) 和 creat(2) 系统调用调用。 仅当您要支持常规文件时才需要。 您获得的目录项不应具有 inode(即,它应为负目录项)。 在这里,您可能会使用目录项和新创建的 inode 调用
d_instantiate()
。lookup
当 VFS 需要在父目录中查找 inode 时调用。 要查找的名称在目录项中找到。 此方法必须调用
d_add()
,以将找到的 inode 插入到目录项中。 应该递增 inode 结构中的 “i_count” 字段。 如果命名的 inode 不存在,则应将 NULL inode 插入到目录项中(这称为负目录项)。 仅当发生实际错误时才应从此例程返回错误代码,否则使用 create(2)、mknod(2)、mkdir(2) 等系统调用创建 inode 将会失败。 如果要重载目录项方法,则应初始化目录项中的 “d_dop” 字段; 这是指向 struct “dentry_operations” 的指针。 在持有目录 inode 信号量的情况下调用此方法。link
由 link(2) 系统调用调用。 仅当您要支持硬链接时才需要。 您可能需要像在 create() 方法中一样调用
d_instantiate()
。unlink
由 unlink(2) 系统调用调用。 仅当您要支持删除 inode 时才需要
symlink
由 symlink(2) 系统调用调用。 仅当您要支持符号链接时才需要。 您可能需要像在 create() 方法中一样调用
d_instantiate()
。mkdir
由 mkdir(2) 系统调用调用。 仅当您要支持创建子目录时才需要。 您可能需要像在 create() 方法中一样调用
d_instantiate()
。rmdir
由 rmdir(2) 系统调用调用。 仅当您要支持删除子目录时才需要
mknod
由 mknod(2) 系统调用调用,以创建设备(字符、块)inode 或命名管道 (FIFO) 或套接字。 仅当您要支持创建这些类型的 inode 时才需要。 您可能需要像在 create() 方法中一样调用
d_instantiate()
。rename
由 rename(2) 系统调用调用,以将对象重命名为具有第二个 inode 和目录项给定的父项和名称。
对于任何不受支持或未知的标志,文件系统必须返回 -EINVAL。 当前实现了以下标志:(1) RENAME_NOREPLACE:此标志指示如果重命名的目标存在,则重命名应失败并返回 -EEXIST 而不是替换目标。 VFS 已经检查了是否存在,因此对于本地文件系统,RENAME_NOREPLACE 实现等效于普通重命名。(2) RENAME_EXCHANGE:交换源和目标。 两者都必须存在; 这由 VFS 检查。 与普通重命名不同,源和目标可能是不同的类型。
get_link
由 VFS 调用以跟随符号链接到它指向的 inode。 仅当您要支持符号链接时才需要。 此方法返回要遍历的符号链接主体(并可能使用 nd_jump_link() 重置当前位置)。 如果主体在 inode 消失之前不会消失,则不需要其他操作; 如果它需要以其他方式固定,请通过让 get_link(..., ..., done) 执行 set_delayed_call(done, destructor, argument) 来安排其释放。 在这种情况下,一旦 VFS 完成您返回的主体,将调用 destructor(argument)。 可以在 RCU 模式下调用; 这由 NULL 目录项参数指示。 如果请求在不离开 RCU 模式的情况下无法处理,则使其返回 ERR_PTR(-ECHILD)。
如果文件系统将符号链接目标存储在 ->i_link 中,则 VFS 可以直接使用它,而无需调用 ->get_link(); 但是,仍必须提供 ->get_link()。 在 RCU 宽限期之后,不得释放 ->i_link。 在 iget() 时间后写入 ->i_link 需要一个“release”内存屏障。
readlink
现在这只是一个覆盖,供 readlink(2) 在 ->get_link 使用 nd_jump_link() 或对象实际上不是符号链接时使用。 通常,文件系统应该只为符号链接实现 ->get_link,readlink(2) 将自动使用它。
permission
由 VFS 调用,以检查类似 POSIX 的文件系统上的访问权限。
可以在 rcu-walk 模式下调用(mask & MAY_NOT_BLOCK)。 如果处于 rcu-walk 模式,则文件系统必须在不阻塞或存储到 inode 的情况下检查权限。
如果遇到 rcu-walk 无法处理的情况,则返回 -ECHILD,它将在 ref-walk 模式下再次调用。
setattr
由 VFS 调用以设置文件的属性。 此方法由 chmod(2) 和相关的系统调用调用。
getattr
由 VFS 调用以获取文件的属性。此方法由 stat(2) 及相关系统调用调用。
listxattr
由 VFS 调用以列出给定文件的所有扩展属性。此方法由 listxattr(2) 系统调用调用。
update_time
由 VFS 调用以更新特定时间或 inode 的 i_version。如果未定义此方法,VFS 将自行更新 inode 并调用 mark_inode_dirty_sync。
atomic_open
在打开操作的最后一个组件上调用。通过使用此可选方法,文件系统可以在一个原子操作中查找、可能创建并打开文件。如果它想将实际的打开操作留给调用者(例如,如果文件被证明是符号链接、设备,或者只是文件系统不会执行原子打开操作的某些内容),它可以通过返回 finish_no_open(file, dentry) 来发出信号。仅当最后一个组件为负或需要查找时才会调用此方法。缓存的肯定 dentry 仍然由 f_op->open() 处理。如果文件已创建,则应在 file->f_mode 中设置 FMODE_CREATED 标志。在 O_EXCL 的情况下,该方法必须仅在文件不存在时成功,因此在成功时应始终设置 FMODE_CREATED。
tmpfile
在 O_TMPFILE open() 的末尾调用。可选,等效于在给定目录中原子地创建、打开和取消链接文件。成功后需要返回已打开的文件;这可以通过在最后调用 finish_open_simple() 来完成。
fileattr_get
在 ioctl(FS_IOC_GETFLAGS) 和 ioctl(FS_IOC_FSGETXATTR) 上调用,以检索各种文件标志和属性。在相关的 SET 操作之前也会调用此方法,以检查正在更改的内容(在这种情况下,i_rwsem 被独占锁定)。如果未设置,则回退到 f_op->ioctl()。
fileattr_set
在 ioctl(FS_IOC_SETFLAGS) 和 ioctl(FS_IOC_FSSETXATTR) 上调用,以更改各种文件标志和属性。调用者持有 i_rwsem 独占锁。如果未设置,则回退到 f_op->ioctl()。
get_offset_ctx
调用以获取目录 inode 的偏移上下文。文件系统必须定义此操作才能使用 simple_offset_dir_operations。
地址空间对象¶
地址空间对象用于分组和管理页缓存中的页面。它可以用于跟踪文件(或任何其他内容)中的页面,并跟踪文件部分到进程地址空间的映射。
地址空间可以提供许多不同的但相关的服务。这些服务包括传递内存压力、按地址查找页面以及跟踪标记为脏页或回写中的页面。
第一个服务可以独立于其他服务使用。VM 可以尝试写入脏页以清理它们,或者释放干净的页面以重复使用它们。为此,它可以对脏页调用 ->writepage 方法,并对设置了 private 标志的干净 folio 调用 ->release_folio。没有 PagePrivate 且没有外部引用的干净页面将在不通知地址空间的情况下释放。
为了实现此功能,需要将页面放在带有 lru_cache_add 的 LRU 上,并且每当使用页面时都需要调用 mark_page_active。
页面通常保存在一个以 ->index 为索引的基数树中。此树维护有关每个页面的 PG_Dirty 和 PG_Writeback 状态的信息,以便可以快速找到具有任一标志的页面。
Dirty 标记主要由 mpage_writepages 使用,它是默认的 ->writepages 方法。它使用该标记查找脏页以在其上调用 ->writepage。如果未使用 mpage_writepages(即地址提供其自己的 ->writepages),则 PAGECACHE_TAG_DIRTY 标记几乎未使用。write_inode_now 和 sync_inode 通过 __sync_single_inode 使用它来检查 ->writepages 是否已成功写出整个地址空间。
Writeback 标记由 filemap*wait* 和 sync_page* 函数通过 filemap_fdatawait_range 使用,以等待所有回写完成。
地址空间处理程序可以将额外的信息附加到页面,通常使用“struct page”中的“private”字段。如果附加了此类信息,则应设置 PG_Private 标志。这将导致各种 VM 例程对地址空间处理程序进行额外调用以处理该数据。
地址空间充当存储和应用程序之间的中介。数据一次读取到地址空间一个整页,并通过复制页面或通过内存映射页面提供给应用程序。数据由应用程序写入到地址空间,然后通常以整页的方式写回存储,但地址空间可以更精细地控制写入大小。
读取过程本质上只需要 “read_folio”。写入过程更复杂,它使用 write_begin/write_end 或 dirty_folio 将数据写入地址空间,并使用 writepage 和 writepages 将数据写回存储。
添加和删除地址空间中的页面受 inode 的 i_mutex 保护。
当数据写入页面时,应设置 PG_Dirty 标志。通常,它会保持设置状态,直到 writepage 要求写入它。这应清除 PG_Dirty 并设置 PG_Writeback。它可以在清除 PG_Dirty 后的任何时间实际写入。一旦已知安全,PG_Writeback 就会被清除。
回写使用 writeback_control 结构来指导操作。这为 writepage 和 writepages 操作提供了一些关于回写请求的性质和原因以及其执行的约束的信息。它还用于将关于 writepage 或 writepages 请求结果的信息返回给调用者。
处理回写期间的错误¶
大多数执行缓冲 I/O 的应用程序会定期调用文件同步调用(fsync、fdatasync、msync 或 sync_file_range),以确保写入的数据已到达后备存储。当回写期间发生错误时,它们希望在发出文件同步请求时报告该错误。在一个请求中报告错误后,同一文件描述符上的后续请求应返回 0,除非自上次文件同步以来发生了进一步的回写错误。
理想情况下,内核只会报告在写入后未能写回的文件描述符上的错误。但是,通用页缓存基础结构不会跟踪已弄脏每个单独页面的文件描述符,因此无法确定哪些文件描述符应该返回错误。
相反,内核中的通用回写错误跟踪基础结构会选择向所有在发生错误时打开的文件描述符上的 fsync 报告错误。在具有多个写入者的情况下,所有写入者都会在后续的 fsync 上返回一个错误,即使通过该特定文件描述符完成的所有写入都成功了(或者即使该文件描述符上根本没有写入)。
希望使用此基础结构的文件系统应在错误发生时调用 mapping_set_error 以记录地址空间中的错误。然后,在从其 file->fsync 操作中的页缓存写回数据后,它们应调用 file_check_and_advance_wb_err 以确保 struct file
的错误光标已前进到后备设备发出的错误流中的正确位置。
struct address_space_operations¶
这描述了 VFS 如何在您的文件系统中操作文件到页缓存的映射。定义了以下成员
struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*read_folio)(struct file *, struct folio *);
int (*writepages)(struct address_space *, struct writeback_control *);
bool (*dirty_folio)(struct address_space *, struct folio *);
void (*readahead)(struct readahead_control *);
int (*write_begin)(struct file *, struct address_space *mapping,
loff_t pos, unsigned len,
struct page **pagep, void **fsdata);
int (*write_end)(struct file *, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct folio *folio, void *fsdata);
sector_t (*bmap)(struct address_space *, sector_t);
void (*invalidate_folio) (struct folio *, size_t start, size_t len);
bool (*release_folio)(struct folio *, gfp_t);
void (*free_folio)(struct folio *);
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
int (*migrate_folio)(struct mapping *, struct folio *dst,
struct folio *src, enum migrate_mode);
int (*launder_folio) (struct folio *);
bool (*is_partially_uptodate) (struct folio *, size_t from,
size_t count);
void (*is_dirty_writeback)(struct folio *, bool *, bool *);
int (*error_remove_folio)(struct mapping *mapping, struct folio *);
int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span)
int (*swap_deactivate)(struct file *);
int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter);
};
writepage
由 VM 调用以将脏页写入后备存储。这可能是出于数据完整性原因(即“sync”)或释放内存(flush)。差异可以在 wbc->sync_mode 中看到。PG_Dirty 标志已被清除,并且 PageLocked 为 true。writepage 应该开始写出操作,应该设置 PG_Writeback,并且应该确保页面已解锁,同步或异步地在写操作完成时解锁。
如果 wbc->sync_mode 为 WB_SYNC_NONE,则 ->writepage 在出现问题时不必过于努力,并且可以选择从映射中写出其他页面(如果更容易,例如由于内部依赖关系)。如果它选择不启动写出操作,则应返回 AOP_WRITEPAGE_ACTIVATE,以便 VM 不会继续在该页面上调用 ->writepage。
有关更多详细信息,请参阅文件“锁定”。
read_folio
由页缓存调用以从后备存储读取 folio。“file”参数向网络文件系统提供身份验证信息,并且通常不被基于块的文件系统使用。如果调用者没有打开的文件,则它可能为 NULL(例如,如果内核正在为自身执行读取,而不是代表具有打开文件的用户空间进程执行读取)。
如果映射不支持大型 folio,则 folio 将包含单个页面。调用 read_folio 时将锁定 folio。如果读取成功完成,则应将 folio 标记为最新。文件系统应在读取完成后解锁 folio,无论是否成功。文件系统不需要修改 folio 上的引用计数;页缓存保留引用计数,并且在 folio 解锁之前不会释放该引用计数。
文件系统可以同步实现 ->read_folio()。在正常操作中,folio 通过 ->readahead() 方法读取。仅当此方法失败,或者如果调用者需要等待读取完成时,页缓存才会调用 ->read_folio()。文件系统不应尝试在 ->read_folio() 操作中执行自己的预读。
如果文件系统此时无法执行读取,则它可以解锁 folio,执行任何需要执行的操作以确保将来读取将成功,并返回 AOP_TRUNCATED_PAGE。在这种情况下,调用者应查找 folio,锁定它,然后再次调用 ->read_folio。
调用者可以直接调用 ->read_folio() 方法,但是使用 read_mapping_folio() 将负责锁定、等待读取完成并处理 AOP_TRUNCATED_PAGE 等情况。
writepages
由 VM 调用以写出与 address_space 对象关联的页面。如果 wbc->sync_mode 为 WB_SYNC_ALL,则 writeback_control 将指定必须写出的页面范围。如果为 WB_SYNC_NONE,则给定 nr_to_write,并且应尽可能多地写入该数量的页面。如果没有给出 ->writepages,则使用 mpage_writepages 代替。这将从标记为 DIRTY 的地址空间中选择页面,并将它们传递给 ->writepage。
dirty_folio
由虚拟机调用,用于将页标记为脏页。当地址空间将私有数据附加到页时,尤其需要此操作,并且当页被置脏时,需要更新该数据。例如,当内存映射的页面被修改时,会调用此函数。如果定义了该函数,则它应设置页的脏标志,以及 i_pages 中的 PAGECACHE_TAG_DIRTY 搜索标记。
预读
由虚拟机调用,以读取与 address_space 对象关联的页面。这些页面在页面缓存中是连续的,并且是锁定的。在开始对每个页面进行 I/O 操作后,实现应递减页面的引用计数。通常,页面将由 I/O 完成处理程序解锁。这组页面被分为一些同步页面,后跟一些异步页面,rac->ra->async_size 给出了异步页面的数量。文件系统应尝试读取所有同步页面,但一旦到达异步页面,可以决定停止。如果确实决定停止尝试 I/O,它可以直接返回。调用者将从地址空间中删除剩余的页面,解锁它们并递减页面的引用计数。如果 I/O 成功完成,则设置 PageUptodate。
write_begin
由通用缓冲写入代码调用,以请求文件系统准备在文件中给定的偏移量处写入 len 个字节。address_space 应检查写入是否能够完成,必要时分配空间并执行任何其他内部管理。如果写入将更新存储上的任何基本块的部分,则应预先读取这些块(如果尚未读取),以便可以正确写入更新后的块。
文件系统必须在
*foliop
中返回指定偏移量的锁定的页面缓存页,供调用者写入。它必须能够处理短写入(即传递给 write_begin 的长度大于复制到页中的字节数)。
可以在 fsdata 中返回一个 void *,然后将其传递给 write_end。
成功时返回 0;失败时返回 < 0(即错误代码),在这种情况下,不会调用 write_end。
write_end
在成功调用 write_begin 并进行数据复制后,必须调用 write_end。len 是传递给 write_begin 的原始 len,copied 是能够复制的数量。
文件系统必须负责解锁页,递减其引用计数,并更新 i_size。
失败时返回 < 0,否则返回能够复制到页面缓存的字节数(<= ‘copied’)。
bmap
由 VFS 调用,以将对象内的逻辑块偏移量映射到物理块号。此方法由 FIBMAP ioctl 和用于处理交换文件。为了能够交换到文件,该文件必须具有到块设备的稳定映射。交换系统不通过文件系统,而是使用 bmap 来查找文件中块的位置,并直接使用这些地址。
invalidate_folio
如果一个页具有私有数据,则当要从地址空间中删除部分或全部页时,将调用 invalidate_folio。这通常对应于截断、打孔或完全使地址空间无效(在后一种情况下,“offset”将始终为 0,“length”将为
folio_size()
)。应更新与页关联的任何私有数据以反映此截断。如果偏移量为 0 且长度为folio_size()
,则应释放私有数据,因为必须能够完全丢弃该页。可以通过调用 ->release_folio 函数来完成此操作,但在这种情况下,释放必须成功。release_folio
在具有私有数据的页上调用 release_folio,以告知文件系统该页即将被释放。->release_folio 应从该页中删除任何私有数据并清除私有标志。如果 release_folio() 失败,则应返回 false。release_folio() 用于两个截然不同但相关的场景。第一个是当虚拟机想要释放一个没有活动用户的干净页时。如果 ->release_folio 成功,则该页将从 address_space 中删除并被释放。
第二个场景是当请求使地址空间中的部分或全部页无效时。这可以通过 fadvise(POSIX_FADV_DONTNEED) 系统调用或文件系统显式请求(如 nfs 和 9p 所做的那样,当它们认为缓存可能与存储过时时)通过调用
invalidate_inode_pages2()
来发生。如果文件系统进行这样的调用,并且需要确保所有页都无效,那么它的 release_folio 将需要确保这一点。如果它还不能释放私有数据,则可能会清除 uptodate 标志。free_folio
一旦该页在页面缓存中不再可见,就会调用 free_folio,以便清理任何私有数据。由于它可能由内存回收器调用,因此它不应假定原始的 address_space 映射仍然存在,并且不应阻塞。
direct_IO
由通用读取/写入例程调用以执行 direct_IO - 即绕过页面缓存并在存储和应用程序地址空间之间直接传输数据的 IO 请求。
migrate_folio
用于压缩物理内存使用量。如果虚拟机想要重新定位一个页(可能来自发出即将发生故障信号的内存设备),它会将新页和旧页传递给此函数。migrate_folio 应跨页传输任何私有数据,并更新其对页的任何引用。
launder_folio
在释放页之前调用 - 它会写回脏页。为了防止重新将页置脏,在整个操作过程中它保持锁定。
is_partially_uptodate
当通过页面缓存读取文件时,如果底层块大小小于页面的大小时,由虚拟机调用。如果所需的块是最新的,则读取可以完成,而无需 I/O 来使整个页面保持最新。
is_dirty_writeback
当尝试回收页时,由虚拟机调用。虚拟机使用脏页和写回信息来确定是否需要暂停以允许刷新器有机会完成某些 IO。通常,它可以使用 folio_test_dirty 和 folio_test_writeback,但某些文件系统具有更复杂的状态(NFS 中不稳定的页会阻止回收)或由于锁定问题而未设置这些标志。此回调允许文件系统向虚拟机指示是否应将页视为脏页或写回,以用于暂停的目的。
error_remove_folio
如果此地址空间可以进行截断,则通常设置为 generic_error_remove_folio。用于内存故障处理。设置此值意味着您处理在您之下消失的页面,除非您已锁定它们或增加了引用计数。
swap_activate
调用以准备给定的文件用于交换。它应执行任何必要的验证和准备工作,以确保可以以最小的内存分配执行写入。它应调用 add_swap_extent() 或助手 iomap_swapfile_activate(),并返回添加的区段数。如果应通过 ->swap_rw() 提交 IO,则它应设置 SWP_FS_OPS,否则 IO 将直接提交到块设备
sis->bdev
。
swap_deactivate
在成功执行 swap_activate 的文件上执行 swapoff 时调用。
swap_rw
当设置 SWP_FS_OPS 时,调用以读取或写入交换页面。
文件对象¶
文件对象表示进程打开的文件。这在 POSIX 术语中也称为“打开文件描述”。
struct file_operations¶
这描述了 VFS 如何操作打开的文件。截至内核 4.18,定义了以下成员
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
};
同样,除非另有说明,否则所有方法都无需持有任何锁即可调用。
llseek
当 VFS 需要移动文件位置索引时调用
read
由 read(2) 和相关的系统调用调用
read_iter
可能使用 iov_iter 作为目标的异步读取
write
由 write(2) 和相关的系统调用调用
write_iter
可能使用 iov_iter 作为源的异步写入
iopoll
当 aio 想要轮询 HIPRI iocb 的完成情况时调用
iterate_shared
当 VFS 需要读取目录内容时调用
poll
当进程想要检查此文件上是否有活动,并且(可选)进入休眠状态,直到有活动为止时,由 VFS 调用。由 select(2) 和 poll(2) 系统调用调用
unlocked_ioctl
由 ioctl(2) 系统调用调用。
compat_ioctl
- 当 32 位系统调用在 64 位内核上使用时,由 ioctl(2) 系统调用调用。
使用
mmap
由 mmap(2) 系统调用调用
open
当应打开 inode 时由 VFS 调用。当 VFS 打开文件时,它会创建一个新的“
struct file
”。然后,它为新分配的文件结构调用 open 方法。您可能会认为 open 方法实际上属于 “struct inode_operations”,您可能是对的。我认为之所以这样做,是因为它使文件系统更容易实现。如果您想要指向设备结构,open() 方法是初始化文件结构中的 “private_data” 成员的好地方flush
由 close(2) 系统调用调用以刷新文件
release
当关闭打开文件的最后一个引用时调用
fsync
由 fsync(2) 系统调用调用。另请参阅上面标题为“处理回写期间的错误”的部分。
fasync
当为文件启用异步(非阻塞)模式时,由 fcntl(2) 系统调用调用
lock
为 F_GETLK、F_SETLK 和 F_SETLKW 命令由 fcntl(2) 系统调用调用
get_unmapped_area
由 mmap(2) 系统调用调用
check_flags
为 F_SETFL 命令由 fcntl(2) 系统调用调用
flock
由 flock(2) 系统调用调用
splice_write
由 VFS 调用以将数据从管道拼接到一个文件。此方法由 splice(2) 系统调用使用
splice_read
由 VFS 调用,用于将数据从文件拼接(splice)到管道。此方法由 splice(2) 系统调用使用。
setlease
由 VFS 调用,用于设置或释放文件锁租约(lease)。setlease 的实现应在设置租约后调用 generic_setlease 来记录或删除 inode 中的租约。
fallocate
由 VFS 调用,用于预分配块或打孔。
copy_file_range
由 copy_file_range(2) 系统调用调用。
remap_file_range
由 ioctl(2) 系统调用为 FICLONERANGE、FICLONE 和 FIDEDUPERANGE 命令调用,用于重新映射文件范围。实现应将源文件中 pos_in 位置的 len 字节重新映射到目标文件中 pos_out 位置。实现必须处理调用者传入 len == 0 的情况;这意味着“重新映射到源文件的末尾”。返回值应为重新映射的字节数,或者如果在重新映射任何字节之前发生错误,则应为通常的负错误代码。remap_flags 参数接受 REMAP_FILE_* 标志。如果设置了 REMAP_FILE_DEDUP,则只有当请求的文件范围具有相同的内容时,实现才必须重新映射。如果设置了 REMAP_FILE_CAN_SHORTEN,则调用者可以接受实现缩短请求长度以满足对齐或 EOF 要求(或任何其他原因)。
fadvise
可能由 fadvise64() 系统调用调用。
请注意,文件操作由 inode 所在的特定文件系统实现。当打开设备节点(字符或块特殊文件)时,大多数文件系统将调用 VFS 中的特殊支持例程,这些例程将找到所需的设备驱动程序信息。这些支持例程将文件系统的文件操作替换为设备驱动程序的文件操作,然后继续调用该文件新的 open() 方法。这就是文件系统中打开设备文件最终如何调用设备驱动程序的 open() 方法的原理。
目录项缓存 (dcache)¶
struct dentry_operations¶
这描述了文件系统如何重载标准目录项操作。目录项和 dcache 是 VFS 和各个文件系统实现的领域。设备驱动程序不应参与此处。这些方法可以设置为 NULL,因为它们是可选的,或者 VFS 使用默认值。从内核 2.6.22 开始,定义了以下成员
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, enum d_real_type type);
};
d_revalidate
当 VFS 需要重新验证目录项时调用。每当名称查找在 dcache 中找到目录项时都会调用此方法。大多数本地文件系统将其保留为 NULL,因为它们在 dcache 中的所有目录项都是有效的。网络文件系统则不同,因为服务器上的内容可能会发生变化,而客户端并不一定知道。
如果目录项仍然有效,则此函数应返回一个正值;如果目录项无效,则应返回零或负的错误代码。
d_revalidate 可以在 rcu-walk 模式下调用 (flags & LOOKUP_RCU)。如果在 rcu-walk 模式下,文件系统必须在不阻塞或存储到目录项的情况下重新验证目录项,d_parent 和 d_inode 应该谨慎使用(因为它们可能会发生变化,并且在 d_inode 的情况下,甚至会在我们操作期间变为 NULL)。
如果遇到 rcu-walk 无法处理的情况,则返回 -ECHILD,它将在 ref-walk 模式下再次调用。
d_weak_revalidate
当 VFS 需要重新验证“跳转”的目录项时调用。当路径遍历在未通过在父目录中进行查找而获取的目录项处结束时会调用此方法。这包括“/”、“.”和“..”,以及 procfs 样式的符号链接和挂载点遍历。
在这种情况下,我们不太关心目录项是否仍然完全正确,而是更关心 inode 是否仍然有效。与 d_revalidate 一样,大多数本地文件系统会将此设置为 NULL,因为它们的 dcache 条目始终有效。
此函数具有与 d_revalidate 相同的返回代码语义。
d_weak_revalidate 仅在退出 rcu-walk 模式后调用。
d_hash
当 VFS 将目录项添加到哈希表时调用。传递给 d_hash 的第一个目录项是要将名称哈希到其中的父目录。
与 d_compare 相同的锁定和同步规则,关于什么可以安全地解引用等。
d_compare
调用此方法以将目录项名称与给定名称进行比较。第一个目录项是要比较的目录项的父目录,第二个是子目录项。len 和 name 字符串是要比较的目录项的属性。qstr 是要与之比较的名称。
必须是常量且幂等的,如果可能,不应加锁,并且不应存储到目录项中。如果不小心,不应在目录项外部解引用指针(例如,d_parent、d_inode、d_name 不应使用)。
但是,我们的 vfsmount 是固定的,并且 RCU 被持有,因此目录项和 inode 不会消失,我们的 sb 或文件系统模块也不会消失。可以使用 ->d_sb。
这是一个棘手的调用约定,因为它需要在 “rcu-walk” 下调用,即没有任何锁定或对事物的引用。
d_delete
当对目录项的最后一个引用被删除并且 dcache 正在决定是否要缓存它时调用。返回 1 以立即删除,或返回 0 以缓存目录项。默认值为 NULL,这意味着始终缓存可访问的目录项。d_delete 必须是常量且幂等的。
d_init
在分配目录项时调用
d_release
在实际释放目录项时调用
d_iput
当目录项丢失其 inode 时调用(就在其被释放之前)。如果此方法为 NULL,则默认情况下 VFS 会调用
iput()
。如果定义了此方法,则必须自己调用iput()
d_dname
当应生成目录项的路径名时调用。对于某些伪文件系统(sockfs、pipefs 等)延迟路径名生成非常有用。(它不是在创建目录项时完成的,而是仅在需要路径时完成的。)实际的文件系统可能不想使用它,因为它们的目录项存在于全局 dcache 哈希中,因此它们的哈希应该是不可变的。由于没有持有锁,d_dname() 不应尝试修改目录项本身,除非使用了适当的 SMP 安全性。注意:
d_path()
逻辑非常棘手。返回例如“Hello”的正确方法是将其放在缓冲区的末尾,并返回指向第一个字符的指针。提供了 dynamic_dname() 辅助函数来处理此问题。示例
static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)
{
return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",
dentry->d_inode->i_ino);
}
d_automount
当要遍历自动挂载目录项时调用(可选)。此方法应创建一个新的 VFS 挂载记录,并将该记录返回给调用者。调用者会收到一个 path 参数,该参数提供要描述自动挂载目标的自动挂载目录,以及父 VFS 挂载记录,以提供可继承的挂载参数。如果其他人设法先进行了自动挂载,则应返回 NULL。如果 vfsmount 创建失败,则应返回错误代码。如果返回 -EISDIR,则该目录将被视为普通目录并返回给 pathwalk 以继续遍历。
如果返回 vfsmount,调用者将尝试将其挂载到挂载点上,并且如果失败,将从其过期列表中删除 vfsmount。应返回 vfsmount,其中有 2 个引用以防止自动过期 - 调用者将清理额外的引用。
仅当在目录项上设置了 DCACHE_NEED_AUTOMOUNT 时才使用此函数。如果在正在添加的 inode 上设置了 S_AUTOMOUNT,则由 __d_instantiate() 设置此标志。
d_manage
调用此方法以允许文件系统管理从目录项的转换(可选)。例如,这允许 autofs 阻止等待探索“挂载点”后面的客户端,同时允许守护程序继续并构建那里的子树。应返回 0 以让调用进程继续。可以返回 -EISDIR 来告知 pathwalk 将此目录用作普通目录,并忽略任何挂载在其上的内容,并且不检查自动挂载标志。任何其他错误代码都将完全中止 pathwalk。
如果 ‘rcu_walk’ 参数为 true,则调用者正在 RCU-walk 模式下进行路径遍历。在此模式下不允许休眠,并且可以通过返回 -ECHILD 来要求调用者退出并再次调用。还可以返回 -EISDIR 来告知 pathwalk 忽略 d_automount 或任何挂载。
仅当在要转换的目录项上设置了 DCACHE_MANAGE_TRANSIT 时才使用此函数。
d_real
覆盖/联合类型文件系统实现此方法以返回由覆盖隐藏的常规文件的底层目录项之一。
‘type’ 参数采用 D_REAL_DATA 或 D_REAL_METADATA 的值,用于分别返回引用托管文件数据或元数据的 inode 的真实底层目录项。
对于非常规文件,返回 ‘dentry’ 参数。
每个目录项都有一个指向其父目录项的指针,以及子目录项的哈希列表。子目录项基本上类似于目录中的文件。
目录项缓存 API¶
定义了许多允许文件系统操作目录项的函数
dget
为现有目录项打开一个新的句柄(这只是增加使用计数)
dput
关闭目录项的句柄(减少使用计数)。如果使用计数降至 0,并且目录项仍在父目录的哈希中,则会调用 “d_delete” 方法来检查是否应缓存它。如果不应缓存,或者如果未哈希目录项,则会删除它。否则,缓存的目录项将放入 LRU 列表中,以便在内存短缺时回收。
d_drop
这将从其父目录的哈希列表中取消哈希目录项。如果其使用计数降至 0,则后续对 dput() 的调用将释放目录项
d_delete
删除一个目录项(dentry)。如果没有其他打开的引用指向该目录项,则该目录项会转变为一个负目录项(调用 d_iput() 方法)。如果存在其他引用,则会调用
d_drop()
。d_add
将目录项添加到其父目录的哈希列表,然后调用
d_instantiate()
。d_instantiate
将目录项添加到索引节点的别名哈希列表,并更新 “d_inode” 成员。索引节点结构中的 “i_count” 成员应该被设置/递增。如果索引节点指针为 NULL,则该目录项被称为“负目录项”。当为已存在的负目录项创建索引节点时,通常会调用此函数。
d_lookup
根据其父目录和路径名组件查找目录项。它从 dcache 哈希表中查找具有给定名称的子目录项。如果找到,则引用计数会递增,并返回该目录项。调用者在使用完目录项后必须使用 dput() 释放它。
挂载选项¶
解析选项¶
在挂载和重新挂载时,文件系统会接收到一个字符串,其中包含以逗号分隔的挂载选项列表。选项可以具有以下两种形式:
option option=value
<linux/parser.h> 头文件定义了一个 API,用于帮助解析这些选项。在现有的文件系统中有很多关于如何使用它的例子。
显示选项¶
如果一个文件系统接受挂载选项,它必须定义 show_options() 来显示当前所有活动的选项。规则如下:
必须显示非默认或其值与默认值不同的选项
可以显示默认启用或具有默认值的选项
仅在挂载辅助程序和内核之间内部使用的选项(例如文件描述符)或者仅在挂载期间生效的选项(例如控制日志创建的选项)不受上述规则的约束。
上述规则的根本原因是为了确保可以根据 /proc/mounts 中找到的信息准确地复制挂载(例如,卸载和重新挂载)。
资源¶
- (请注意,其中一些资源与最新的内核版本不同步。)
版本。)
- 创建 Linux 虚拟文件系统。2002
- Neil Brown 的 Linux 虚拟文件系统层。1999
<http://www.cse.unsw.edu.au/~neilb/oss/linux-commentary/vfs.html>
- Michael K. Johnson 的 Linux VFS 之旅。1996
<https://www.tldp.org/LDP/khg/HyperNews/get/fs/vfstour.html>
- Andries Brouwer 的 Linux 内核小径。2001