VFS的任务并不简单。一方面,它用来提供了一种操作文件、目录及其他对象的统一方法。另一方面,它必须能够与各种方法给出的具体文件系统的实现达成妥协,这些实现在具体细节、总体设计方面都有一些不同之处。

文件系统类型

  • 基于磁盘的文件系统
  • 虚拟文件系统
  • 网络文件系统

通用文件模型

在处理文件时,内核空间和用户空间使用的主要对象是不同的。对用户程序来说,一个文件由一个文件描述符标识。内核处理文件的关键是inode。

inode

目录只是一个特殊的文件。

inode的成员可能分为下面两类。

  • 描述文件状态的元数据。
  • 保存实际文件内容的数据段。

为阐明如何用inodes来构造文件系统的目录层次结构,我们来考察内核查找对应于/usr/bin/emacs的inode过程。

查找起始于inode,它表示根目录/,对系统来说必须总是已知的。该目录由一个inode表示,其数据段并不包含普通数据,而是根目录下的各个目录项。这些目录项可能代表文件或其他目录。每个项由两个成员组成。

  • 该目录项的数据所在inode的编号
  • 文件或目录的名称

链接

对每个符号链接都使用了一个独立的inode。相应inode的数据段包含一个字符串,给出了链接目标的路径。

对于硬链接建立时,创建的目录项使用了给定文件的inode编号。

VFS结构

结构概观

VFS由两个部分组成,文件和文件系统,这些都需要管理和抽象。

文件表示

在抽象对底层文件系统的访问时,并未使用固定的函数,而是使用了函数指针。这些函数指针保存在两个结构中,包括了所有相关的函数。

  • inode操作:创建链接、文件重命名、在目录中生成新文件、删除文件。
  • 文件操作:作用于文件的数据内容。它们包含一些显然的操作(如读和写),还包括如设置文件指针和创建内存映射之类的操作。

每个inode还包含了一个指向底层文件系统的超级块对象的指针,用于执行inode本身的操作。

文件系统和超级块信息

超级块还包含了读、写、操作inode的函数指针。也包含了文件系统的关键信息(块长度、最大文件长度,等等)。

超级块结构的一个重要成员是一个列表,包括相关文件系统中所有修改过的inode(内核相当不敬地称之为脏inode???miao miao miao?)。根据该列表很容易标识已经修改过的文件和目录,以便将其写回到存储介质。

inode

<fs.h>

struct inode {
    struct hlist_node   i_hash;
    struct list_head    i_list;
    struct list_head    i_sb_list;
    struct list_head    i_dentry;
    unsigned long       i_ino;//inode编号标识
    atomic_t        i_count;//访问该inode结构的进程数目
    unsigned int        i_nlink;//硬链接总数
    uid_t           i_uid;
    gid_t           i_gid;
    dev_t           i_rdev;//在inode表示设备文件时,使用
    unsigned long       i_version;
    loff_t          i_size;//文件长度,字节计算
#ifdef __NEED_I_SIZE_ORDERED
    seqcount_t      i_size_seqcount;
#endif
    struct timespec     i_atime;//最后访问的时间,
    struct timespec     i_mtime;//最后修改的时间(数据内容)
    struct timespec     i_ctime;//最后修改inode的时间(inode内容)
    unsigned int        i_blkbits;
    blkcnt_t        i_blocks;//文件按块计算的长度
    unsigned short          i_bytes;
    umode_t         i_mode;//文件访问权限
    spinlock_t      i_lock; /* i_blocks, i_bytes, maybe i_size */
    struct mutex        i_mutex;
    struct rw_semaphore i_alloc_sem;
    const struct inode_operations   *i_op;
    const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
    struct super_block  *i_sb;
    struct file_lock    *i_flock;
    struct address_space    *i_mapping;
    struct address_space    i_data;
#ifdef CONFIG_QUOTA
    struct dquot        *i_dquot[MAXQUOTAS];
#endif
    struct list_head    i_devices;
    union {
        struct pipe_inode_info  *i_pipe;
        struct block_device *i_bdev;
        struct cdev     *i_cdev;
    };
    int         i_cindex;

    __u32           i_generation;

#ifdef CONFIG_DNOTIFY
    unsigned long       i_dnotify_mask; /* Directory notify events */
    struct dnotify_struct   *i_dnotify; /* for directory notifications */
#endif

#ifdef CONFIG_INOTIFY
    struct list_head    inotify_watches; /* watches on this inode */
    struct mutex        inotify_mutex;  /* protects the watches list */
#endif

    unsigned long       i_state;
    unsigned long       dirtied_when;   /* jiffies of first dirtying */

    unsigned int        i_flags;

    atomic_t        i_writecount;
#ifdef CONFIG_SECURITY
    void            *i_security;
#endif
    void            *i_private; /* fs or device private pointer */
};

inode操作

file_operations用于操作文件中包含的数据,而inode_operations负责管理结构性的操作(例如删除一个文件)和文件相关的元数据(例如,属性)。

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);//根据文件系统对象的名称。查找其inode实例
    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 *,int);
    int (*rmdir) (struct inode *,struct dentry *);
    int (*mknod) (struct inode *,struct dentry *,int,dev_t);
    int (*rename) (struct inode *, struct dentry *,
            struct inode *, struct dentry *);
    int (*readlink) (struct dentry *, char __user *,int);
    void * (*follow_link) (struct dentry *, struct nameidata *);//根据符号链接查找目录的inode
    void (*put_link) (struct dentry *, struct nameidata *, void *);
    void (*truncate) (struct inode *);
    int (*permission) (struct inode *, int, struct nameidata *);
    int (*setattr) (struct dentry *, struct iattr *);//xattr函数,用于建立、读取、删除文件的扩展属性
    int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
    int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
    ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
    ssize_t (*listxattr) (struct dentry *, char *, size_t);
    int (*removexattr) (struct dentry *, const char *);
    void (*truncate_range)(struct inode *, loff_t, loff_t);//截断一个范围内的块
    long (*fallocate)(struct inode *inode, int mode, loff_t offset,
              loff_t len);//用于对文件预先分配空间
};

inode链表

每个inode都有一个i_list成员,可以将inode存储在一个链表中。根据inode的状态,它可能有3种主要的情况。

  • inode处于内存中,未关联到任何文件,也不处于活动使用状态
  • inode结构在内存中,正在由一个或多个进程使用,通常表示一个文件。两个计数器(i_count和i_nlink)的值都必须大于0。文件内容和inode元数据都与底层块设备上的信息相同。也就是说,与上一次与存储介质同步以来,该inode没有改变过
  • inode处于活动使用状态。其数据内容已经改变,与存储介质上的内容不同。这种状态的inode被称作脏的。

