Linux VFS

翻译自Linux文档中的vfs.txt

介绍

VFS(Virtual File System)是内核提供的文件系统抽象层,其提供了文件系统的操作接口,可以隐藏底层不同文件系统的实现。

Directiry Entry Cache(dcache)

VFS通过open()stat()这些接口传入的文件名搜索dcache,快速找到文件名对应的dentry。dentry的结构是struct dentry,这只是一个内存结构,不会持久化到磁盘中,仅仅是为了提升性能而已。

dentry cache是整个文件空间的视图,但是大部分情况下并不能同时将所有dentry都加载到内存。VFS会在执行lookup()时,创建还不在内存中的dentry。

The Inode Object

inode代表真实存储在文件系统中的对象,比如文件、目录、FIFO等等。inode的结构是struct inode。一个dentry只能指向一个inode,但一个inode可以被多个dentry指向。

对于块设备的文件系统,inode存储在磁盘上,并在需要的时候拷贝到内存中,修改inode后又会写回磁盘进行持久化。对于伪文件系统,inode保存在内存中。

VFS调用lookup()从指定路径的第一级目录的dentry开始查找对应的inode。lookup()的真实实现是由inode所在的底层文件系统提供的。

The File Object

打开一个文件实际就是创建一个file对象。file的结构是struct file,其有指向一个dentry的指针,和一组操作file的函数指针。这些信息是从inode中获取的。在打开文件的最后,file会被加入当前进程的文件描述符表中。

用户的读、写、关闭文件都通过fd进行,内核会可以根据fd获取到对应的file对象,这些操作最终都会调用到底层文件系统提供的函数。

注册和挂载文件系统

注册和注销文件系统的函数如下

#include <linux/fs.h>

extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);

当挂载文件系统时,VFS会调用底层文件系统指定的mount()函数,mount()会返回一个dentry作为文件系统的根目录。所有已挂载的文件系统,可以在/proc/filesystems中看到。

struct file_system_type

该结构用于描述文件系统,自内核2.6.30,有如下定义

struct file_system_type {
    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"等。
  • fs_flags:比如FS_REQUIRES_DEV、FS_NO_DCACHE等一些标志。
  • mount:挂载文件系统时调用。
  • kill_sb:卸载文件系统时调用。
  • owner:在大部分情况下,应当初始化为THIS_MODULE。
  • next:应当初始化为NULL。
  • s_lock_key,s_umount_key:用于检查死锁。

mount()的参数如下

  • struct file_system_type* fs_type:描述文件系统,其中部分底层文件系统初始化。
  • int flags:挂载的标志,如FS_REQUIRES_DEV,FS_NO_DCACHE等。
  • const char *dev_name:挂载的设备名。
  • void *data:任意的选项,通常是ASCII字符串。

mount()成功时,要求加锁并获得superblock的活动引用,返回dentry(可以是子目录的dentry),失败时返回ERR_PTR(error)

grab_super()是获取活动引用的函数,即让spuer_block::a_active加1

mount()会创建一个superblock,其结构是struct superblockstruct superblock中有一个指向struct super_operations的指针s_op,其由一系列操作文件系统的函数指针组成。

VFS提供了如下方法挂载不同类型的文件系统

  • mount_bdev():挂载基于块设备的文件系统。
  • mount_nodev():挂载不基于设备的文件系统。
  • mount_single():挂载共享实例的文件系统。

这些函数都有一个入参int (*fill_super)(struct super_block *, void *, int),其用于初始化struct superblock的部分字段。

fill_super的参数如下

  • struct super_block *sb:指向superblock。
  • void *data:mount的参数,通常是一个ASCII字符串,需要自行解析。
  • int silent:确定是否输出错误信息。

The Superblock Object

一个superblock代表了一个已挂载的文件系统。

struct super_operations

VFS通过struct super_operations操作superblock

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);
};

