Linux内核--虚拟文件系统VFS

虚拟文件系统VFS
VFS是一个内核子系统;是在用户进程(或C库)和实际的文件系统之间引入的一种抽象层,用来提供一种操作文件、目录以及其他对象的统一方法。

文件系统的分类
基于磁盘的文件系统(ext2/3 fat iso9660…)、虚拟文件系统(proc)、网络文件系统(nfs)

VFS的通用数据模型主要包括4种对象类型
Superblock对象,表示一个特定的已挂载文件系统
Inode对象,表示一个特定的文件
Dentry对象,表示一个directory entry,即dentry。路径上的每一个单独的组件,都是一个dentry。VFS中没有目录对象,目录只是一种文件。
File对象,表示进程中打开的文件。

Superblock
超级块对象代表一个具体的已安装文件系统

struct super_block {
    struct list_head                s_list;     /* 指向所有超级块的链表 */
    const struct super_operations   *s_op;      /* 超级块方法 */
    struct dentry                   *s_root;    /* 目录挂载点 */
    struct mutex                    s_lock;     /* 超级块信号量 */
    int                             s_count;    /* 超级块引用计数 */
    struct list_head                s_inodes;   /* inode链表 */
    struct mtd_info                 *s_mtd;     /* 存储磁盘信息 */
    fmode_t                         s_mode;     /* 安装权限 */
};

各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。对于并非基于磁盘的文件系统(如基于内存的文件系统,如sysfs),它们会在使用现场创建超级块并将其保存到内存中。超级块对象由super_block结构体表示,定义在中

超级块操作
超级块对象中最重要的一个域是s_op,它指向超级块的操作函数表

Inode
Inode对象包含了内核操作一个文件或者目录需要的所有信息,其数据结构成员包括2类
① 描述文件状态的元数据(例如,文件的创建者、文件的创建日期、文件的大小等等)
② 保存实际文件内容的数据段(或指向数据的指针)

struct inode {
    struct hlist_node       i_hash;     /* 散列表,用于快速查找inode */
    struct list_head        i_list;     /* 索引节点链表 */
    struct list_head        i_sb_list;  /* 超级块链表超级块  */
    struct list_head        i_dentry;   /* 目录项链表 */
    unsigned long           i_ino;      /* 节点号 */
    atomic_t                i_count;    /* 引用计数 */
    unsigned int            i_nlink;    /* 硬链接数 */
    uid_t                   i_uid;      /* 使用者id */
    gid_t                   i_gid;      /* 使用组id */
    struct timespec         i_atime;    /* 最后访问时间 */
    struct timespec         i_mtime;    /* 最后修改时间 */
    struct timespec         i_ctime;    /* 最后改变时间 */
    const struct inode_operations       *i_op;      /* 索引节点操作函数 */
    const struct file_operations        *i_fop;     /* 缺省的索引节点操作 */
    struct super_block      *i_sb;          /* 相关的超级块 */
    struct address_space    *i_mapping;     /* 相关的地址映射 */
    struct address_space    i_data;         /* 设备地址映射 */
    unsigned int            i_flags;        /* 文件系统标志 */
    void                    *i_private;     /* fs 私有指针 */
};

文件系统中的每个文件(包括设备文件和管道这样的特殊文件)都可以用一个inode对象来表示,但是inode对象只有在文件被访问时才会在内存中构建。 inode对象中一些域是和特殊文件相关的,比如i_pipe指向named pipe数据结构,i_bdev指向了block device数据结构,i_cdev指向character device数据结构,这三个指针存储在了union中,因为一个给定的inode最多指向这三个数据结构中的0个或者1个。

Inode操作:Struct inode_operations* i_op是一个指针,指向一组函数,这些函数负责管理结构性的操作(例如删除一个文件)和文件相关的元数据(例如属性)

inode的状态
每个inode处于三种状态中的一个:
inode位于内存中,未关联到文件;(inode_unused)
inode位于内存中,由一个或多个进程使用,已与磁盘同步;(inode_in_used)
inode处于活动使用状态,与磁盘上内容未同步,脏inode;

内核使用两种方式组织inode
链表:每个inode都有一个i_list成员,可将inode存储在链表中(i_list可以存在于超级块的s_dirty、s_io、s_more_io链表)。(inode出现在特定于超级块的链表中i_sb_list,同时出现在特定于状态的链表中,例如inode_in_used)
散列:每个inode同时出现在一个散列表中(根据inode号快速访问inode)

目录项
目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它
为什么引入目录项?路径名查找需要解析路径中的每一个组成部分,解析一个路径并遍历其分量绝非简单的演练,它是耗时的、常规的字符串比较过程,执行耗时、代码繁琐;目录项对象的引入使得这个过程更加简单。

struct dentry {
    atomic_t                d_count;        /* 使用计数 */
    unsigned int            d_flags;        /* 目录项标识 */
    struct inode            *d_inode;       /* 相关联的索引节点 */
    struct hlist_node       d_hash;         /* 散列表 */
    struct dentry           *d_parent;      /* 父目录的目录项对象 */
    struct qstr             d_name;         /* 目录项名称 */
    struct list_head        d_subdirs;      /* 子目录链表 */
    struct list_head        d_alias;        /* 索引节点别名链表 */
    struct dentry_operations    *d_op;      /* 目录项操作指针 */
    ...
}

目录项有三种有效状态
被使用:d_count>0,d_inode指向相应inode节点
未被使用:d_count=0,d_inode指向相应inode节点
负状态:d_inode=NULL

目录项缓存

相关数据结构
/fs/dcache.c中定义的目录项缓存相关的链表结构
static struct list_head *dentry_hashtable;
static LIST_HEAD(dentry_unused);

struct dentry {
	...
	struct hlist_node       d_hash;         /* 散列表 */
	...
	struct list_head d_lru;/* LRU list */
  ...
}

每个 dentry 结构都通过队列头 d_hash 链入哈希链表 dentry_hashtable 中的某个队列里
共享计数d_count为 0 的 dentry 结构都通过队列头 d_lru 链入 LRU 队列 dentry_unused ,在队列中等待释放或者“东山再起”。

相关函数
dcache_init():分配cache空间,并将hash表什么的都初始化
d_alloc():分配一个目录项对象
d_lookup():寻找一个目录

File对象,表示进程中打开的文件。
文件对象由file结构体表示,定义在文件 linux/fs.h 中

struct file {
    struct list_head        f_list;
    struct path f_path; /* 包含目录项 */
    struct file_operations *f_op;  /* 文件操作表 */
    atomic_t f_count; /* 文件对象的使用计数 */
    unsigned int f_flags; /* 当打开文件时所指定的标志 */
    mode_t f_mode; /* 文件的访问模式 */
    loff_t f_pos; /* 文件当前的位移量(文件指针)*/
    unsigned long           f_reada, f_ramax, f_raend, f_ralen, f_rawin;
    struct fown_struct      f_owner;/*处理该文件的进程的相关信息*/
    unsigned int            f_uid, f_gid;/*用户UID和GID*/
    int                     f_error;
    size_t                  f_maxcount;
    unsigned long           f_version;
    void                    *private_data;
    struct kiobuf           *f_iobuf;
    long                    f_iobuf_lock;
}

Linux内核--虚拟文件系统VFS