在fs/inode.c中内核定义了两个全局变量用作表头,inode_unsued用于有效但非活动的inode。inode_in_use用于所有使用但未改变的inode。脏的inode保存在一个特定于超级块的链表中(super_block->s_dirty)。

每个inode不仅出现在特定于状态的链表中,还在一个散列表中出现(inode_hashtable,也定义在fs/inode.c中),以根据inode编号和超级快快速访问inode。

inode还通过一个特定于超级块的链表维护,表头是super_block->s_inodes。i_sb_list用作链表元素。

表头为super_block->s_io和super_block->s_more_io使用同样的链表元素i_list。这两个链表包含的是已经选中向磁盘回写的inode,但正在等待回写进行。

特定于进程的信息

<sched.h>
struct task_struct {
...
    /* 文件系统信息 */
    int link_count, total_link_count;
...
    /* 文件系统信息 */
    struct fs_struct *fs;
    /* 打开文件信息 */
    struct files_struct *files;
    /* 命名空间 */
    struct nsproxy *nsproxy;
...
} 
<file.h>

struct files_struct {
  /*
   * read mostly part
   */
    atomic_t count;

    //fdtable在打开超过NR_OPEN_DEFAULT个文件时,使用
    struct fdtable *fdt;
    struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    int next_fd;//表示下一次打开新文件时使用的文件描述符
    struct embedded_fd_set close_on_exec_init;//位图,对执行exec时将关闭的所有文件描述符,都置位。
    struct embedded_fd_set open_fds_init;//最初文件描述符集合
    struct file * fd_array[NR_OPEN_DEFAULT];//指向每个打开文件的struct file实例(默认情况下,内核允许每个进程打开NR_OPEN_DEFAULT个文件)
};

如果进程试图打开更多的文件(大于NR_OPEN_DEFAULT),则内核需要分配更多的内核空间。fdtable用于该目的。

struct fdtable {
    unsigned int max_fds;//max_fds指定了进程当前可以处理的文件对象和文件描述符的大数目
    struct file ** fd;      /* current fd array */
    fd_set *close_on_exec;//是一个指向位域的指针,该位域保存了所有在exec系统调用时将要关闭的文件描述符的信息
    fd_set *open_fds;//是一个指向位域的指针,该位域管理着当前所有打开文件的描述符
    struct rcu_head rcu;
    struct fdtable *next;
};

开始,fdt指向fdtab,fdtab中的成员fd、open_fds和 close_on_exec都初始化为指向前者对应的3个成员。

struct file。该结构保存了内核所看到的文件的特征信息。

<fs.h>

struct file {
    /*
     * fu_list becomes invalid after file_free is called and queued via
     * fu_rcuhead for RCU freeing
     */
    union {
        struct list_head    fu_list;
        struct rcu_head     fu_rcuhead;
    } f_u;
    struct path     f_path;//封装了两部分信息:1.文件名和inode之间的关联;2.文件所在文件系统的有关信息。
#define f_dentry    f_path.dentry
#define f_vfsmnt    f_path.mnt
    const struct file_operations    *f_op;//文件操作函数
    atomic_t        f_count;
    unsigned int        f_flags;
    mode_t          f_mode;//文件操作模式
    loff_t          f_pos;//文件指针
    struct fown_struct  f_owner;//包含了处理该文件的进程有关的信息
    unsigned int        f_uid, f_gid;
    struct file_ra_state    f_ra;//预读

    u64         f_version;
#ifdef CONFIG_SECURITY
    void            *f_security;
#endif
    /* needed for tty driver, and maybe others */
    void            *private_data;

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
    spinlock_t      f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;//指向属于文件相关的inode实例的地址空间映射
};

每个超级块都提供了一个s_list成员用作表头,以建立file对象的链表,链表元素是 file->f_list。该链表包含该超级块表示的文件系统的所有打开文件。

file实例可以用get_emtpy_filep分配,该函数利用了自身的缓存并将实例用基本数据预先初始化。

每当内核打开一个文件或做其他的操作时,如果需要file_struct提供比初始值更多的项,则调用expand_files函数。

文件操作

各个file实例都包含一个指向struct file_operations实例的指针,该结构保存了指向所有可能文件操作的函数指针。

<fs.h>

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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//异步读取操作
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);//读取目录内容
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    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 *);//打开一个文件,相当于将file对象关联到inode
    int (*flush) (struct file *, fl_owner_t id);//在文件描述符关闭时调用,同时将file对象计数减1
    int (*release) (struct inode *, struct file *);//file对象为0时调用
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, 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 (*dir_notify)(struct file *filp, unsigned long arg);
    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 **);
};

目录信息

每个task_struct实例都包含一个指针,指向另一个结构,类型为fs_struct.

<fs_struct.h>

struct fs_struct {
    atomic_t count;
    rwlock_t lock;
    int umask;//用于设置新文件的权限
    struct dentry * root, * pwd, * altroot;
    struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
  • root和rootmnt指定了相关进程的根目录和文件系统
  • pwd和pwdmnt指定了当前工作目录和文件系统。pwdmnt只有进入了一个新的装载点时,才会改变
  • altroot和altrootmnt成员用于实现个性(personality)。这种特性允许为二进制程序建立一个仿真环境,使得程序认为是在不同于Linux的某个操作系统下运行。例如,在Sparc系统上仿真SunOS时就使用了该方法。仿真所需的特殊文件和库安置在一个目录中(通常是/usr/gnemul/)。有关该路径的信息保存在alt成员中。在搜索文件时总是优先扫描上述目录,因此首先会找到仿真的库或系统文件,而不是Linux系 统的文件(这些之后才搜索)。这支持对不同的二进制格式同时使用不同的库

VFS命名空间

VFS命名空间是所有已经装载、构成某个容器目录树的文件系统的集合。

内核使用以下结构管理命名空间。在各种命名空间中,其中之一是VFS命名空间。

<nsproxy.h>

struct nsproxy{
...
    struct mnt_namespace *mnt_ns;
...
}
<mnt_namespace.h>

struct mnt_namespace{
    atomic_t count;//使用该命名空间的进程数目
    struct vfsmount *root;
    struct list_head list;//VFS命名空间中所有文件系统的vfsmount实例
...
}

目录项缓存

dentry结构的主要用途是建立文件名和相关的inode之间的关联。

在VFS连同文件系统实现读取的一个目录项的数据之后,则创建一个dentry实例,以缓存找到的数据。

<dcache.h>

struct dentry {
    atomic_t d_count;
    unsigned int d_flags;       /* protected by d_lock *///DCACHE_DISCONNECTED指定一个dentry当前没有连接到超级块的dentry树。DCACHE_UNHASHED表明该dentry实例没有包含在任何inode的散列表中。
    spinlock_t d_lock;      /* per dentry lock */
    struct inode *d_inode;      /* Where the name belongs to - NULL is
                     * negative *///文件名所属的inode,如果为NULL,则表示不存在的文件名
    /*
     * The next three fields are touched by __d_lookup.  Place them here
     * so they all fit in a cache line.
     */
    struct hlist_node d_hash;   /* lookup hash list */
    struct dentry *d_parent;    /* parent directory *///指向当前节点父目录的dentry实例(对于根目录指向自身)
    struct qstr d_name;         //指定了文件的名称,qstr是一个内核字符串的包装器,它存储了实际的char(只存储最后一个分量,如/usr/src,则存储src) *字符串以及字符串长度和散列值,这使得更容易处理查找工作。

