Linux内核入门到放弃-虚拟文件系统-《深入Linux内核架构》笔记

时间:2023-03-10 04:48:15
Linux内核入门到放弃-虚拟文件系统-《深入Linux内核架构》笔记

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