admin管理员组

文章数量:1599275

目录

Linux虚拟文件系统概述

介绍

目录条目缓存(dcache)

索引节点对象

文件对象

注册和挂载文件系统

结构file_system_type

超级块对象

struct super_operations

struct xattr_handlers

索引节点对象

struct inode_operations

地址空间对象

写回期间处理错误

结构地址空间操作

文件对象

结构文件操作

目录条目缓存(dcache)

struct dentry_operations

目录条目缓存API

挂载选项

解析选项

显示选项

资源资源


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)等。文件文档/文件系统/locking.rst中描述了文件系统锁定。

目录条目缓存(dcache)

VFS实现了open(2),stat(2),chmod(2)和类似的系统调用。VFS使用传递给它们的pathname参数搜索目录条目缓存(也称为dentry缓存或dcache)。这提供了一种非常快速的查找机制,可以将路径名(文件名)转换为特定的dentry。齿孔位于RAM中,并且永远不会保存到光盘中:它们仅出于性能目的而存在。

Dentry缓存旨在成为整个文件空间的视图。由于大多数计算机无法同时将所有内存放入RAM中,因此缺少缓存的某些位。为了将您的路径名解析为dentry,VFS可能必须在此过程中诉诸于创建牙科,然后加载inode。这是通过查找索引节点来完成的。

索引节点对象

单个Dentry通常具有指向inode的指针。索引节点是文件系统对象,例如常规文件,目录,FIFO和其他野兽。它们要么驻留在光盘上(用于块设备文件系统),要么驻留在内存中(用于伪文件系统)。光盘上存在的inode会在需要时复制到内存中,并将对inode的更改写回到光盘上。一个单一的inode可以由多个牙科程序指向(例如,执行硬链接)。

要查找索引节点,需要VFS调用父目录索引节点的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()方法。引用由-> mount()返回的树的新vfsmount将附加到安装点,因此,当路径名解析达到安装点时,它将跳入该vfsmount的根目录。

您可以在文件/ proc / filesystems中查看所有已注册到内核的文件系统。

结构file_system_type

这描述了文件系统。从内核2.6.39开始,定义了以下成员:

struct file_system_operations {
        const char *name;
        int fs_flags;
        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 list_head fs_supers;
        struct lock_class_key s_lock_key;
        struct lock_class_key s_umount_key;
};

name

文件系统类型的名称,例如“ ext2”,“ iso9660”,“ msdos”等

fs_flags

各种标志(即FS_REQUIRES_DEV,FS_NO_DCACHE等)

mount

挂载此文件系统新实例时调用的方法

kill_sb

该文件系统实例应关闭时调用的方法

owner

供内部VFS使用:大多数情况下,应将其初始化为THIS_MODULE。

next

供内部VFS使用:您应该将此初始化为NULL

s_lock_key,s_umount_key:特定于lockdep

mount()方法具有以下参数:

struct file_system_type *fs_type

描述文件系统,部分由特定文件系统代码初始化

int flags

挂载标志

const char *dev_name

我们正在安装的设备名称。

void *data

任意安装选项,通常以ASCII字符串的形式出现(请参阅“安装选项”部分)

mount()方法必须返回调用者请求的树的根目录。必须获取对其超级块的活动引用,并且必须锁定超级块。失败时,应返回ERR_PTR(error)。

这些参数与mount(2)的参数匹配,并且它们的解释取决于文件系统类型。例如,对于块文件系统,dev_name被解释为块设备名称,该设备将打开,并且如果该设备包含合适的文件系统映像,则该方法将相应地创建并初始化struct super_block,并将其根目录权返回给调用者。

-> 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如何操纵文件系统的超级块。从内核2.6.22开始,定义了以下成员:

struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);
        void (*destroy_inode)(struct inode *);

        void (*dirty_inode) (struct inode *, int flags);
        int (*write_inode) (struct inode *, int);
        void (*drop_inode) (struct inode *);
        void (*delete_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        int (*sync_fs)(struct super_block *sb, int wait);
        int (*freeze_fs) (struct super_block *);
        int (*unfreeze_fs) (struct super_block *);
        int (*statfs) (struct dentry *, struct kstatfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*clear_inode) (struct inode *);
        void (*umount_begin) (struct super_block *);

        int (*show_options)(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);
        int (*nr_cached_objects)(struct super_block *);
        void (*free_cached_objects)(struct super_block *, int);
};

除非另有说明,否则在不持有任何锁的情况下调用所有方法。这意味着大多数方法都可以安全地阻止。所有方法都只能从进程上下文中调用(即不能从中断处理程序或下半部分调用)。