    struct list_head d_lru;     /* LRU list */
    /*
     * d_child and d_rcu can share memory
     */
    union {
        struct list_head d_child;   /* child of parent list *///用于将当前dentry链接到父目录dentry的d_subdirs链表中。
        struct rcu_head d_rcu;
    } d_u;
    struct list_head d_subdirs; /* our children *///子目录/文件的目录项链表
    struct list_head d_alias;   /* inode alias list *///用于将dentry链接到inode的i_dentry链表中,以链接表示相同文件的各个dentry对象
    unsigned long d_time;       /* used by d_revalidate *///
    struct dentry_operations *d_op;
    struct super_block *d_sb;   /* The root of the dentry tree */
    void *d_fsdata;         /* fs-specific data */
#ifdef CONFIG_PROFILING
    struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
    int d_mounted;//当前dentry对象表示一个装载点,那么d_mounted设置为1,否则其值为0
    unsigned char d_iname[DNAME_INLINE_LEN_MIN];    /* small names *///短文件名(文件名只由少量字符组成时,才保存在d_iname中)
};

内存中所有活动的dentry实例都保存在一个散列表中,该散列表使用fs/dcache.c中的全局变量dentry_hashtable实现。用d_hash实现的溢出链,用于解决散列碰撞。在下文中,我将该散列表称 为全局dentry散列表。

内核中还有另一个dentry的链表,表头是全局变量dentry_unused(也在fs/dcache.c中初始化)。所有使用计数器(d_count)到达0(因而任何进程都不再使用)的 dentry实例都自动地放置到该链表上。

缓存组织

dentry对象在内存中的组织,涉及下面两个部分。

  • 一个散列表,包含了所有dentry对象。
  • 一个LRU链表,其中不再使用的对象将授予一个最后宽期限,宽期限过后才从内存移除。

在dentry对象的使用计数器(d_count)到达0时,会被置于LRU链表上,这表明没有什么应用程序正在使用该对象。新项总是置于该链表的起始处。换句话说,一项在链表中越靠后,它就越老,这是经典的LRU原理。prune_dcache会时常调用,例如在卸载文件系统或内核需要更多内存时。其中会删除比较老的对象,以释放内存。要注意,有时候dentry对象可能临时处于该链表上,尽管这些对象仍然处于活动使用状态,而且其使用计数大于0。这是因为内核进行了一些优化:在LRU链表上的dentry对象恢复使用时,不会立即将其从LRU链表移除,这可以省去一些锁操作,从而提高了性能。有些操作如prune_dcache,无论如何代价都比较高,我们可以对这种情况作出补救。具体地,如果遇到使用计数为正值的对象,只是将其从链表移除,而不释放该对象。

dentry操作

dentry_operations结构保存了一些指向各种特定于文件系统可以对dentry对象执行的操作的函数指针。

struct dentry_operations {
    int (*d_revalidate)(struct dentry *, struct nameidata *);//检查内存中,各个dentry对象构成的结构是否仍然能够反映当前文件系统中的情况
    int (*d_hash) (struct dentry *, struct qstr *);//计算散列值,该值用于将对象放置到dentry散列表中。
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);//比较两个dentry对象的文件名。
    int (*d_delete)(struct dentry *);//再d_count到达0时,调用
    void (*d_release)(struct dentry *);//再d_count到达0时,先于d_delete调用
    void (*d_iput)(struct dentry *, struct inode *);//从一个不再使用的dentry对象中释放inode(默认情况下,将inode的使用计数减1,计数器到达0后,将inode从各种链表中移除)
    char *(*d_dname)(struct dentry *, char *, int);
};

由于大多数文件系统都没有实现前述的这些函数,内核的惯例是这样:如果文件系统对每个函数 提供的实现为NULL指针,则将其替换为VFS的默认实现。

标准函数

以下辅助函数需要一个指向struct dentry的指针作为参数。

  • 每当内核的某个部分需要使用一个dentry实例时,都需要调用dget。调用dget将对象的引用 计数加1,即获取对象的一个引用。
  • dput是dget的对应物。如果内核中的某个使用者不再需要一个dentry实例时,就必须调用dput。该函数将dentry对象的使用计数减1。如果计数下降到0,则调用dentry_operations->d_delete方法(如果可用)。此外,还需要使用d_drop从全局dentry散列表移除该实例,并将其置于LRU链表上。
  • d_drop将一个dentry实例从全局dentry散列表移除。
  • d_delete在确认dentry对象仍然包含在全局dentry散列表中之后,使用__d_drop将其移除。如果该对象此时只剩余一个使用者,还会调用dentry_iput将相关inode的使用计数减1。
  • d_instantiate将一个dentry实例与一个inode关联起来。这意味着设置d_inode字段并将该 dentry增加到inode->i_dentry链表。
  • d_add调用了d_instantiate。此外,该对象还添加到全局dentry散列表dentry_hashtable 中。
  • d_alloc为一个新的struct dentry实例分配内存。初始化各个字段,如果给出了一个表示父结点的dentry,则新dentry对象的超级块指针从父结点获取。
  • d_alloc_anon为一个struct dentry实例分配内存,但并不设置与父结点dentry的任何关联, 因此该函数与d_alloc相比去掉了相关参数。
  • d_splice_alias将一个断开连接的dentry对象连接到dentry树中。该功能的inode参数表示 与dentry关联的inode。
  • d_lookup根据目录对应的dentry实例,搜索名称为name的文件对应的dentry对象。

处理VFS对象

文件系统操作

每个文件系统在使用以前必须注册到内核,这样内核都能够了解可用的文件系统,并按需调用装载功能。