除非有额外的说明,否则VFS不会加锁调用这些函数。这意味着这些函数阻塞时,不会影响到其他线程。所有的函数只会在进程上下文调用,不会在中断上下文中调用。

  • alloc_inode:用于分配和初始化inode。如果未指定该方法,则使用纯粹的struct inode。通常情况下,文件系统会自定义自己的inode结构,除了包含struct inode外,还会包含和底层文件系统相关的字段。
  • destroy_inode:销毁inode,必须和alloc_inode一起实现。
  • dirty_inode:将inode标记为脏inode
  • write_inode:将inode写回到磁盘。该函数第二个参数用于指定是否同步写入,并非所有文件系统都检查这个标志
  • drop_inode:用于判断是否从内存中移除inode。当inode引用计数为0时调用该函数,调用前会锁定inode->i_lock。drop_inode应该是NULL(正常UNIX文件系统语义)或者是generic_delete_inode()(不考虑引用计数,强制清除inode,适用于不想缓存inode的文件系统)。
  • delete_inode:从内存中移除inode。v2.6.39的struct super_operations就没有这个字段,取而代之的是evict_inode
  • put_super:释放superblock调用前锁定superblock lock
  • sync_fs:将superblock关联的脏数据写回到存储。第二个参数用于指明是否等待数据写完后再返回。
  • freeze_fs:锁定文件系统并迫使它进入一致状态。目前被逻辑卷管理(LVM)会用到该函数。
  • unfreeze_fs:解锁文件系统,使其可以重新写入。
  • statfs:获取文件系统统计信息。
  • remount_fs:重新挂载文件系统,主要用于更新挂载参数。调用前锁定**kernel lock
  • clear_inode:标记不再使用该inode。v2.6.39的struct super_operations就没有这个字段
  • umount_begin:卸载文件系统
  • show_options:用于在/proc/ /mounts里输出挂载选项
  • quota_read:读quota file
  • quota_write:写quota file
  • nr_cached_objects:返回可释放的对象个数。
  • free_cache_objects:清理对象。需要和nr_cached_objects一起定义

如果这些函数内会执行批量任务,那么必须包含支持重新调度的函数,这使得VFS无需担心这些函数长时间处理的问题。

设置inode时必须初始化struct inodei_op字段,其指向struct inode_operations,该结构包含了一系列操作inode的函数。

struct xattr_handler

当文件系统需要支持扩展属性时,可以指定superblocks_xattr字段,其指向一个一NULL结尾的struct xattr_handler数组。扩展属性是一个name-value对。

struct xattr_handler {
    const char *name;
    const char *prefix;
    int flags;      /* fs private flags */
    bool (*list)(struct dentry *dentry);
    int (*get)(const struct xattr_handler *, struct dentry *dentry,
           struct inode *inode, const char *name, void *buffer,
           size_t size);
    int (*set)(const struct xattr_handler *, struct dentry *dentry,
           struct inode *inode, const char *name, const void *buffer,
           size_t size, int flags);
};
  • name:该结构中的函数用于处理该名字代表的属性,比如"system.posix_acl_access"。指定name,则prefix必须是NULL
  • prefix:该结构中的函数用于处理该前缀代表的属性,比如"user."。指定了prefix,则name必须是NULL
  • list:确定是否应为特定的dentry列出与此处理函数匹配的属性。
  • get:获取扩展属性的值。该方法在getxattr(2)流程中调用
  • set:设置扩展属性的值。如果新值为NULL,则移除扩展属性。该函数在setxattr(2)removexattr(2)流程中调用

当文件系统没有xattr的处理方法或者没有匹配的属性时,会返回-EOPNOTSUPP

The Inode Object

一个inode代表了一个文件系统对象

struct inode_operations