alloc_inode

alloc_inode()调用此方法为struct inode分配内存并对其进行初始化。如果未定义此功能,则会分配一个简单的“结构索引节点”。通常,alloc_inode将用于分配更大的结构,该结构包含嵌入其中的“结构inode”。

destroy_inode

destroy_inode()调用此方法以释放为struct inode分配的资源。仅在定义了-> alloc_inode并且仅撤消-> alloc_inode完成的所有操作时才需要。

dirty_inode

VFS调用此方法将inode标记为脏。

write_inode

当VFS需要将inode写入磁盘时,将调用此方法。第二个参数指示写入是否应该同步,并非所有文件系统都检查该标志。

drop_inode

在删除对inode的最后一次访问时调用,并保持inode-> i_lock自旋锁。

此方法应该为NULL(常规UNIX文件系统语义)或“ generic_delete_inode”(对于不希望缓存inode的文件系统-不管i_nlink的值如何,都会始终调用“ delete_inode”)

“ generic_delete_inode()”行为与在put_inode()情况下使用“ force_delete”的旧做法等效,但不具有“ force_delete()”方法所具有的竞争。

delete_inode

当VFS想要删除inode时调用

put_super

当VFS希望释放超级块(即卸载)时调用。这是通过持有超级块锁来调用的

sync_fs

VFS正在写出与超级块关联的所有脏数据时调用。第二个参数指示该方法是否应等待写完为止。可选的。

freeze_fs

当VFS锁定文件系统并将其强制为一致状态时调用。逻辑卷管理器(LVM)当前使用此方法。

unfreeze_fs

当VFS解锁文件系统并使它再次可写时调用。

statfs

当VFS需要获取文件系统统计信息时调用。

remount_fs

重新挂载文件系统时调用。这是在持有内核锁的情况下进行的

clear_inode

然后调用VFS清除索引节点。可选的

umount_begin

当VFS卸载文件系统时调用。

show_options

由VFS调用以显示/ proc / <pid> / mounts的安装选项。(请参见“安装选项”部分)

quota_read

由VFS调用以从文件系统配额文件读取。

quota_write

由VFS调用以写入文件系统配额文件。

nr_cached_objects

由sb缓存缩减函数调用的文件系统,以返回其包含的可释放缓存对象的数量。可选的。

free_cache_objects

由sb缓存缩减函数调用,用于文件系统扫描指示的对象数以尝试释放它们。可选的,但是任何实现此方法的文件系统也需要实现-> nr_cached_objects才能正确调用它。

对于文件系统可能遇到的任何错误,我们无法做任何事情,因此返回值类型为void。如果VM试图在GFP_NOFS条件下进行回收,则永远不会调用此方法,因此此方法本身无需处理这种情况。

实现必须在完成的任何扫描循环中包括条件重新计划调用。这使VFS可以确定适当的扫描批处理大小,而不必担心由于大的扫描批处理大小而导致实现是否会引起延迟问题。

设置索引节点的人员负责填写“ i_op”字段。这是指向“结构inode_operations”的指针,该结构描述了可以在各个inode上执行的方法。

struct xattr_handlers

在支持扩展属性(xattrs)的文件系统上,s_xattr超级块字段指向以NULL结尾的xattr处理程序数组。扩展属性是名称:值对。

name

指示处理程序匹配具有指定名称的属性(例如“ system.posix_acl_access”);前缀字段必须为NULL。

prefix

指示处理程序将所有具有指定名称前缀的属性(例如“ user”)匹配。名称字段必须为NULL。

list

确定是否应该为特定的dentry列出与该xattr处理程序匹配的属性。由某些listxattr实现(例如generic_listxattr)使用。

get

由VFS调用以获取特定扩展属性的值。此方法由getxattr(2)系统调用调用。

set

由VFS调用以设置特定扩展属性的值。当新值为NULL时,调用以删除特定的扩展属性。setxattr(2)和removexattr(2)系统调用调用此方法。

当文件系统的xattr处理程序都不与指定的属性名称匹配时,或者当文件系统不支持扩展属性时,各种*xattr(2)系统调用将返回-EOPNOTSUPP。

索引节点对象

索引节点对象表示文件系统中的对象。

struct inode_operations

这描述了VFS如何操作文件系统中的索引节点。从内核2.6.22开始,定义了以下成员:

struct inode_operations {
        int (*create) (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 inode *,struct dentry *,const char *);
        int (*mkdir) (struct inode *,struct dentry *,umode_t);
        int (*rmdir) (struct inode *,struct dentry *);
        int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
        int (*rename) (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 inode *, int);
        int (*get_acl)(struct inode *, int);
        int (*setattr) (struct dentry *, struct iattr *);
        int (*getattr) (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 inode *, struct dentry *, umode_t);
};

同样,除非另有说明,否则调用所有方法时都不会持有任何锁。

create

由open(2)和creat(2)系统调用调用。仅在要支持常规文件时才需要。您获得的牙科应用程序应该没有索引节点(即它应该是负性牙科应用程序)。在这里您可能会调用d_instantiate()dentry和新创建的inode

lookup

当VFS需要在父目录中查找inode时调用。要查找的名称在牙科中找到。必须调用此方法才能d_add()将找到的索引节点插入到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)系统调用调用。仅在要支持硬链接时才需要。您可能需要d_instantiate()像在create()方法中那样调用

unlink

由unlink(2)系统调用调用。仅当您要支持删除inode时才需要

symlink

由symlink(2)系统调用调用。仅在要支持符号链接时才需要。您可能需要d_instantiate()像在create()方法中那样调用

mkdir

由mkdir(2)系统调用调用。仅在要支持创建子目录时才需要。您可能需要d_instantiate()像在create()方法中那样调用

rmdir

由rmdir(2)系统调用调用。仅在要支持删除子目录时才需要

mknod

由mknod(2)系统调用调用以创建设备(字符,块)inode或命名管道(FIFO)或套接字。仅在要支持创建这些类型的inode时才需要。您可能需要d_instantiate()像在create()方法中那样调用

rename

由named(2)系统调用调用,以将对象重命名为具有第二个inode和dentry给定的父对象和名称。

对于任何不受支持或未知的标志,文件系统必须返回-EINVAL。当前,实现了以下标志:(1)RENAME_NOREPLACE:该标志指示,如果重命名的目标存在,则重命名应该失败,并带有-EEXIST而不是替换目标。VFS已经检查是否存在,因此对于本地文件系统,RENAME_NOREPLACE实现等效于纯重命名。(2)RENAME_EXCHANGE:交换源和目标。两者必须存在;这由VFS检查。与普通重命名不同,源和目标的类型可能不同。

get_link