注册文件系统

文件系统的表示:

<fs.h>

struct file_system_type {
    const char *name;//文件系统的名称
    int fs_flags;//使用的标志,例如标明只读装载、禁止setuid/setgid操作或进行其他的微调
    int (*get_sb) (struct file_system_type *, int,
               const char *, void *, struct vfsmount *);//从底层存储介质读取超级块
    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;

    struct lock_class_key i_lock_key;
    struct lock_class_key i_mutex_key;
    struct lock_class_key i_mutex_dir_key;
    struct lock_class_key i_alloc_sem_key;
};

文件系统的注册函数,register_filesystem。

<fs/filesystems.c>

int register_filesystem(struct file_system_type * fs)
{
    int res = 0;
    struct file_system_type ** p;

    BUG_ON(strchr(fs->name, '.'));
    if (fs->next)
        return -EBUSY;
    INIT_LIST_HEAD(&fs->fs_supers);
    write_lock(&file_systems_lock);
    p = find_filesystem(fs->name, strlen(fs->name));//查看准备注册的文件系统是否已经存在
    if (*p)
        res = -EBUSY;
    else
        *p = fs;//不存在,将其添加到file_systems文件系统链表上
    write_unlock(&file_systems_lock);
    return res;
}

装载和卸载

每个装载的文件系统都有一个本地根目录,其中包含了系统目录。在将文件系统装载到一个目录时,装载点的内容被替换为即将装载的文件系统的相对根目录的内容。前一个目录数据消失,直至新文件系统卸载才重新出现。

每个装载的文件系统都对应于一个vfsmount结构的实例,其定义如下:

<mount.h>

struct vfsmount {
    struct list_head mnt_hash;      //vfsmount实例的地址和相关的dentry对象的地址用来计算散列和。散列表,称作mount_hashtable,定义在fs/namespace.c中。
    struct vfsmount *mnt_parent;    /* fs we are mounted on *///装载点所在的父文件系统
    struct dentry *mnt_mountpoint;  /* dentry of mountpoint *///装载点在父文件系统中的dentry
    struct dentry *mnt_root;    /* root of the mounted tree *///当前文件系统根目录的dentry
    struct super_block *mnt_sb; /* pointer to superblock *///指向超级块的指针
    struct list_head mnt_mounts;    /* list of children, anchored here *///子文件系统链表
    struct list_head mnt_child; /* and going through their mnt_child *///用于连接到父文件系统中的mnt_mounts
    int mnt_flags;
    /* 4 bytes hole on 64bits arches */
    char *mnt_devname;      /* Name of device e.g. /dev/dsk/hda1 */
    struct list_head mnt_list;      //一个命名空间的所有装载的文件系统都保存在namespace->list链表中。使用vfsmount的mnt_list成员作为链表元素
    struct list_head mnt_expire;    /* link in fs-specific expiry list */ /* 链表元素,用于特定于文件系统的到期链表中 */
    struct list_head mnt_share; /* circular list of shared mounts */ /* 链表元素,用于共享装载的循环链表 */
    struct list_head mnt_slave_list;/* list of slave mounts */  /* 从属装载的链表 */
    struct list_head mnt_slave; /* slave list entry */ /* 链表元素,用于从属装载的链表 */
    struct vfsmount *mnt_master;    /* slave is on master->mnt_slave_list */
    struct mnt_namespace *mnt_ns;   /* containing namespace *///所属的命名空间
    /*
     * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
     * to let these frequently modified fields in a separate cache line
     * (so that reads of mnt_flags wont ping-pong on SMP machines)
     */
    atomic_t mnt_count;        //mnt_count实现了一个使用计数器。每当一个vfsmount实例不再需要时,都必须用mntput将计数器减1。mntget与mntput相对,在获取vfsmount实例使用时,必须调用mntget。
    int mnt_expiry_mark;        /* true if marked for expiry */ /* 如果标记为到期,则其值为true */
    int mnt_pinned;
};

超级块的定义非常冗长,因此我们给出一个简化的版本

<fs.h>

struct super_block {
    struct list_head   s_list;   //将系统中所有的超级块聚集到一个链表中。该链表的表头是全局变量super_blocks
    dev_t      s_dev;    /* 搜索索引,不是kdev_t */
    unsigned long    s_blocksize;   //指定文件系统的块长度(单位字节)
    unsigned char    s_blocksize_bits;   //指定文件系统的块长度(2^s_blocksize_bits字节)
    unsigned char    s_dirt;            //如果以任何方式改变了超级块,需要向磁盘回写,都会将s_dirt设置为1.否则,其值为0.
    unsigned long long  s_maxbytes;  /* 最大的文件长度 */
    struct file_system_type *s_type;   //指向文件系统
    struct super_operations *s_op;
    unsigned long    s_flags;
    unsigned long    s_magic;
    struct dentry    *s_root;   //将超级块与全局根目录的dentry项关联起来(为NULL,则该文件系统是一个伪文件系统,只在内核内部可见)
    struct xattr_handler  **s_xattr; //该结构包含了一些用于处理扩展属性的函数指针

    struct list_head   s_inodes;   /* 所有inode的链表 */
    struct list_head   s_dirty;   /* 脏inode的链表 */
    struct list_head   s_io;    /* 等待回写 */
    struct list_head   s_more_io;  /* 等待回写,另一个链表 */
    struct list_head   s_files;     //包含了一系列file结构,列出了该超级块表示的文件系统上所有打开的文件
    struct block_device   *s_bdev;   // s_dev和s_bdev指定了底层文件系统的数据所在的块设备
    struct list_head   s_instances;     //各个超级块都连接到另一个链表中,表示同一类型文件系统的所有超级块实例。表头是file_system_type结构的fs_supers成员。

    char   s_id[32];       /* 有意义的名字 */
    void      *s_fs_info;  /* 文件系统私有信息 */ 