struct inode_operations描述了VFS如何操作inode

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:创建inode,由open(2)creat(2)调用。只有当需要支持常规文件时,才实现该函数。入参dentry必须没有指向任何inode的,最后需要调用d_instantiate()将新创建的inode加入到这个dentry中。
  • lookup:查找inode。
    • 需要查找的inode的名字记录在dentry中。如果找到了则需要调用d_add()将inode加入到dentry中,并且inode的引用计数要加1。如果inode不存在,则需要将NULL加入到dentry,代表dentry是无效的。
    • 当错误发生时,必须返回错误。如果只是将dentry指向NULL,VFS会执行create(2), mknod(2), mkdir(2)去创建inode,但这些函数还是会因为lookup失败的原因而再次失败。
    • 如果需要重新设置dentry的操作函数,可以给d_dop赋值。这些函数被调用时,会使用目录inode的信号量。
  • link:创建硬链接。和create一样,该函数中需要调用d_instantiate()
  • unlink:支持删除inode。
  • symlink:创建符号链接。
  • mkdir:创建目录。该函数中也需要调用d_instantiate()
  • rmdir:删除目录。
  • mknod:创建设备inode、命名管道或socket。该函数中也需要调用d_instantiate()
  • rename:重命名。当文件系统不支持某些方法和flag时,必须返回-EINVAL。当前以下flag已经实现
    • RENAME_NOREPLACE:如果新文件名已经存在,则应当返回-EEXIST。但是目前VFS已经检查了新文件名是否存在。
    • RENAME_EXCHANGE:两个文件必须都存在,只是交换一下文件名。
  • get_link:获取符号链接所指向的inode。
  • readlink:读取符号链接所指向的inode的文件名。正常情况下,文件系统只需要实现get_link。
  • permission:检查类POSIX文件系统的访问权限。可能会在rcu-walk模式下调用。如果在rcu-walk模式下,文件系统必须在不阻塞或者不存储inode的情况下检查权限。如果在rcu-walk模式下,发生一个无法处理的情况,则返回-ECHILD,此后会在ref-walk模式下再尝试一次。
  • setattr:设置文件属性,由chmod(2)和相关的系统调用触发。
  • getattr:获取文件属性,由stat(2)和相关的系统调用触发。
  • listxattr:由VFS调用列出一个文件的所有扩展属性,由listxattr(2)触发。
  • update_time:更新inode的时间(atime、ctime、mtime)或i_version。如果该方法未定义,那么VFS会自行更新inode,然后调用mark_inode_dirty_sync()标记该inode为脏inode
  • atomic_open:在open操作的最后调用。使用这个可选的方法,文件系统可以原子性地查找、创建、打开一个文件。如果该方法想让调用者去打开文件,则应当通过finish_no_open()通知调用者。该方法只在最后一步是无效或者需要lookup时才调用。缓存有效的dentry需要在f_op->open()中完成。如果文件创建成功了,则需要在file->f_mode设置FMODE_CREATED。如果指定了O_EXCL,该方法需要在文件存在时返回失败。返回成功就设置FMODE_CREATED
  • tmpfile:在open(O_TMPFILE)的最后被调用。这是一个可选的方法,等同于在一个指定的目录下,原子性地创建、打开、删除一个文件。

The Address Space Object

address space对象用于组织和管理page cache中的page。它可以追踪一个文件使用的page以及一个文件被映射到进程空间的page。它可以根据地址查找page,追踪被标记为Dirty或Writeback的page。

VM可以调用writepage()来清理脏页,或者设置PagePrivate后调用releasepage()来释放干净的页以便重新使用。如果干净的页没有设置PagePrivate或者没有外部引用,就会在没有通知address space的情况下被释放掉。为了实现这些功能,page需要通过lru_cache_add()方法放到LRU中,当page被使用时需要调用mark_page_active()

page使用一个基数树(radix tree)来保存。这棵树维护了所有page的PG_DiryPG_Writeback状态,因此这些页能被快速找到。

mpage_writepages()是默认的writepages,它使用Dirty标记查找脏页并写回。如果没有使用mpage_writepages()(可能是address space提供了自己的writepage),那么PAGECACHE_TAG_DIRTY标记就不会被使用。write_inode_now()sync_inode()使用PAGECACHE_TAG_DIRTY来检查writepages是否将整个address space写回到存储中。

filemap*wait*sync_page*会使用Writeback标记,并通过filemap_fdatawait_range()等待所有写回操作完成。

address space的处理方法可以通过page::private附加一些额外的信息到page中。如果附加了信息,那么必须设置PG_Private。VM会调用address space的额外函数来处理这些数据。

address space作为存储和应用之间的中间层。每次从存储读取一页的数据到address space中,供应用读取。应用可以将任意大小的数据写入address space,但最后以页为单位写入到存储中。

读进程只需要readpage。写进程则复杂一点,需要write_begin/write_end将数据写入到address space,还需要writepagewritepages将数据写到存储中。

address space增删页受inode::i_mutex的保护。

当数据被写入到page中时,需要设置PG_Dirtyset_page_dirty())。当需要writepages时会设置PG_Writeback并清除PG_Dirty。标记为PG_Writeback的page可以在任意时间写回到存储,一旦写回完成后,该标志被清除。

写回会使用struct writeback_controlwritepagewritepages提供写回的请求和限制信息,该结构也用于返回写回的结果。

处理写回的错误

大部分使用缓存IO的应用都会周期性地调用同步接口(如fsync()fdatasync()msync()sync_file_rage())来保证数据被写回到存储中。如果写回时发生错误,这些接口应当返回错误,但仅在第一次返回错误,后续应当返回0,除非又有新的数据写入且再次调用同步接口时又发生写回错误。

理想情况下,内核应当以文件为单位,返回其写回的错误。但实际上page cache并没有以文件为单位追踪脏页,因此当发生写回错误时,内核无法知道是哪个文件发生的写回错误。当前当发生写回错误时,内核给所有打开的文件都返回错误,即使这个文件并没有写入或者已经写回成功了。