由VFS调用以跟随指向它所指向的索引节点的符号链接。仅在要支持符号链接时才需要。此方法返回符号链接主体以进行遍历(并可能使用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()。-> i_link必须在RCU宽限期之后才能释放。写入-> i_link post-iget()时间需要“释放”内存障碍。

readlink

对于-> get_link使用nd_jump_link()或object实际上不是符号链接的情况,现在这只是readlink(2)使用的替代。通常,文件系统仅应为符号链接实现-> 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调用以更新特定时间或索引节点的i_version。如果未定义,则VFS将更新索引节点本身并调用mark_inode_dirty_sync。

atomic_open

调用打开的最后一个组件。使用此可选方法,文件系统可以查找,并可能通过一次原子操作创建并打开文件。如果它想让调用者实际打开(例如,如果文件原来是符号链接,设备,或者仅仅是文件系统不会对其进行原子打开),则可以通过返回finish_no_open(file,dentry)来发出信号。仅当最后一个分量为负或需要查找时才调用此方法。缓存的肯定牙科仍然由f_op-> open()处理。如果已创建文件,则应在文件-> f_mode中设置FMODE_CREATED标志。在O_EXCL的情况下,该方法仅在文件不存在时才必须成功,因此应始终在成功时设置FMODE_CREATED。

tmpfile

在O_TMPFILE open()的末尾调用。可选,等效于原子地创建,打开和取消链接给定目录中的文件。

地址空间对象

地址空间对象用于对页面缓存中的页面进行分组和管理。它可以用来跟踪文件(或其他任何文件)中的页面,也可以跟踪文件的各个部分到进程地址空间的映射。

地址空间可以提供许多不同但又相关的服务。其中包括传达内存压力,按地址查找页面以及跟踪标记为“脏”或“写回”的页面。

第一个可以独立于其他使用。VM可以尝试写脏页以对其进行清理,或释放干净页以重新使用它们。为此,它可以在脏页上调用-> writepage方法,在设置了PagePrivate的干净页上调用-> releasepage。没有PagePrivate并且没有外部引用的干净页面将被释放,而不会通知address_space。

为了实现此功能,需要将页面放置在具有lru_cache_add的LRU上,并且每当使用页面时都需要调用mark_page_active。

页面通常通过-> index保留在基数树索引中。该树维护有关每个页面的PG_Dirty和PG_Writeback状态的信息,以便可以快速找到带有这些标志之一的页面。

脏标签主要由mpage_writepages-默认-> writepages方法使用。它使用标记查找要调用的脏页-> writepage。如果不使用mpage_writepages(即地址提供其自己的-> writepages),则PAGECACHE_TAG_DIRTY标记几乎未使用。write_inode_now和sync_inode确实使用它(通过__sync_single_inode)来检查-> writepages是否已成功写出整个address_space。

Filemap * wait *和sync_page *函数通过filemap_fdatawait_range使用Writeback标记来等待所有写回完成。

address_space处理程序通常可以使用“结构页面”中的“私有”字段将额外的信息附加到页面上。如果附加了此类信息,则应设置PG_Private标志。这将导致各种VM例程对address_space处理程序进行额外的调用以处理该数据。

地址空间充当存储和应用程序之间的中介。一次将数据读入整个页面的地址空间,然后通过复制页面或通过内存映射页面将其提供给应用程序。数据由应用程序写入地址空间,然后通常在整个页面中写回到存储,但是address_space具有对写入大小的更好控制。

读取过程本质上只需要'readpage'。写入过程更加复杂,并且使用write_begin / write_end或set_page_dirty将数据写入到address_space中,并使用writepage和writepages将数据写回存储中。

在address_space中添加页面或从中删除页面受inode的i_mutex保护。

将数据写入页面时,应设置PG_Dirty标志。它通常保持设置状态,直到writepage要求将其写入为止。这应该清除PG_Dirty并设置PG_Writeback。可以在清除PG_Dirty之后的任何时候实际写入它。一旦知道是安全的,就清除PG_Writeback。

Writeback利用writeback_control结构来指导操作。这为writepage和writepages操作提供了一些有关回写请求的性质和原因以及完成该请求的约束的信息。它还用于将有关写页面或写页面请求结果的信息返回给调用方。

写回期间处理错误

大多数执行缓冲I / O的应用程序都会定期调用文件同步调用(fsync,fdatasync,msync或sync_file_range),以确保写入的数据已到达后备存储。如果在写回过程中出现错误,他们希望在发出文件同步请求时报告该错误。在报告了一个请求的错误之后,对同一文件描述符的后续请求应返回0,除非自上次文件同步以来发生了进一步的写回错误。

理想情况下,内核只会报告有关文件描述的错误,这些描述是在完成写操作之后又无法回写的。通用的页面缓存基础结构不会跟踪已弄脏每个页面的文件描述,因此无法确定哪个文件描述符应该返回错误。

取而代之的是,内核中的通用写回错误跟踪基础结构可以将发生错误时打开的所有文件描述上的错误报告给fsync。在有多个编写器的情况下,即使通过该特定文件描述符完成的所有写操作都成功(或者即使该文件描述符上根本没有写操作),所有这些操作者都将在随后的fsync中返回错误。

希望使用此基础结构的文件系统应在发生错误时调用mapping_set_error将错误记录在address_space中。然后,在以他们的file-> fsync操作从页面缓存中写回数据之后,他们应该调用file_check_and_advance_wb_err,以确保结构文件的错误游标已前进到后备设备发出的错误流中的正确位置。

结构地址空间操作

这说明了VFS如何操纵文件到文件系统中页面高速缓存的映射。定义了以下成员:

struct address_space_operations {
        int (*writepage)(struct page *page, struct writeback_control *wbc);
        int (*readpage)(struct file *, struct page *);
        int (*writepages)(struct address_space *, struct writeback_control *);
        int (*set_page_dirty)(struct page *page);
        void (*readahead)(struct readahead_control *);
        int (*readpages)(struct file *filp, struct address_space *mapping,
                         struct list_head *pages, unsigned nr_pages);
        int (*write_begin)(struct file *, struct address_space *mapping,
                           loff_t pos, unsigned len, unsigned flags,
                        struct page **pagep, void **fsdata);
        int (*write_end)(struct file *, struct address_space *mapping,
                         loff_t pos, unsigned len, unsigned copied,
                         struct page *page, void *fsdata);
        sector_t (*bmap)(struct address_space *, sector_t);
        void (*invalidatepage) (struct page *, unsigned int, unsigned int);
        int (*releasepage) (struct page *, int);
        void (*freepage)(struct page *);
        ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
        /* isolate a page for migration */
        bool (*isolate_page) (struct page *, isolate_mode_t);
        /* migrate the contents of a page to the specified target */
        int (*migratepage) (struct page *, struct page *);
        /* put migration-failed page back to right list */
        void (*putback_page) (struct page *);
        int (*launder_page) (struct page *);

        int (*is_partially_uptodate) (struct page *, unsigned long,
                                      unsigned long);
        void (*is_dirty_writeback) (struct page *, bool *, bool *);
        int (*error_remove_page) (struct mapping *mapping, struct page *page);
        int (*swap_activate)(struct file *);
        int (*swap_deactivate)(struct file *);
};

writepage

由VM调用以将脏页写入后备存储。出于数据完整性原因(即“同步”)或释放内存(刷新),可能会发生这种情况。区别可以在wbc-> sync_mode中看到。PG_Dirty标志已被清除,PageLocked为true。writepage应开始写出,应设置PG_Writeback,并应确保在写操作完成时以同步方式或异步方式解锁页面。

如果wbc-> sync_mode为WB_SYNC_NONE,则-> writepage不必在出现问题时太费劲,并且可以选择从映射中写出其他页面(如果更容易的话)(例如,由于内部依赖性)。如果选择不开始写出,则应返回AOP_WRITEPAGE_ACTIVATE,以使VM不会在该页面上继续调用-> writepage。

有关更多详细信息,请参见文件“锁定”。

readpage

由VM调用以从后备存储读取页面。调用readpage时,该页面将被锁定;读取完成后,该页面应被解锁并标记为最新。如果-> readpage发现出于某种原因需要解锁页面,则可以这样做,然后返回AOP_TRUNCATED_PAGE。在这种情况下,页面将被重新定位,重新锁定,如果一切成功,将再次调用-> readpage。

writepages

由VM调用以写出与address_space对象关联的页面。如果wbc-> sync_mode为WBC_SYNC_ALL,则writeback_control将指定必须写出的页面范围。如果它是WBC_SYNC_NONE,则给出nr_to_write,并且如果可能的话应该写入许多页面。如果没有给出-> writepages,则使用mpage_writepages。这将从地址空间中选择标记为DIRTY的页面,并将它们传递给-> writepage。

set_page_dirty

由VM调用以将页面设置为脏。如果地址空间将私有数据附加到页面,并且在弄脏页面时需要更新该数据,则尤其需要这样做。例如,在修改内存映射页面时,将其称为。如果已定义,则应在基数树中设置PageDirty标志和PAGECACHE_TAG_DIRTY标签。

readahead

VM调用以读取与address_space对象关联的页面。页面在页面缓存中是连续的并被锁定。在每个页面上启动I / O之后,实现应减少页面引用计数。通常,页面将由I / O完成处理程序解锁。如果文件系统决定在到达预读窗口结束之前停止尝试I / O,则可以简单地返回。呼叫者将减少页面引用计数并为您解锁其余页面。如果I / O成功完成,则设置PageUptodate。在任何页面上设置PageError都将被忽略;如果发生I / O错误,只需解锁页面即可。

readpages

由VM调用以读取与address_space对象关联的页面。本质上,这只是readpage的矢量版本。不仅要一页,还需要几页。readpages仅用于预读,因此将忽略读取错误。如果有任何问题,请随时放弃。该接口已弃用,并将于2020年底删除;改为实施预读。

write_begin

由通用的缓冲写入代码调用,以要求文件系统准备在文件中给定的偏移量处写入len个字节。address_space应该通过在必要时分配空间并进行其他内部整理来检查写入是否能够完成。如果写操作将更新存储中任何基本块的一部分,则应预先读取这些块(如果尚未读取),以便可以正确地写出更新的块。

文件系统必须返回锁定偏移的页面高速缓存页面,以指定的偏移量in *pagep,以供调用者写入。

它必须能够应付短写(传递给write_begin的长度大于复制到页面中的字节数)。

flags是AOP_FLAG_xxx标志的字段,在include / linux / fs.h中进行了描述。

可能在fsdata中返回void *,然后将其传递到write_end中。

成功返回0;<0失败(错误代码),在这种情况下,不会调用write_end。

write_end

成功执行write_begin和数据复制后,必须调用write_end。len是传递给write_begin的原始len,并且copyed是能够复制的数量。

文件系统必须负责解锁页面并释放其refcount,并更新i_size。

如果失败,则返回<0,否则返回能够复制到页面缓存中的字节数(<='已复制')。

bmap

由VFS调用以将对象内的逻辑块偏移量映射到物理块编号。FIBMAP ioctl使用此方法并用于交换文件。为了能够交换到文件,该文件必须具有到块设备的稳定映射。交换系统不遍历文件系统,而是使用bmap找出文件中块的位置,并直接使用这些地址。

invalidatepage

如果页面设置了PagePrivate,则当要从地址空间中删除部分或全部页面时,将调用invalidatepage。这通常对应于截断,打孔或地址空间的完全无效(在后一种情况下,“偏移”将始终为0,而​​“长度”将为PAGE_SIZE)。与页面关联的任何私有数据都应更新以反映此截断。如果offset为0,长度为PAGE_SIZE,则应释放私有数据,因为必须能够完全丢弃该页面。这可以通过调用-> releasepage函数来完成,但是在这种情况下,释放必须成功。

releasepage

在PagePrivate页面上调用releasepage以指示应尽可能释放该页面。-> releasepage应该从页面中删除所有私有数据,并清除PagePrivate标志。如果releasepage()由于某种原因失败,则必须使用0的返回值指示失败。releasepage()在两种不同但相关的情况下使用。第一种是VM找到没有活动用户的干净页面并希望将其设为免费页面时。如果-> releasepage成功,则该页面将从address_space中删除并变为空闲。

第二种情况是当请求使address_space中的某些或所有页面无效时。这可以通过fadvise(POSIX_FADV_DONTNEED)系统调用或通过文件系统显式地请求它来实现,就像nfs和9fs那样(当他们认为高速缓存的存储空间可能已过期时) invalidate_inode_pages2()。如果文件系统进行了这样的调用,并且需要确定所有页面均无效,那么它的释放页面将需要确保这一点。如果尚不能释放私有数据,它可能会清除PageUptodate位。

freepage

一旦页面在页面缓存中不再可见时,就会调用freepage,以允许清除任何私有数据。由于可以由内存回收器调用它,因此不应假定原始的address_space映射仍然存在,并且不应阻塞。

direct_IO

通用读/写例程调用该命令来执行direct_IO-即IO请求绕过页面缓存并直接在存储和应用程序的地址空间之间传输数据。

isolate_page

当隔离可移动的非lru页面时,由VM调用。如果成功隔离页面,则VM通过__SetPageIsolated将页面标记为PG_isolated。

migrate_page

这用于压缩物理内存使用量。如果VM要重定位页面(可能是从即将发出故障信号的存储卡上移开),它将为该功能传递新页面和旧页面。migrate_page应该跨所有传输私有数据并更新对该页面的引用。

putback_page

当隔离页面的迁移失败时,由VM调用。

launder_page

在释放页面之前调用-它写回脏页面。为了防止页面变脏,在整个操作过程中页面保持锁定状态。

is_partially_uptodate

当基础块大小!= pagesize时,VM通过页面缓存读取文件时由VM调用。如果所需的块是最新的,则读取可以完成,而无需IO使整个页面更新。

is_dirty_writeback

VM在尝试回收页面时调用。VM使用脏和回写信息来确定是否需要停顿以使刷新程序有机会完成一些IO。通常,它可以使用PageDirty和PageWriteback,但是某些文件系统具有更复杂的状态(NFS中不稳定的页面会阻止回收)或由于锁定问题而未设置这些标志。此回调允许文件系统向VM指示是否出于停顿的目的将页面视为脏页或写回。

error_remove_page

如果此地址空间可以截断,通常将其设置为generic_error_remove_page。用于内存故障处理。进行此设置意味着您将处理掉在您身下的页面,除非您将它们锁定或增加引用计数。

swap_activate

在文件上使用swapon分配空间(如有必要)并将块查找信息固定在内存中时调用。返回值为零表示成功,在这种情况下,该文件可用于后退交换空间。

swap_deactivate

在交换成功的swap_activate文件上调用。

文件对象

文件对象表示由进程打开的文件。在POSIX中,这也称为“打开文件描述”。

结构文件操作

这说明了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) (struct file *, struct dir_context *);
        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 *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        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