    /* 创建/修改/访问时间的粒度,单位为ns(纳秒)。   粒度不能大于1秒 */
    u32   s_time_gran;
};  
<fs.h>

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

    void (*read_inode) (struct inode *);//读取inode数据,参数为inode的编号,通过参数传递

    void (*dirty_inode) (struct inode *);//将传递的inode结构标记为脏的,因为其数据已经修改
    int (*write_inode) (struct inode *, int);
    void (*put_inode) (struct inode *);//将inode使用计数器减1
    void (*drop_inode) (struct inode *);
    void (*delete_inode) (struct inode *);//将inode从内存和底层存储介质删除
    void (*put_super) (struct super_block *);//将超级块的私有信息从内存移除,这发生在文件系统卸载、该数据不再需要时
    void (*write_super) (struct super_block *);//将超级块写入存储介质
    int (*sync_fs)(struct super_block *sb, int wait);//将文件系统数据与底层块设备上的数据同步。
    void (*write_super_lockfs) (struct super_block *);
    void (*unlockfs) (struct super_block *);
    int (*statfs) (struct dentry *, struct kstatfs *);//给出有关文件系统的统计信息
    int (*remount_fs) (struct super_block *, int *, char *);//重新装载一个已经装载的文件系统
    void (*clear_inode) (struct inode *);//当某个inode不再使用时,由VFS在内部调用clear_inode。它释放仍然包含数据的所有相关的内存页面
    void (*umount_begin) (struct vfsmount *, int);//仅用于网络文件系统(NFS、CIFS和9fs)和用户空间文件系统(FUSE)。

    int (*show_options)(struct seq_file *, struct vfsmount *);//用于proc文件系统,用于显示文件系统装载的选项
    int (*show_stats)(struct seq_file *, struct vfsmount *);//用于proc文件系统,提供了文件系统的统计信息。
#ifdef CONFIG_QUOTA
    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);
#endif
};

mount系统调用

mount->sys_mount->do_new_mount

static int do_new_mount(struct nameidata *nd, char *type, int flags,
            int mnt_flags, char *name, void *data)
{
    struct vfsmount *mnt;

    if (!type || !memchr(type, 0, PAGE_SIZE))
        return -EINVAL;

    /* we need capabilities... */
    if (!capable(CAP_SYS_ADMIN))
        return -EPERM;

    mnt = do_kern_mount(type, flags, name, data);//使用get_fs_type找到匹配的file_system_type实例,然后分配并初始化vfsmount结构,
    if (IS_ERR(mnt))
        return PTR_ERR(mnt);

    return do_add_mount(mnt, nd, mnt_flags, NULL);//处理一些必需的锁定操作,并确保一个文件系统不会重复装载到同一位置。主要工作委托给graft_tree。
}

nameidata结构用于将一个vfsmount实例和一个dentry实例聚集起来。在这里,该结构保存了装载点的dentry实例和该目录此前(即新的装载操作执行之前)所在文件系统的vfsmount实例。

graft_tree -> attach_recursive_mount:将新装载的文件系统添加到父文件系统的命名空间。

attach_recursive_mount -> mnt_set_mountpoint:确保新的vfsmount实例的mnt_parent成员指向父文件系统的vfsmount实例,而mnt_mountpoint成员指向装载点所在文件系统中的dentry实例。

attach_recursive_mount -> commit_tree:将新的vfsmount实例添加到全局散列表( list_add_tail(&mnt->mnt_hash, mount_hashtable +hash(parent,mnt->mnt_mountpoint)); )以及父文件系统vfsmount实例中的子文件系统链表.(hash(父挂在vfsmount,父dentry))

mount系统调用分析:https://www.cnblogs.com/cslunatic/p/3683117.html

umount实现:使用保存在mnt_mountpoint和mnt_parent中的数据,将环境恢复到所述文件系统装载之前的原始状态。

伪文件系统

伪文件系统是不能装载的文件系统,因而不可能从用户层直接看到,内核可以用kern_mount或kern_mount_data装载一个伪文件系统。

文件操作

查找inode

nameidata结构用来向查找函数传递参数,并保存查找结果。

<namei.h>

struct nameidata {
    struct dentry   *dentry;// 查找完成之后,dentry和mnt包含了找到的文件系统项的数据
    struct vfsmount *mnt;
    struct qstr last;//包含了需要查找的名称
    unsigned int    flags;
    int     last_type;
    unsigned    depth;
    char *saved_names[MAX_NESTED_LINKS + 1];

    /* Intent data */
    union {
        struct open_intent open;
    } intent;
};

内核使用path_lookup函数查找路径或文件名

<fs/namei.c>
int fastcall path_lookup(const char *name, unsigned int flags,
            struct nameidata *nd);//name:所需的名称,flags标志,nd:临时结果的“暂存器”

path_lookup->do_path_lookup

<fs/namei.c>

static int fastcall do_path_lookup(int dfd, const char *name,
                unsigned int flags, struct nameidata *nd)
{
    int retval = 0;
    int fput_needed;
    struct file *file;
    struct fs_struct *fs = current->fs;

    nd->last_type = LAST_ROOT; /* if there are only slashes... */
    nd->flags = flags;
    nd->depth = 0;

    if (*name=='/') {//使用当前根目录的dentry和vfsmount实例作为起点
        read_lock(&fs->lock);
        if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
            nd->mnt = mntget(fs->altrootmnt);
            nd->dentry = dget(fs->altroot);
            read_unlock(&fs->lock);
            if (__emul_lookup_dentry(name,nd))
                goto out; /* found in altroot */
            read_lock(&fs->lock);
        }
        nd->mnt = mntget(fs->rootmnt);
        nd->dentry = dget(fs->root);
        read_unlock(&fs->lock);
    } else if (dfd == AT_FDCWD) {//或者使用当前工作目录作为起点
        read_lock(&fs->lock);
        nd->mnt = mntget(fs->pwdmnt);
        nd->dentry = dget(fs->pwd);
        read_unlock(&fs->lock);
    } else {
        struct dentry *dentry;

        file = fget_light(dfd, &fput_needed);
        retval = -EBADF;
        if (!file)
            goto out_fail;

        dentry = file->f_path.dentry;

        retval = -ENOTDIR;
        if (!S_ISDIR(dentry->d_inode->i_mode))
            goto fput_fail;

        retval = file_permission(file, MAY_EXEC);
        if (retval)
            goto fput_fail;

        nd->mnt = mntget(file->f_path.mnt);
        nd->dentry = dget(dentry);

        fput_light(file, fput_needed);
    }

    retval = path_walk(name, nd);
out:
    if (unlikely(!retval && !audit_dummy_context() && nd->dentry &&
                nd->dentry->d_inode))
        audit_inode(name, nd->dentry);
out_fail:
    return retval;

fput_fail:
    fput_light(file, fput_needed);
    goto out_fail;
}

path_walk --> link_path_walk --> __link_path_walk

<fs/namei.c>