想使用该框架的文件系统应当调用mapping_set_error()来记录错误。在写回数据后,文件系统还应当调用file_check_and_advance_wb_err()来确保struct file::f_wb_err记录的是正确的写回错误。

struct address_space_operations

VFS使用该结构操作文件的page cache。

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);
    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:基于数据完整性(sync)或者释放内存(flush)的原因,VM会调用该方法将脏页写回到存储。写回过程:清除PG_Dirty,设置PageLockedtrue,然后writepage开始写回,并设置PG_Writeback,然后在完成写回后解锁page。
    • 如果wbc->sync_modeWB_SYNC_NONE,那么writepage在无法完成写回指定页的情况下,返回AOP_WRITEPAGE_ACTIVATE,这样VM就不会一直为该页调用writepage了。
  • readpage:VM调用该方法从存储中读取一页的数据。该页会被锁定,但需要在读取完成后,标记为uptodate并解锁。如果readpage需要解锁,也可以解锁,并返回AOP_TRUNCATED_PAGE。在这种情况下,VM会重新安置和加锁该页,再次调用readpage
  • writepages:VM调用该方法将address space相关联的页都写回到存储。如果如果wbc->sync_modeWBC_SYNC_ALL,那么writeback_control就会指定一组必须写回的页。如果是WBC_SYNC_NONE的话,则只要求尽可能写入nr_to_write页。如果该方法未定义,那么会用mpage_writepages来替代。该方法会从address space中获取所有标记为DIRTY的页,然后传递给writepage
  • set_page_dirty:VM调用该方法设置脏页。只有在address space中有私有数据的page且在页变脏时需要更新这些私有数据时,才需要该方法。如果定义了该方法,那么它必须设置PageDirty标志和基数树中的PAGECACHE_TAG_DIRTY标志。
  • readpages:VM调用该方法读取address space相关联的页。这个方法本质上是多次调用readpage。readpages只用于预读,因此读取错误被忽略了。
  • write_begin:由generic_perform_write()调用,用于告知文件系统准备在指定偏移处写len字节的数据。address space应预留一些资源保证完成这次写入操作。如果写入要更新页中的一部分,那么需要将整页块读取到内存中,即读改写。文件系统通过pagep返回page cache中已锁定的page。调用者会将数据写入到该page中。需要支持实际写入的数据长度小于传给write_begin的长度的场景。可通过fsdata保存需要传递给write_end的数据。
  • write_end:该函数必须在成功调用write_begin和拷贝数据后被调用。len是传递给write_begin的len,copied则是实际拷贝的长度。文件系统必须释放page的锁和引用,并更新struct inode::i_size。失败返回小于0,否则返回实际拷贝到page cache的长度。
  • bmap:VFS调用该方法将逻辑块偏移映射为物理块序号。该方法用于ioctl(FIBMAP)和swap文件。为了swap一个文件,文件必须固定地映射到一个块设备上。swap系统会通过bmap来找到文件所在的块,然后直接存储,而不通过文件系统。
  • invalidatepage:如果page设置了PagePrivate,那么当部分或全部的page从address space中被移除时,就会调用invalidatepage。
  • releasepage:释放标记为PagePrivate的page,该方法需要移除page的私有数据,并清除PagePrivate。如果该方法失败了则返回0。releasepage用于两种场景
    • VM发现一个没有活动用户的干净page,并向释放该page。如果释放成功,那么该页就会从address space中移除,变为自由的page。
    • 需要让address space中的部分或全部page失效。这种场景由fadvise(POSIX_FADV_DONTNEED)触发,或者文件系统明确请求(比如当nfs和9fs认为cache的数据已经与存储不一致时,会调用invalidate_inode_pages2()
  • freepage:一旦page在page cache中不可见时,为了允许清除私有数据,就会调用该方法。因为该方法可能被内存回收者调用,所以该方法不能假设原始的address space还存在,也不应当阻塞。
  • direct_IO:由generic read/write类函数调用来执行direct IO。
  • isolate_page:VM在需要隔离一个可移动的非LRU的page时调用。如果成功了,VM会通过__SetPageIsolated将该页标记为PG_isolated
  • migrate_page:该方法用于压缩物理内存使用量。如果VM想重新放置page(可能是内存卡的故障信号触发的),就会传递一个老page和一个新page给这个方法。该方法需要传输私有数据,并更新所有引用。
  • putback_page:当已隔离的页迁移失败时,VM会调用该方法。
  • launder_page:在释放页前调用。该方法将脏页写回存储并避免重新弄脏该页,在整个过程中,都会加锁。
  • is_partially_uptodate:块大小不等于页大小,一页可能包含多个块。如果VM读取到所需的块数据,那么就无需等待整个页读取完毕。
  • is_dirty_writeback:当VM想回收page时调用。VM会根据dirty和writeback的值决定是否需要停顿回收页,以便能完成某些IO。通常情况下,VM可以使用PageDirtyPageWriteback,但是某些文件系统会有更复杂的状态(比如NFS的unstable pages需要避免被回收),或者因为锁的问题没有设置这些标志。该方法可以向VM表明该页是脏页或正在写回的页,让VM停止回收该页。
  • error_remove_page:如果truncation是正常的话,通常设置为generic_error_remove_page()。主要用于处理内存失败。实现该方法,意味着你会处理那些页,除非你已经锁定或者增加了引用计数。
  • swap_activate:当文件使用了swapon时调用,用于分配空间并将块的查询信息保存在内存中。返回0代表成功,也意味着该文件可以被当做备份的交换空间。
  • swap_deactivate:当swap_activate成功后,调用该方法使得该文件变为swapoff。

The File Object

一个file对象代表进程打开的一个文件。

struct file_operations

VFS使用struct file_operations操作一个打开的文件。

自v4.18,struct file_operations定义如下

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 (*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);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64);
    int (*fadvise)(struct file *, loff_t, loff_t, int);
};