当VFS需要读取目录内容时调用

iterate_shared

当文件系统支持并发目录迭代器时,当VFS需要读取目录内容时调用

poll

当进程要检查该文件上是否有活动并且(可选)进入睡眠状态之前,由VFS调用。由select(2)和poll(2)系统调用调用

unlocked_ioctl

由ioctl(2)系统调用调用。

compat_ioctl

当32位系统调用为ioctl(2)系统调用时调用

用于64位内核。

mmap

由mmap(2)系统调用调用

open

当应该打开一个inode时由VFS调用。VFS打开文件时,将创建一个新的“结构文件”。然后,它为新分配的文件结构调用open方法。您可能会认为open方法确实属于“ struct inode_operations”,并且可能是正确的。我认为这样做是因为它使文件系统更易于实现。如果要指向设备结构,则open()方法是在文件结构中初始化“ private_data”成员的好地方

flush

由close(2)系统调用调用以刷新文件

release

当最后一个打开文件引用关闭时调用

fsync

由fsync(2)系统调用调用。另请参见上面标题为“回写期间的错误处理”的部分。

fasync

为文件启用异步(非阻塞)模式时,由fcntl(2)系统调用调用

lock

fcntl(2)系统调用F_GETLK,F_SETLK和F_SETLKW命令调用