static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
{
    struct path next;
    struct inode *inode;
    int err;
    unsigned int lookup_flags = nd->flags;

    while (*name=='/')
        name++;
    if (!*name)
        goto return_reval;

    inode = nd->dentry->d_inode;
    if (nd->depth)
        lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);

    /* At this point we know we have a real path component. */
    for(;;) {
        unsigned long hash;
        struct qstr this;
        unsigned int c;

        nd->flags |= LOOKUP_CONTINUE;
        err = exec_permission_lite(inode, nd);//权限检查
        if (err == -EAGAIN)
            err = vfs_permission(nd, MAY_EXEC);//调用inode_operations的permission方法进行权限检查
        if (err)
            break;//权限错误

        this.name = name;
        c = *(const unsigned char *)name;

        hash = init_name_hash();
        do {
            name++;
            hash = partial_name_hash(c, hash);
            c = *(const unsigned char *)name;
        } while (c && (c != '/'));
        this.len = name - (const char *) this.name;//提取路径中分量
        this.hash = end_name_hash(hash);//计算散列值

        /* remove trailing slashes? */
        if (!c)//
            goto last_component;//当前分量为最后一个分量
        while (*++name == '/');//为下一次循环做准备,跳过‘/’
        if (!*name)
            goto last_with_slashes;

        /*
         * "." and ".." are special - ".." especially so because it has
         * to be able to know about the current root directory and
         * parent relationships.
         */
        if (this.name[0] == '.') switch (this.len) {
            default:
                break;
            case 2: //两个点的情况,需要回退
                if (this.name[1] != '.')
                    break;
                follow_dotdot(nd);
                /*follow_dotdot函数。当查找操作处理进程的根目录时,..是没有效果的,因为无法切换到根目录的父目录。否则,有两个可用的选项。如果当前目录不是一个装载点的根目录,则将当前dentry对象的d_parent成员用作新的目录,因为它总是表示父目录。但如果当前目录是一个已装载文件系统的根目录,保存在mnt_mountpoint和mnt_parent中的信息用于定义新的dentry和vfsmount对象。follow_mount和lookup_mnt用于取得所需的信息(follow_mount,找到最后的挂载点)*/
                inode = nd->dentry->d_inode;
                /* fallthrough */
            case 1://一个点(.),表示当前目录,内核将直接跳过查找循环的下一个周期,因为在目录层次结构中的位置没有改变
                continue;
        }
        /*
         * See if the low-level filesystem might want
         * to use its own hash..
         */
        if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
            err = nd->dentry->d_op->d_hash(nd->dentry, &this);
            if (err < 0)
                break;
        }
        /* This does the actual lookups.. */
        err = do_lookup(nd, &this, &next);//查找分量对应的dentry实例
        if (err)
            break;

        err = -ENOENT;
        inode = next.dentry->d_inode;
        if (!inode)
            goto out_dput;
        err = -ENOTDIR;
        if (!inode->i_op)
            goto out_dput;

        if (inode->i_op->follow_link) {//下面进行符号连接的处理
            err = do_follow_link(&next, nd);
            if (err)
                goto return_err;
            err = -ENOENT;
            inode = nd->dentry->d_inode;
            if (!inode)
                break;
            err = -ENOTDIR;
            if (!inode->i_op)
                break;
        } else
            path_to_nameidata(&next, nd);
        err = -ENOTDIR;
        if (!inode->i_op->lookup)
            break;
        continue;
        /* here ends the main loop */

last_with_slashes:
        lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
        /* Clear LOOKUP_CONTINUE iff it was previously unset */
        nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
        if (lookup_flags & LOOKUP_PARENT)
            goto lookup_parent;
        if (this.name[0] == '.') switch (this.len) {
            default:
                break;
            case 2:
                if (this.name[1] != '.')
                    break;
                follow_dotdot(nd);
                inode = nd->dentry->d_inode;
                /* fallthrough */
            case 1:
                goto return_reval;
        }
        if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
            err = nd->dentry->d_op->d_hash(nd->dentry, &this);
            if (err < 0)
                break;
        }
        err = do_lookup(nd, &this, &next);
        if (err)
            break;
        inode = next.dentry->d_inode;
        if ((lookup_flags & LOOKUP_FOLLOW)
            && inode && inode->i_op && inode->i_op->follow_link) {
            err = do_follow_link(&next, nd);
            if (err)
                goto return_err;
            inode = nd->dentry->d_inode;
        } else
            path_to_nameidata(&next, nd);
        err = -ENOENT;
        if (!inode)
            break;
        if (lookup_flags & LOOKUP_DIRECTORY) {
            err = -ENOTDIR;
            if (!inode->i_op || !inode->i_op->lookup)
                break;
        }
        goto return_base;
lookup_parent:
        nd->last = this;
        nd->last_type = LAST_NORM;
        if (this.name[0] != '.')
            goto return_base;
        if (this.len == 1)
            nd->last_type = LAST_DOT;
        else if (this.len == 2 && this.name[1] == '.')
            nd->last_type = LAST_DOTDOT;
        else
            goto return_base;
return_reval:
        /*
         * We bypassed the ordinary revalidation routines.
         * We may need to check the cached dentry for staleness.
         */
        if (nd->dentry && nd->dentry->d_sb &&
            (nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
            err = -ESTALE;
            /* Note: we do not d_invalidate() */
            if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))
                break;
        }
return_base:
        return 0;
out_dput:
        dput_path(&next, nd);
        break;
    }
    path_release(nd);
return_err:
    return err;
}

do_lookup的实现

<fs/namei.c>

static int do_lookup(struct nameidata *nd, struct qstr *name,
             struct path *path)
{
    struct vfsmount *mnt = nd->mnt;
    struct dentry *dentry = __d_lookup(nd->dentry, name);//试图在dentry缓存中查找inode

    if (!dentry)
        goto need_lookup;
    if (dentry->d_op && dentry->d_op->d_revalidate)
        goto need_revalidate;
done:
    path->mnt = mnt;
    path->dentry = dentry;
    __follow_mount(path);//确保已装载文件系统的根目录用作装载点(可能有几个文件系统相继装载到前一个文件系统中,,除了后一个文件系统,所有其他文件系统都被相邻的后一个文件系统隐藏)
    return 0;

need_lookup:
    dentry = real_lookup(nd->dentry, name, nd);//缓存无效,必须从底层文件系统中发起一个查找操作
    if (IS_ERR(dentry))
        goto fail;
    goto done;

need_revalidate:
    dentry = do_revalidate(dentry, nd);
    if (!dentry)
        goto need_lookup;
    if (IS_ERR(dentry))
        goto fail;
    goto done;

fail:
    return PTR_ERR(dentry);
}

do_follow_link的实现:

在内核跟踪符号链接时,它必须要注意用户可能构造出的环状结构,如:a->b,b->c,c->a。如果内核不采取适当的防护措施,这可能被利用,导致系统变得不可用。

打开文件