除非额外说明,否则这些函数都不会持锁调用。

  • read_iter:支持将文件数据读取到非连续的内存中
  • write_iter:支持将非连续内存中的数据写入到文件中。
  • iterate:读取目录内容
  • iterate_shared:当文件系统支持并发的目录迭代时,使用该函数读取目录内容
  • compat_ioctl:在64位内核上兼容32位的系统调用
  • open:创建一个新的struct file,并初始化strutc file::private_data
  • flush:由close(2)调用
  • release:当文件的引用计数为0时调用
  • fasync:文件为非阻塞模式时,由fcntl(2)调用
  • lock:由fcntl(2)调用,执行F_GETLKF_SETLKF_SETLKW命令
  • fallocate:预分配block

Directory Entry Cache (dcache)

dentry属于VFS和单个文件系统,与设备驱动无关。每个dentry都有一个指向其父dentry的指针,以及一个子dentry的hash链表。

struct dentry_operations

struct dentry_operations是可选的,可以设置为NULL,或是使用VFS默认的函数。

v2.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:当在cache中找到dentry时,判断dentry是否有效,返回正数代表还有效,返回0或负数代表无效。大多数本地文件系统将其设为NULL,因为它们的dentry总是有效的。网络文件系统则不同,因为服务端的变更,客户端可能感知不到。
  • d_weak_revalidate
  • d_hash:计算hash值,根据hash值加入父dentry的hash表中
  • d_compare:比较dentry的名字,必须是常熟且幂等
  • d_delete:判断是否删除dentry。返回1表示立即删除,返回0代表缓存。d_delete必须是常熟且幂等
  • d_init:当分配dentry后调用,初始化dentry
  • d_release:释放dentry
  • d_iput:归还inode引用(在d_release前调用)。如果是NULL,VFS会调用iput(),否则需要自行调用iput()
  • d_dname:当需要生成dentry的路径名时调用。对于伪文件系统(sockfs,pipefs)延迟生成路径名来说很有用。因为没有加锁,所以d_dname不能修改dentry本身

Directory Entry Cache API

以下是操作dentry的函数

  • dget():获取一个已存在的dentry的引用
  • dput():归还一个dentry的引用。如果引用计数为0,且该dentry还在其父dentry的hash表中,则调用d_delete()检查该dentry是否还应该缓存。如果需要缓存,则放入LRU链表中。
  • d_drop():从父dentry的hash表中删除dentry
  • d_delete():删除一个dentry,如果没有其他引用,则该dentry变为一个无效的dentry(即指向NULL inode),d_iput()就会被调用。如果还有引用,则调用d_drop()
  • d_add():将dentry加入到其父dentry的hash表中,然后调用d_instantiate()
  • d_instantiate():将dentry加入到inode的dentry链表中,并更新dentry指向的inode(即struct dentry::d_inode)。inode的引用计数也会增加。
  • d_lookup():查找dentry,如果找到了则增加引用计数后返回。

猜你喜欢

转载自www.cnblogs.com/yizui/p/10890939.html