get_unmapped_area

由mmap(2)系统调用调用

check_flags

由fcntl(2)系统调用以调用F_SETFL命令

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字节重新映射到pos_out的dest文件。实现必须处理传入len == 0的调用方;这意味着“重新映射到源文件的末尾”。返回值应为重新映射的字节数,如果在重新映射任何字节之前发生错误,则返回通常的负错误代码。remap_flags参数接受REMAP_FILE_ *标志。如果设置了REMAP_FILE_DEDUP,则只有在所请求的文件范围具有相同内容的情况下,实现才必须重新映射。如果设置了REMAP_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 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 *, const struct inode *);
};

d_revalidate

当VFS需要重新验证Dentry时调用。每当名称查找在dcache中找到dentry时,就会调用此方法。大多数本地文件系统将其保留为NULL,因为它们在dcache中的所有凭据均有效。网络文件系统是不同的,因为事情可能会在服务器上发生变化,而客户端不必知道这一点。

如果dentry仍然有效,则此函数应返回正值;如果无效,则应返回零或负错误代码。

可以在rcu-walk模式下调用d_revalidate(标志和LOOKUP_RCU)。如果在rcu-walk模式下,文件系统必须重新验证登齿,而不会阻塞或存储到该登齿中,则应小心使用d_parent和d_inode(因为它们可以更改,并且在d_inode情况下,甚至在我们的控制下变为NULL)。