sys_open->do_sys_open->do_filp_open

do_filp_open->open_namei:调用path_lookup函数查找inode并执行几个额外的检查。

do_filp_open->nameidata_to_filp:初始化预读结构,将新创建的file实例放置到超级块的s_files链表上,并调用底层文件系统的file_operations结构的open函数

标准函数

通用读取例程

ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
    struct iovec iov = { .iov_base = buf, .iov_len = len };
    struct kiocb kiocb;
    ssize_t ret;

    init_sync_kiocb(&kiocb, filp);
    kiocb.ki_pos = *ppos;
    kiocb.ki_left = len;

    for (;;) {
        ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);//通常为generic_file_aio_read(异步)
        if (ret != -EIOCBRETRY)
            break;
        wait_on_retry_sync_kiocb(&kiocb);//等待异步读取完成
    }

    if (-EIOCBQUEUED == ret)
        ret = wait_on_sync_kiocb(&kiocb);
    *ppos = kiocb.ki_pos;
    return ret;
}
<mm/filemap.c>

ssize_t
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
        unsigned long nr_segs, loff_t pos)
{
    struct file *filp = iocb->ki_filp;
    ssize_t retval;
    unsigned long seg;
    size_t count;
    loff_t *ppos = &iocb->ki_pos;

    count = 0;
    retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);//确认读请求包含的参数是否有效
    if (retval)
        return retval;

    /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
    if (filp->f_flags & O_DIRECT) {//直接读取,不使用页缓存
        loff_t size;
        struct address_space *mapping;
        struct inode *inode;

        mapping = filp->f_mapping;
        inode = mapping->host;
        retval = 0;
        if (!count)
            goto out; /* skip atime */
        size = i_size_read(inode);
        if (pos < size) {
            retval = generic_file_direct_IO(READ, iocb,
                        iov, pos, nr_segs);
            if (retval > 0)
                *ppos = pos + retval;
        }
        if (likely(retval != 0)) {
            file_accessed(filp);
            goto out;
        }
    }

    retval = 0;
    if (count) {
        for (seg = 0; seg < nr_segs; seg++) {
            read_descriptor_t desc;

            desc.written = 0;
            desc.arg.buf = iov[seg].iov_base;
            desc.count = iov[seg].iov_len;
            if (desc.count == 0)
                continue;
            desc.error = 0;
            do_generic_file_read(filp,ppos,&desc,file_read_actor);//该函数将对文件的读操作转换为对映射的读操作
            retval += desc.written;
            if (desc.error) {
                retval = retval ?: desc.error;
                break;
            }
            if (desc.count > 0)
                break;
        }
    }
out:
    return retval;
}

do_generic_file_read->do_generic_mapping_read

void do_generic_mapping_read(struct address_space *mapping,
                 struct file_ra_state *ra,
                 struct file *filp,
                 loff_t *ppos,
                 read_descriptor_t *desc,
                 read_actor_t actor)
{
    struct inode *inode = mapping->host;
    pgoff_t index;
    pgoff_t last_index;
    pgoff_t prev_index;
    unsigned long offset;      /* offset into pagecache page */
    unsigned int prev_offset;
    int error;

    index = *ppos >> PAGE_CACHE_SHIFT;
    prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
    prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1);
    last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT;
    offset = *ppos & ~PAGE_CACHE_MASK;

    for (;;) {
        struct page *page;
        pgoff_t end_index;
        loff_t isize;
        unsigned long nr, ret;

        cond_resched();
find_page:
        page = find_get_page(mapping, index);
        if (!page) {//页没有在页缓存中
            page_cache_sync_readahead(mapping,
                    ra, filp,
                    index, last_index - index);
            page = find_get_page(mapping, index);//发出一个同步预读请求
            if (unlikely(page == NULL))
                goto no_cached_page;
        }
        if (PageReadahead(page)) {//检查是否需要进行异步预读操作
            page_cache_async_readahead(mapping,
                    ra, filp, page,
                    index, last_index - index);
        }
        if (!PageUptodate(page))//检查页缓存中的数据是否是最新的
            goto page_not_up_to_date;
page_ok:
        /*
         * i_size must be checked after we know the page is Uptodate.
         *
         * Checking i_size after the check allows us to calculate
         * the correct value for "nr", which means the zero-filled
         * part of the page is not copied back to userspace (unless
         * another truncate extends the file - this is desired though).
         */

        isize = i_size_read(inode);
        end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
        if (unlikely(!isize || index > end_index)) {
            page_cache_release(page);
            goto out;
        }

        /* nr is the maximum number of bytes to copy from this page */
        nr = PAGE_CACHE_SIZE;
        if (index == end_index) {
            nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
            if (nr <= offset) {
                page_cache_release(page);
                goto out;
            }
        }
        nr = nr - offset;

        /* If users can be writing to this page using arbitrary
         * virtual addresses, take care about potential aliasing
         * before reading the page on the kernel side.
         */
        if (mapping_writably_mapped(mapping))
            flush_dcache_page(page);

        /*
         * When a sequential read accesses a page several times,
         * only mark it as accessed the first time.
         */
        if (prev_index != index || offset != prev_offset)
            mark_page_accessed(page);
        prev_index = index;

        /*
         * Ok, we have the page, and it's up-to-date, so
         * now we can copy it to user space...
         *
         * The actor routine returns how many bytes were actually used..
         * NOTE! This may not be the same as how much of a user buffer
         * we filled up (we may be padding etc), so we can only update
         * "pos" here (the actor routine has to update the user buffer
         * pointers and the remaining count).
         */
        ret = actor(desc, page, offset, nr);
        offset += ret;
        index += offset >> PAGE_CACHE_SHIFT;
        offset &= ~PAGE_CACHE_MASK;
        prev_offset = offset;

        page_cache_release(page);
        if (ret == nr && desc->count)
            continue;
        goto out;

page_not_up_to_date:
        /* Get exclusive access to the page ... */
        lock_page(page);

        /* Did it get truncated before we got the lock? */
        if (!page->mapping) {
            unlock_page(page);
            page_cache_release(page);
            continue;
        }

        /* Did somebody else fill it already? */
        if (PageUptodate(page)) {
            unlock_page(page);
            goto page_ok;
        }

readpage:
        /* Start the actual read. The read will unlock the page. */
        error = mapping->a_ops->readpage(filp, page);

        if (unlikely(error)) {
            if (error == AOP_TRUNCATED_PAGE) {
                page_cache_release(page);
                goto find_page;
            }
            goto readpage_error;
        }

        if (!PageUptodate(page)) {
            lock_page(page);
            if (!PageUptodate(page)) {
                if (page->mapping == NULL) {
                    /*
                     * invalidate_inode_pages got it
                     */
                    unlock_page(page);
                    page_cache_release(page);
                    goto find_page;
                }
                unlock_page(page);
                error = -EIO;
                shrink_readahead_size_eio(filp, ra);
                goto readpage_error;
            }
            unlock_page(page);
        }

        goto page_ok;

readpage_error:
        /* UHHUH! A synchronous read error occurred. Report it */
        desc->error = error;
        page_cache_release(page);
        goto out;

no_cached_page:
        /*
         * Ok, it wasn't cached, so we need to create a new
         * page..
         */
        page = page_cache_alloc_cold(mapping);
        if (!page) {
            desc->error = -ENOMEM;
            goto out;
        }
        error = add_to_page_cache_lru(page, mapping,
                        index, GFP_KERNEL);
        if (error) {
            page_cache_release(page);
            if (error == -EEXIST)
                goto find_page;
            desc->error = error;
            goto out;
        }
        goto readpage;
    }

