翻译自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,如果找到了则增加引用计数后返回。

Linux VFS的更多相关文章

  1. Linux VFS机制简析(二)

    Linux VFS机制简析(二) 接上一篇Linux VFS机制简析(一),本篇继续介绍有关Address space和address operations.file和file operations. ...

  2. Linux VFS机制简析(一)

    Linux VFS机制简析(一) 本文主要基于Linux内核文档,简单分析Linux VFS机制,以期对编写新的内核文件系统(通常是给分布式文件系统编写内核客户端)的场景有所帮助. 个人渊源 切入正文 ...

  3. Linux VFS Extended Attribute And Access Control Table

    catalog . 简介 . 扩展属性 . 访问控制表 . 小结 0. 简介 许多文件系统都提供了一些特性,扩展了VFS层提供的标准功能,虚拟文件系统不可能为所有特性都提供具体的数据结构.超出标准的U ...

  4. linux VFS 内核数据结构

    <strong>简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp.</strong> 文件描述 ...

  5. Linux VFS数据结构

    先说明一下,linux内核中各种数据结构也不停的在变,所以不同版本的内核各个数据结构的定义可能会差别很大,这一组关于linux 文件系统的文章中的代码都摘自linux-2.6.34.1. VFS依赖于 ...

  6. Linux VFS的主要的数据结构

    先说明一下,linux内核中各种数据结构也不停的在变,所以不同版本的内核各个数据结构的定义可能会差别很大,这一组关于linux 文件系统的文章中的代码都摘自linux-2.6.34.1. VFS依赖于 ...

  7. Linux VFS分析(二)

    inode的管理:Inode-cache hash表inode_hashtable索引节点缓存 dentry的管理: 我们知道,若干dentry描绘了一个树型的目录结构,这就是用户所看到的目录结构,每 ...

  8. linux VFS 之一 :虚拟文件系统的面向对象设计思想

    VFS的面向对象的思想,如下图: VFS在上层用户空间的进程与底层特定文件系统之间起到一个承上启下的作用, 对上:封装标准的系统调用接口给用户空间app,user space app不必关心特定文件系 ...

  9. Linux VFS中write系统调用实现原理【转】

    转自:http://blog.chinaunix.net/uid-28362602-id-3425881.html 目录 用户空间的write函数在内核里面的服务例程为sys_write Vfs_wr ...

随机推荐

  1. Python基础——赋值机制

    使用id()函数用于获取对象的内存地址. 使用is来判断是不是指向同一个内存. 把一个对象赋值给另一个对象,两个对象都指向同一个内存地址. test=1000 test1=test id(test) ...

  2. Git命令大总结(纯手办)

    Git完整命令手册地址:http://git-scm.com/docs PDF版命令手册地址:github-git-cheat-sheet.pdf 1.git config -l查看全局用户信息配置 ...

  3. fortran子程序传入可变数组要在module里实现

    坑死我了,我说怎么子程序传递不了可变数组 在写fortran程序的时候,要对矩阵实现特定的功能,如高斯法解线性方程组,很多时候子程序不知道矩阵的大小,如有限元程序中先要用程序得到总体刚度矩阵再把总刚传 ...

  4. luogu2774 方格取数问题

    最大点权独立集,参见胡伯涛论文 #include <iostream> #include <cstring> #include <cstdio> #include ...

  5. [POJ 1000] A+B Problem 经典水题 C++解题报告 JAVA解题报告

        A+B Problem Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 311263   Accepted: 1713 ...

  6. [Cake] 2. dotnet 全局工具 cake

    在上篇博客[Cake] 1. CI中的Cake中介绍了如何在CI中利用Cake来保持与CI/CD环境的解耦. 1. 简化cake的安装 当时dotnet 2.1还未正式发布,dotnet 还没有工具的 ...

  7. Selenium WebDriver- 操作frame中的页面元素

    #encoding=utf-8 import unittest import time from selenium import webdriver from selenium.webdriver i ...

  8. 分区脚本(fdisk)

    #!/bin/bash echo "np w" | fdisk /dev/sdc && mkfs -t /dev/sdc1

  9. 【bzoj4568】[Scoi2016]幸运数字 树上倍增+高斯消元动态维护线性基

    题目描述 A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征.一些旅行者希望游 ...

  10. BZOJ3098 Hash Killer II 【概率】

    挺有意思的一题 就是卡一个\(hash\) 我们先取L大概几十保证结果会超出\(10^9 + 7\) 然后就随机输出\(10^5\)个字符 由题目的提示我们可以想到,如果我们有\(n\)个数,选\(k ...