如果遇到rcu-walk无法处理的情况,请返回-ECHILD,它将在ref-walk模式下再次调用。

_weak_revalidate

当VFS需要重新验证“跳跃的”牙科时,调用此方法。当路径遍历在登革树上结束而无法通过在父目录中进行查找获得时,将调用此方法。这包括 ”/”, ”。” 和“ ..”,以及procfs样式的符号链接和mountpoint遍历。

在这种情况下,我们不太关心牙科是否仍然完全正确,而是担心索引节点仍然有效。与d_revalidate一样,大多数本地文件系统会将其设置为NULL,因为它们的dcache条目始终有效。

该函数具有与d_revalidate相同的返回码语义。

仅在离开rcu-walk模式后才调用d_weak_revalidate。

d_hash

当VFS向哈希表中添加dentry时调用。传递给d_hash的第一个dentry是名称要哈希到的父目录。

与d_comp相同的锁定和同步规则涉及可以安全取消引用的内容等。

d_compare

调用以比较牙科名称与给定名称。第一个牙科是要比较的牙科的父级,第二个是子牙科。len和name字符串是要比较的Dentry的属性。qstr是与其进行比较的名称。

必须是恒定且幂等的,并且在可能的情况下不应使用锁,并且不应将其存储在牙科设备中。不应在没有过多注意的情况下取消引用牙科外部的指针(例如,不应使用d_parent,d_inode,d_name)。

但是,我们的vfsmount已固定,RCU已固定,因此牙科和inode不会消失,我们的sb或文件系统模块也不会消失。-> d_sb可以使用。

这是一个棘手的调用约定,因为它需要在“ rcu-walk”下调用,即。没有对事物的任何锁定或引用。

d_delete

在删除对dentry的最后一个引用并且dcache正在决定是否对其进行缓存时调用。返回1以立即删除,或返回0来缓存dentry。默认值为NULL,这意味着始终缓存可访问的dentry。d_delete必须是常数和幂等的。

d_init

分配牙科时调用

d_release

当牙科设备真正释放时调用

d_iput

当dentry失去其inode时调用(就在其被释放之前)。默认值为NULL时,是VFS调用iput()。如果定义此方法,则必须调用iput() 自己

d_dname

在应生成Dentry的路径名时调用。对于某些伪文件系统(sockfs,pipefs等)很有用,它可以延迟路径名的生成。(不是在创建dentry时执行此操作,而是仅在需要路径时执行此操作。)。实际的文件系统可能不希望使用它,因为它们的凭据存在于全局dcache哈希中,因此它们的哈希应该是不变的。由于没有锁,除非使用适当的SMP安全性,否则d_dname()不应尝试修改Dentry本身。注意: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从其过期列表中删除。应该在vfsmount上返回2个引用,以防止其自动过期-调用方将清除其他引用。