out:
    ra->prev_pos = prev_index;
    ra->prev_pos <<= PAGE_CACHE_SHIFT;
    ra->prev_pos |= prev_offset;

    *ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset;
    if (filp)
        file_accessed(filp);
}

Linux内核入门到放弃-虚拟文件系统-《深入Linux内核架构》笔记的更多相关文章

  1. Linux内核入门到放弃-网络-《深入Linux内核架构》笔记

    网络命名空间 struct net { atomic_t count; /* To decided when the network * namespace should be freed. */ a ...

  2. Linux内核入门到放弃-模块-《深入Linux内核架构》笔记

    使用模块 依赖关系 modutils标准工具集中的depmod工具可用于计算系统的各个模块之间的依赖关系.每次系统启动时或新模块安装后,通常都会运行该程序.找到的依赖关系保存在一个列表中.默认情况下, ...

  3. Linux从入门到放弃、零基础入门Linux(第三篇):在虚拟机vmware中安装linux(二)超详细手把手教你安装centos6分步图解

    一.继续在vmware中安装centos6.9 本次安装是进行最小化安装,即没有图形化界面的安装,如果是新手,建议安装带图形化界面的centos, 具体参考Linux从入门到放弃.零基础入门Linux ...

  4. Linux从入门到放弃、零基础入门Linux(第四篇):在虚拟机vmware中安装centos7.7

    如果是新手,建议安装带图形化界面的centos,这里以安装centos7.7的64位为例 一.下载系统镜像 镜像文件下载链接https://wiki.centos.org/Download 阿里云官网 ...

  5. Linux内核入门到放弃-无持久存储的文件系统-《深入Linux内核架构》笔记

    proc文件系统 proc文件系统是一种虚拟的文件系统,其信息不能从块设备读取.只有在读取文件内容时,才动态生成相应的信息. /proc的内容 内存管理 系统进程的特征数据 文件系统 设备驱动程序 系 ...

  6. (笔记)Linux内核学习(十)之虚拟文件系统概念

    虚拟文件系统 虚拟文件系统:内核子系统VFS,VFS是内核中文件系统的抽象层,为用户空间提供文件系统相关接口: 通过虚拟文件系统,程序可以利用标准Linux文件系统调用在不同的文件系统中进行交互和操作 ...

  7. Linux内核入门到放弃-页缓存和块缓存-《深入Linux内核架构》笔记

    内核为块设备提供了两种通用的缓存方案. 页缓存(page cache) 块缓存(buffer cache) 页缓存的结构 在页缓存中搜索一页所花费的时间必须最小化,以确保缓存失效的代价尽可能低廉,因为 ...

  8. Linux内核入门到放弃-Ext2数据结构-《深入Linux内核架构》笔记

    Ext2文件系统 物理结构 结构概观 块组是该文件系统的基本成分,容纳了文件系统的其他结构.每个文件系统都由大量块组组成,在硬盘上相继排布: ----------------------------- ...

  9. Linux内核入门到放弃-设备驱动程序-《深入Linux内核架构》笔记

    I/O体系结构 总线系统 PCI(Peripheral Component Interconnect) ISA(Industrial Standard Architecture) SBus IEEE1 ...

随机推荐

  1. SQL优化总结之一

    一.实践中如何优化mysql 1) SQL语句及索引的优化 2) 数据库表结构的优化 3) 系统配置的优化 4) 硬件优化 二.索引的底层实现原理和优化 2.1 底层实现 在DB2数据库中索引采用的是 ...

  2. Storm环境搭建(分布式集群)

    作为流计算的开篇,笔者首先给出storm的安装和部署,storm的第二篇,笔者将详细的介绍storm的工作原理.下边直接上干货,跟笔者的步伐一块儿安装storm. 原文链接:Storm环境搭建(分布式 ...

  3. Powershell:关于hashtable你想知道的一切

    译者语:本篇为一篇译文,详细介绍了在powershell中如何使用hashtable这种数据类型.本文为本人2018年最后一篇博文(哈哈,一年内写没写几篇),也是本人的第一次译文,有不足之处还请指教. ...

  4. Mybatis源码之StatementType

      在mybatis中StatementType的值决定了由什么对象来执行我们的SQL语句.本文来分析下在mybatis中具体是怎么处理的. StatementType 1.StatementType ...

  5. Java开发笔记(五十六)利用枚举类型实现高级常量

    前面介绍了联合利用final和static可实现常量的定义,该方式用于简单的常量倒还凑合,要是用于复杂的.安全性高的常量,那就力不从心了.例如以下几种情况,final结合static的方式便缺乏应对之 ...

  6. Dynamics 365出现数据加密错误怎么办?

    本人微信公众号:微软动态CRM专家罗勇 ,回复290或者20181227可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . Dy ...

  7. IBM Watson启示录:AI不应该仅仅是炫技

    IBM Watson启示录:AI不应该仅仅是炫技 https://mp.weixin.qq.com/s/oNp8QS7vQupbi8fr5RyLxA                         导 ...

  8. java 线程方法 ---- yiled()

    class MyThread3 implements Runnable{ @Override public void run() { for (int i = 0; i < 3; i++){ / ...

  9. Android ION内存分配

    The Android ION memory allocator 英文原文 ION heaps ION设计的目标 为了避免内存碎片化,或者为一些有着特殊内存需求的硬件,比如GPUs.display c ...

  10. CentOS7 设置yum源

    1.关闭防火墙 临时关闭防火墙 systemctl stop firewalld 永久防火墙开机自关闭 systemctl disable firewalld 临时打开防火墙 systemctl st ...