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 缓存旨在查看您的整个文件空间。由于大多数计算机无法同时将所有 dentries 放入 RAM 中,因此缓存中缺少一些位。为了将您的路径名解析为 dentry,VFS 可能不得不沿途创建 dentries,然后加载 inode。这是通过查找 inode 来完成的。
Inode 对象¶
单个 dentry 通常有一个指向 inode 的指针。Inode 是文件系统对象,例如常规文件、目录、FIFO 和其他类型。它们要么存在于磁盘上(对于块设备文件系统),要么存在于内存中(对于伪文件系统)。磁盘上的 Inodes 在需要时被复制到内存中,并且对 inode 的更改被写回磁盘。单个 inode 可以被多个 dentries 指向(例如,硬链接就是这样做的)。
要查找 inode,VFS 需要调用父目录 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系统实现安装。一旦 VFS 拥有所需的 dentry(以及 inode),我们就可以进行所有那些无聊的事情,例如 open(2) 文件或 stat(2) 文件以查看 inode 数据。stat(2) 操作相当简单:一旦 VFS 拥有 dentry,它就会查看 inode 数据并将其中一些数据传递回用户空间。
File 对象¶
打开文件需要另一个操作:分配文件结构(这是文件描述符的内核端实现)。新分配的文件结构用指向 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(error)。
参数与 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
是否对错误保持静默
Superblock 对象¶
超级块对象表示已挂载的文件系统。
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”(对于不想缓存 inodes 的文件系统 - 导致始终调用“delete_inode”,而不管 i_nlink 的值如何)
“generic_delete_inode()” 行为等效于在 put_inode() case 中使用 “force_delete” 的旧方法,但不具有 “force_delete()” 方法存在的竞争条件。
evict_inode
当 VFS 想要逐出 inode 时调用。调用者不会逐出 pagecache 或与 inode 关联的元数据缓冲区;该方法必须使用
truncate_inode_pages_final()
来摆脱这些。调用者确保在调用 ->evict_inode() 时(或之后),异步写回不会为 inode 运行。可选。put_super
当 VFS 希望释放超级块时(即卸载)调用。在持有超级块锁的情况下调用此函数
sync_fs
当 VFS 正在写出与超级块关联的所有脏数据时调用。第二个参数指示该方法是否应等待写出完成。可选。
freeze_super
如果提供,则调用此函数来代替 ->freeze_fs 回调。主要区别在于,在没有获取 down_write(&sb->s_umount) 的情况下调用 ->freeze_super。如果文件系统实现了它并且也希望调用 ->freeze_fs,那么它必须从此回调显式调用 ->freeze_fs。可选。
freeze_fs
当 VFS 锁定文件系统并强制其进入一致状态时调用。此方法当前由逻辑卷管理器 (LVM) 和 ioctl(FIFREEZE) 使用。可选。
thaw_super
在 ->freeze_super 之后,当 VFS 解锁文件系统并使其再次可写时调用。可选。
unfreeze_fs
在 ->freeze_fs 之后,当 VFS 解锁文件系统并使其再次可写时调用。可选。
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)调用以显示相对于文件系统根目录的挂载根目录 dentry 路径。
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¶
在支持扩展属性 (xattrs) 的文件系统上,s_xattr 超级块字段指向一个以 NULL 结尾的 xattr 处理程序数组。扩展属性是 name:value 对。
name
表示处理程序匹配具有指定名称的属性(例如 “system.posix_acl_access”);prefix 字段必须为 NULL。
prefix
表示处理程序匹配具有指定名称前缀的所有属性(例如“user.”);name 字段必须为 NULL。
list
确定是否应为特定 dentry 列出与此 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 *);
struct dentry *(*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) 系统调用调用。仅当您要支持常规文件时才需要。您获得的 dentry 不应具有 inode(即,它应该是负 dentry)。在这里,您可能会使用 dentry 和新创建的 inode 调用
d_instantiate()
lookup
当 VFS 需要在父目录中查找 inode 时调用。要查找的名称在 dentry 中找到。此方法必须调用
d_add()
以将找到的 inode 插入到 dentry 中。应递增 inode 结构中的 “i_count” 字段。如果指定的 inode 不存在,则应将 NULL inode 插入到 dentry 中(这称为负 dentry)。从此例程返回错误代码只能在实际错误时完成,否则使用系统调用(如 create(2)、mknod(2)、mkdir(2) 等)创建 inode 将失败。如果您希望重载 dentry 方法,则应初始化 dentry 中的 “d_dop” 字段;这是指向结构 “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_new()。
如果未使用 d_instantiate_new() 并且提供了 fh_to_dentry() 导出操作,或者存储可能可以通过另一条路径访问(例如使用网络文件系统),则可能需要更多注意。重要的是,如果 inode 不再是 I_NEW,并且 inode 可能已附加到 dentry,则不应使用 d_instantate()。这是因为 VFS 中有一个硬性规定,即目录只能有一个 dentry。
例如,如果 NFS 文件系统挂载两次,则在第一次 mkdir 返回之前,新目录可能在另一个挂载上可见,并且一对 name_to_handle_at()、open_by_handle_at() 调用可能会使用 IS_ROOT() dentry 实例化目录 inode。
如果发生这种情况的可能性很大,则新的 inode 应使用
d_splice_alias()
进行d_drop()
ed 和附加。返回的 dentry(如果有)应由 ->mkdir() 返回。rmdir
由 rmdir(2) 系统调用调用。仅当您要支持删除子目录时才需要
mknod
由 mknod(2) 系统调用调用以创建设备(字符、块)inode 或命名管道 (FIFO) 或套接字。仅当您要支持创建这些类型的 inode 时才需要。您可能需要像在 create() 方法中一样调用
d_instantiate()
rename
由 rename(2) 系统调用调用,以将对象重命名为具有第二个 inode 和 dentry 给出的父项和名称。
对于任何不支持或未知的标志,文件系统必须返回 -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 dentry 参数指示。如果请求在不离开 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) 来发出信号。仅当最后一个组件为负数或需要查找时才调用此方法。缓存的肯定 dentries 仍由 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。
地址空间对象¶
地址空间对象用于对页面缓存中的页面进行分组和管理。它可用于跟踪文件(或任何其他内容)中的页面,并跟踪文件各个部分到进程地址空间的映射。
地址空间可以提供许多不同的但相关的服务。这些服务包括传递内存压力、按地址查找页面以及跟踪标记为 Dirty 或 Writeback 的页面。
第一个可以独立于其他服务使用。VM 可以尝试释放干净的页面以重新使用它们。为此,它可以调用具有设置了私有标志的干净 folios 的 ->release_folio。没有 PagePrivate 且没有外部引用的干净页面将在不通知地址空间的情况下释放。
为了实现此功能,需要将页面放置在带有 lru_cache_add 的 LRU 上,并且每当使用页面时都需要调用 mark_page_active。
页面通常按 ->index 保存在基数树索引中。此树维护有关每个页面的 PG_Dirty 和 PG_Writeback 状态的信息,以便可以快速找到具有这些标志的页面。
Dirty 标记主要由 mpage_writepages 使用 - 默认的 ->writepages 方法。它使用该标记来查找要写回的脏页面。如果不使用 mpage_writepages(即,地址提供自己的 ->writepages),则 PAGECACHE_TAG_DIRTY 标记几乎未使用。write_inode_now 和 sync_inode 确实使用它(通过 __sync_single_inode)来检查 ->writepages 是否已成功写出整个 address_space。
Writeback 标记由 filemap*wait* 和 sync_page* 函数通过 filemap_fdatawait_range 使用,以等待所有写回完成。
地址空间处理程序可以将额外信息附加到页面,通常使用 “struct page” 中的 “private” 字段。如果附加了此类信息,则应设置 PG_Private 标志。这将导致各种 VM 例程对地址空间处理程序进行额外调用以处理该数据。
地址空间充当存储和应用程序之间的中介。数据一次一个整页地读入地址空间,并通过复制页面或通过内存映射页面提供给应用程序。数据由应用程序写入地址空间,然后通常以整页的形式写回存储,但是地址空间可以更精细地控制写入大小。
读取过程本质上只需要 “read_folio”。写入过程更加复杂,并使用 write_begin/write_end 或 dirty_folio 将数据写入地址空间,并使用 writepages 将数据写回存储。
向地址空间添加和从中删除页面的操作受 inode 的 i_mutex 保护。
当数据写入页面时,应设置 PG_Dirty 标志。它通常保持设置状态,直到 writepages 请求写入它。这应该清除 PG_Dirty 并设置 PG_Writeback。它可以在清除 PG_Dirty 之后的任何时间实际写入。一旦知道安全,PG_Writeback 就会被清除。
Writeback 使用 writeback_control 结构来指导操作。这为 writepages 操作提供了一些有关写回请求的性质和原因的信息,以及正在执行写回的约束。它还用于将有关 writepages 请求结果的信息返回给调用者。
处理写回期间的错误¶
大多数执行缓冲 I/O 的应用程序将定期调用文件同步调用(fsync、fdatasync、msync 或 sync_file_range),以确保写入的数据已写入后备存储。当写回期间发生错误时,他们希望在发出文件同步请求时报告该错误。在一个请求上报告错误之后,除非自上次文件同步以来发生了进一步的写回错误,否则同一文件描述符上的后续请求应返回 0。
理想情况下,内核只会报告在写入到后未能成功写回的文件描述上的错误。但是,通用页面缓存基础结构不会跟踪已弄脏每个页面的文件描述,因此无法确定哪些文件描述应返回错误。
相反,内核中的通用写回错误跟踪基础结构会选择向在发生错误时打开的所有文件描述的 fsync 报告错误。在具有多个写入器的情况下,所有写入器都将在后续的 fsync 上返回错误,即使通过该特定文件描述完成的所有写入都成功了(甚至在该文件描述上根本没有写入)。
希望使用此基础结构的文件系统应调用 mapping_set_error 以在发生错误时在 address_space 中记录该错误。然后,在文件 ->fsync 操作中从页面缓存写回数据之后,他们应调用 file_check_and_advance_wb_err 以确保 struct file
的错误光标已前进到后备设备发出的错误流中的正确位置。
struct address_space_operations¶
这描述了 VFS 如何操作您的文件系统中文件到页面缓存的映射。定义了以下成员
struct address_space_operations {
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);
};
read_folio
由页面缓存调用以从后备存储读取 folio。“file” 参数为网络文件系统提供身份验证信息,通常不被基于块的文件系统使用。如果调用者没有打开的文件(例如,如果内核正在执行自己的读取而不是代表具有打开文件的用户空间进程执行读取),则它可能为 NULL。
如果映射不支持大型 folios,则 folio 将包含单个页面。调用 read_folio 时将锁定 folio。如果读取成功完成,则应将 folio 标记为 uptodate。文件系统应在读取完成后解锁 folio,无论它是成功还是失败。文件系统不需要修改 folio 上的引用计数;页面缓存保留引用计数,并且在解锁 folio 之前不会释放该引用计数。
文件系统可以同步实现 ->read_folio()。在正常操作中,通过 ->readahead() 方法读取 folios。仅当此失败,或者调用者需要等待读取完成时,页面缓存才会调用 ->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 的页面并将它们写回。
dirty_folio
由 VM 调用以将 folio 标记为 dirty。 如果地址空间将私有数据附加到 folio,并且该数据需要在 folio 被标记为 dirty 时更新,则尤其需要这样做。 例如,当修改内存映射页面时,会调用此函数。如果已定义,它应设置 folio 的 dirty 标志以及 i_pages 中的 PAGECACHE_TAG_DIRTY 搜索标记。
readahead
由 VM 调用以读取与 address_space 对象关联的页面。 这些页面在页面缓存中是连续的,并且已锁定。 在对每个页面启动 I/O 后,实现应减少页面引用计数。 通常,该页面将由 I/O 完成处理程序解锁。 页面集分为一些同步页面和一些异步页面,rac->ra->async_size 给出了异步页面的数量。 文件系统应尝试读取所有同步页面,但可能会决定在到达异步页面后停止。 如果它确实决定停止尝试 I/O,则可以简单地返回。 调用者将从地址空间中删除剩余页面,解锁它们并减少页面引用计数。 如果 I/O 成功完成,则设置 PageUptodate。
write_begin
由通用缓冲写入代码调用,以要求文件系统准备在文件中给定的偏移量处写入 len 个字节。 address_space 应通过在必要时分配空间并执行任何其他内部管理来检查写入是否能够完成。 如果写入将更新存储上的任何基本块的部分,则应预先读取这些块(如果尚未读取),以便可以正确写出更新后的块。
文件系统必须在
*foliop
中返回指定偏移量的锁定 pagecache folio,以供调用者写入。它必须能够处理短写入(其中传递给 write_begin 的长度大于复制到 folio 中的字节数)。
可以在 fsdata 中返回一个 void *,然后将其传递到 write_end。
成功时返回 0;失败时返回 < 0(这是错误代码),在这种情况下,不会调用 write_end。
write_end
在成功 write_begin 和数据复制之后,必须调用 write_end。 len 是传递给 write_begin 的原始 len,copied 是能够复制的数量。
文件系统必须负责解锁 folio,减少其引用计数并更新 i_size。
失败时返回 < 0,否则返回能够复制到 pagecache 中的字节数(<= 'copied')。
bmap
由 VFS 调用以将对象中的逻辑块偏移量映射到物理块号。 此方法由 FIBMAP ioctl 使用,并用于处理交换文件。 为了能够交换到文件,该文件必须具有到块设备的稳定映射。 交换系统不经过文件系统,而是使用 bmap 找出文件中块的位置,并直接使用这些地址。
invalidate_folio
如果 folio 具有私有数据,则当要从地址空间中删除部分或全部 folio 时,将调用 invalidate_folio。 这通常对应于截断、打孔或地址空间的完全无效(在后一种情况下,“offset”将始终为 0,“length”将为
folio_size()
)。 应更新与 folio 关联的任何私有数据以反映此截断。 如果 offset 为 0 且 length 为folio_size()
,则应释放私有数据,因为必须能够完全丢弃 folio。 可以通过调用 ->release_folio 函数来完成,但在这种情况下,释放必须成功。release_folio
在具有私有数据的 folio 上调用 release_folio 以告知文件系统 folio 即将被释放。 ->release_folio 应从 folio 中删除任何私有数据并清除私有标志。 如果 release_folio() 失败,则应返回 false。 release_folio() 用于两个不同的但相关的案例。 第一个是 VM 想要释放没有活动用户的干净 folio。 如果 ->release_folio 成功,则将从 address_space 中删除该 folio 并释放。
第二种情况是已请求使地址空间中的部分或全部 folio 无效。 这可以通过 fadvise(POSIX_FADV_DONTNEED) 系统调用或由文件系统显式请求,如 nfs 和 9p 所做的那样(当它们认为缓存可能与存储过时时),通过调用
invalidate_inode_pages2()
。 如果文件系统进行这样的调用,并且需要确保所有 folio 都无效,则其 release_folio 将需要确保这一点。 如果它还不能释放私有数据,则可能会清除 uptodate 标志。free_folio
一旦 folio 在页面缓存中不再可见,就会调用 free_folio,以便清理任何私有数据。 由于它可能被内存回收器调用,因此它不应假设原始 address_space 映射仍然存在,并且不应阻塞。
direct_IO
由通用读/写例程调用以执行 direct_IO - 即绕过页面缓存并在存储和应用程序的地址空间之间直接传输数据的 IO 请求。
migrate_folio
这用于压缩物理内存使用。 如果 VM 想要重新定位 folio(可能来自发出迫在眉睫的故障信号的内存设备),它会将新的 folio 和旧的 folio 传递给此函数。 migrate_folio 应跨传输任何私有数据并更新它具有的对 folio 的任何引用。
launder_folio
在释放 folio 之前调用 - 它写回 dirty folio。 为了防止重新标记 folio 为 dirty,在整个操作过程中它保持锁定状态。
is_partially_uptodate
当通过页面缓存读取文件时,如果底层块大小小于 folio 的大小,则由 VM 调用。 如果所需的块是最新的,则读取可以在不需要 I/O 来使整个页面保持最新状态的情况下完成。
is_dirty_writeback
当尝试回收 folio 时,由 VM 调用。 VM 使用 dirty 和 writeback 信息来确定是否需要停止以允许刷新器有机会完成一些 IO。 通常,它可以使用 folio_test_dirty 和 folio_test_writeback,但某些文件系统具有更复杂的状态(NFS 中的不稳定 folio 会阻止回收),或者由于锁定问题而未设置这些标志。 此回调允许文件系统向 VM 指示是否应将 folio 视为 dirty 或 writeback 以用于停止的目的。
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
在 swapoff 时对 swap_activate 成功的文件调用。
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(2) 系统调用使用
setlease
由 VFS 调用以设置或释放文件锁定租约。 setlease 实现应调用 generic_setlease 以在设置租约后记录或删除 inode 中的租约。
fallocate
由 VFS 调用以预先分配块或打孔。
copy_file_range
由 copy_file_range(2) 系统调用调用。
remap_file_range
由 ioctl(2) 系统调用调用,用于 FICLONERANGE 和 FICLONE 以及 FIDEDUPERANGE 命令以重新映射文件范围。 实现应将源文件的 pos_in 处的 len 字节重新映射到 dest 文件在 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¶
这描述了文件系统如何重载标准 dentry 操作。 Dentries 和 dcache 是 VFS 和各个文件系统实现的领域。 设备驱动程序在这里没有任何业务。 这些方法可以设置为 NULL,因为它们是可选的,或者 VFS 使用默认值。 截至内核 2.6.22,定义了以下成员
struct dentry_operations {
int (*d_revalidate)(struct inode *, const struct qstr *,
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);
bool (*d_unalias_trylock)(const struct dentry *);
void (*d_unalias_unlock)(const struct dentry *);
};
d_revalidate
当 VFS 需要重新验证 dentry 时调用。 每当名称查找在 dcache 中找到 dentry 时都会调用此函数。 大多数本地文件系统将其保留为 NULL,因为它们在 dcache 中的所有 dentry 都是有效的。 网络文件系统不同,因为服务器上的内容可能会发生变化,而客户端不一定知道这一点。
如果 dentry 仍然有效,此函数应返回正值,如果无效,则返回零或负错误代码。
d_revalidate 可以在 rcu-walk 模式下调用(flags & LOOKUP_RCU)。 如果处于 rcu-walk 模式,文件系统必须在不阻塞或存储到 dentry 的情况下重新验证 dentry,d_parent 和 d_inode 不应在没有注意的情况下使用(因为它们可能会发生变化,并且在 d_inode 情况下,甚至可能在我们下面变为 NULL)。
如果遇到 rcu-walk 无法处理的情况,则返回 -ECHILD,它将在 ref-walk 模式下再次调用。
d_weak_revalidate
当 VFS 需要重新验证“跳转”的 dentry 时调用。 当路径遍历在未通过在父目录中查找获得的 dentry 处结束时,会调用此函数。 这包括“/”、“.”和“..”,以及 procfs 样式的符号链接和挂载点遍历。
在这种情况下,我们不太关心 dentry 是否仍然完全正确,而是关心 inode 是否仍然有效。 与 d_revalidate 一样,大多数本地文件系统会将此设置为 NULL,因为它们的 dcache 条目始终有效。
此函数具有与 d_revalidate 相同的返回代码语义。
d_weak_revalidate 仅在离开 rcu-walk 模式后调用。
d_hash
当 VFS 将 dentry 添加到哈希表时调用。 传递给 d_hash 的第一个 dentry 是名称要哈希到的父目录。
与 d_compare 关于取消引用等安全性的相同锁定和同步规则。
d_compare
调用以将 dentry 名称与给定名称进行比较。 第一个 dentry 是要比较的 dentry 的父项,第二个是子 dentry。 len 和 name 字符串是要比较的 dentry 的属性。 qstr 是要与其比较的名称。
必须是常量和幂等的,如果可能,不应采用锁,也不应存储到 dentry 中。 如果没有特别注意,不应取消引用 dentry 之外的指针(例如,不应使用 d_parent、d_inode、d_name)。
但是,我们的 vfsmount 已固定,并且 RCU 已保持,因此 dentry 和 inode 不会消失,我们的 sb 或文件系统模块也不会消失。 可以使用 ->d_sb。
这是一个棘手的调用约定,因为它需要在“rcu-walk”下调用,即没有任何锁定或引用。在事物上。
d_delete
当最后一个对 dentry 的引用被删除并且 dcache 正在决定是否缓存它时调用。 返回 1 以立即删除,或返回 0 以缓存 dentry。 默认为 NULL,这意味着始终缓存可访问的 dentry。 d_delete 必须是常量和幂等的。
d_init
在分配 dentry 时调用
d_release
在真正取消分配 dentry 时调用
d_iput
当 dentry 丢失其 inode 时调用(就在取消分配之前)。 此值为 NULL 时的默认值为 VFS 调用
iput()
。 如果您定义此方法,则必须自己调用iput()
d_dname
当应生成 dentry 的路径名时调用。 对于某些伪文件系统(sockfs、pipefs 等),延迟路径名生成非常有用。(不应在创建 dentry 时执行,而仅在需要路径时执行)。 真正的文件系统可能不想使用它,因为它们的 dentry 存在于全局 dcache 哈希中,因此它们的哈希应该是不可变的。 由于未保持锁,因此 d_dname() 不应尝试修改 dentry 本身,除非使用适当的 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
在遍历自动挂载 dentry 时调用(可选)。 这应创建一个新的 VFS 挂载记录,并将该记录返回给调用方。 调用方将收到一个路径参数,该参数提供自动挂载目录以描述自动挂载目标和父 VFS 挂载记录以提供可继承的挂载参数。 如果其他人设法首先进行自动挂载,则应返回 NULL。 如果 vfsmount 创建失败,则应返回错误代码。 如果返回 -EISDIR,则目录将被视为普通目录,并返回给 pathwalk 以继续遍历。
如果返回 vfsmount,调用方将尝试将其挂载在挂载点上,并且在失败的情况下将从其过期列表中删除 vfsmount。
仅当在 dentry 上设置 DCACHE_NEED_AUTOMOUNT 时才使用此函数。 如果在添加的 inode 上设置了 S_AUTOMOUNT,则 __d_instantiate() 会设置此标志。
d_manage
调用以允许文件系统管理从 dentry 的转换(可选)。 例如,这允许 autofs 阻止等待探索“挂载点”背后的客户端,同时让守护程序过去并在那里构造子树。 应返回 0 以使调用进程继续。 可以返回 -EISDIR 以告诉 pathwalk 将此目录用作普通目录,并忽略挂载在其上的任何内容,并且不检查自动挂载标志。 任何其他错误代码将完全中止 pathwalk。
如果 'rcu_walk' 参数为 true,则调用方正在 RCU-walk 模式下进行 pathwalk。 在此模式下不允许睡眠,并且可以通过返回 -ECHILD 来要求调用方离开并再次调用。 也可以返回 -EISDIR 以告诉 pathwalk 忽略 d_automount 或任何挂载。
仅当在正从中传输的 dentry 上设置 DCACHE_MANAGE_TRANSIT 时才使用此函数。
d_real
覆盖/联合类型文件系统实现此方法以返回隐藏在覆盖层后面的常规文件的基础 dentry 之一。
“type”参数采用 D_REAL_DATA 或 D_REAL_METADATA 值,以返回分别引用托管文件数据或元数据的 inode 的实际基础 dentry。
对于非常规文件,将返回“dentry”参数。
d_unalias_trylock
如果存在,则将在移动预先存在的附加别名之前由
d_splice_alias()
调用。 返回 false 会阻止 __d_move(),从而导致d_splice_alias()
以 -ESTALE 失败。基本原理:设置 FS_RENAME_DOES_D_MOVE 将阻止从文件系统方法外部调用 d_move() 和 d_exchange(); 但是,它不能保证附加的 dentry 不会被
d_splice_alias()
重命名或移动,因为d_splice_alias()
找到了目录 inode 的预先存在的别名。 通常我们不会在意; 但是,想要通过阻塞操作稳定到根目录的整个路径的东西可能需要它。 有关一个(并且希望只是一个)示例,请参见 9p。d_unalias_unlock
应与
d_unalias_trylock
配对; 该调用是在 __d_unalias() 中的 __d_move() 调用之后调用的。
每个 dentry 都有一个指向其父 dentry 的指针,以及一个子 dentry 的哈希列表。 子 dentry 基本上就像目录中的文件。
目录项缓存 API¶
定义了许多允许文件系统操作 dentry 的函数
dget
为现有 dentry 打开一个新句柄(这只会增加使用计数)
dput
关闭 dentry 的句柄(减少使用计数)。 如果使用计数降至 0,并且 dentry 仍在父项的哈希中,则会调用“d_delete”方法来检查是否应缓存它。 如果不应缓存,或者 dentry 未哈希,则将其删除。 否则,缓存的 dentry 将放入 LRU 列表,以便在内存短缺时回收。
d_drop
这会从其父哈希列表中取消哈希 dentry。 如果其使用计数降至 0,则随后对 dput() 的调用将取消分配 dentry
d_delete
删除 dentry。 如果没有其他对 dentry 的打开引用,则 dentry 将变为负 dentry(调用 d_iput() 方法)。 如果有其他引用,则会调用
d_drop()
d_add
将 dentry 添加到其父哈希列表,然后调用
d_instantiate()
d_instantiate
将 dentry 添加到 inode 的别名哈希列表并更新“d_inode”成员。 应设置/递增 inode 结构中的“i_count”成员。 如果 inode 指针为 NULL,则 dentry 称为“负 dentry”。 通常在为现有负 dentry 创建 inode 时调用此函数
d_lookup
给定其父项和路径名组件,查找 dentry。它从 dcache 哈希表中查找该给定名称的子项。 如果找到,则引用计数将递增并返回 dentry。 调用方必须在使用完 dentry 后使用 dput() 释放 dentry。
挂载选项¶
解析选项¶
在挂载和重新挂载时,文件系统将传递一个字符串,其中包含以逗号分隔的挂载选项列表。 这些选项可以具有以下形式之一
选项 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