仅当在牙科设备上设置了DCACHE_NEED_AUTOMOUNT时,才使用此功能。如果在要添加的inode上设置了S_AUTOMOUNT,则由__d_instantiate()设置。

d_manage

调用以允许文件系统管理从dentry的过渡(可选)。例如,这允许autofs阻止客户机等待在“挂载点”后面进行探索,同时让守护进程通过并在那里构建子树。应该返回0以使调用过程继续。可以返回-EISDIR来告诉pathwalk使用该目录作为普通目录,并忽略安装在该目录上的任何内容,而不检查automount标志。任何其他错误代码将完全中止路径行走。

如果“ rcu_walk”参数为true,则调用方正在RCU漫游模式下进行路径漫游。在此模式下不允许睡眠,并且可以要求呼叫者离开它并通过返回-ECHILD再次呼叫。-EISDIR也可能返回,以告诉pathwalk忽略d_automount或任何安装。

仅当在要从其传输的登齿上设置了DCACHE_MANAGE_TRANSIT时,才使用此功能。

d_real

覆盖/联合类型文件系统实现此方法,以返回由覆盖隐藏的基础牙科之一。它以两种不同的模式使用:

从file_dentry()调用,它返回与inode参数匹配的真实dentry。真正的牙科设备可能来自已经复制但仍从文件引用的较低层。使用非NULL inode参数选择此模式。

使用NULL索引节点,将返回最顶层的实际基础Dentry。

每个牙科都有指向其父牙科的指针,以及子牙科的哈希列表。子牙科基本上就像目录中的文件。

目录条目缓存API

定义了许多允许文件系统操作牙科的功能:

dget

为现有的Dentry打开新的句柄(这只会增加使用计数)

dput

关闭Dentry的句柄(减少使用计数)。如果使用计数降至0,并且dentry仍在其父级的哈希中,则调用“ d_delete”方法以检查是否应对其进行缓存。如果不应该对其进行缓存,或者不对Dentry进行哈希处理,则将其删除。否则,将缓存的内存放入LRU列表中,以在内存不足时进行回收。

d_drop

这从其父哈希列表中消除了牙科。如果dput()的使用计数降至0,则随后的调用将取消分配dentry

d_delete

删除一个牙科。如果没有其他打开的对该Dentry的引用,则将dentry变成一个否定的dentry(调用d_iput()方法)。如果还有其他引用,则调用d_drop()

d_add

向其父哈希列表添加一个dentry,然后调用 d_instantiate()

d_instantiate

向inode的别名哈希列表中添加一个dentry,并更新“ d_inode”成员。应设置/递增inode结构中的“ i_count”成员。如果inode指针为NULL,则该dentry称为“负性dentry”。当为现有的负面牙科创建索引节点时,通常会调用此函数

d_lookup

在给定其父级和路径名组件的情况下查找dentry。它从dcache哈希表中查找该给定名称的子级。如果找到,则参考计数增加,并返回牙科。调用方完成使用后,必须使用dput()释放该dentry。

挂载选项

解析选项

在挂载和重新挂载时,文件系统会传递一个字符串,其中包含一个用逗号分隔的挂载选项列表。选项可以具有以下两种形式之一:

选项选项=值

<linux / parser.h>标头定义了一个有助于解析这些选项的API。关于如何在现有文件系统中使用它的例子很多。

显示选项

如果文件系统接受安装选项,则它必须定义show_options()来显示所有当前活动的选项。规则是:

  • 必须显示非默认选项,或者其值与默认值不同
  • 可能会显示默认启用或具有默认值的选项

仅在安装助手和内核之间内部使用的选项(例如文件描述符),或仅在安装过程中起作用的选项(例如控制日记创建的选项)不受上述规则的限制。

上述规则的根本原因是要确保可以基于/ proc / mounts中的信息准确地复制安装(例如,重新安装和重新安装)。

资源资源

(请注意,其中一些资源不是最新的内核

版。)

创建Linux虚拟文件系统。2002年

< http://lwn/Articles/13325/ >

Neil Brown撰写的Linux虚拟文件系统层。1999年

< http://www.cse.unsw.edu.au/~neilb/oss/linux-commentary/vfs.html >

Michael K. Johnson浏览Linux VFS。1996年

< http://www.tldp/LDP/khg/HyperNews/get/fs/vfstour.html >

Andries Brouwer撰写的关于Linux内核的一小段内容。2001

< http://www.win.tue.nl/~aeb/linux/vfs/trail.html >

本文标签: 文件系统Linux