概述
本节重在讨论VFS文件系统的设计目标、涉及结构体的相关联系视图。unix标准文件类型有5种:普通文件、目录文件、符号链接文件、设备文件、管道文件,本节集中讨论前3者,后2者将另外分析。
VFS的作用
Linux宗旨是一切皆“文件”,广义的讲,凡是可以产生或者消耗信息的都是文件。文件系统是指操作系统用来管理文件以及对文件进行操作的机制和实现。将各文件按照上述分类即形成了不同的文件系统,为了对用户程序隐藏各类不同文件系统的实现细节,就需要将各类不同的文件系统纳入到一个统一管理的框架之中,提供统一的系统调用接口。为了实现这个这个机制就引入了VFS(也可以称为虚拟文件系统转换),起作用相当于用户程序与具体文件系统之间的适配层。
通用文件模型
VFS主要思想在于引入一个通用模型,这个模型能够支持所有的文件系统,将每个具体的文件系统的物理组织结构转化为VFS的通用文件模型。在该模型中,为了便于有组织的管理每个独立的信息,创建一个内存对象(文件)来对其进行描述,为了有组织的管理每个文件,创建了第2个内存对象(目录),为了统一管理目录,创建了第3个内存对象(超级块),由此形成设备模型的3类对象类型:超级块对象、目录项对象、索引节点对象。为了支持上述功能,内核不能对一个特定的函数进行硬编码执行诸如read这样的操作,而是对每个操作都必须使用指针,指向要访问的具体文件系统的适当函数。
通用文件模型对象
- 超级块(super block)
存放已安装文件系统的相关信息。它一般存储在磁盘的特定扇区中,但是对于那些基于内存的文件系统(比如proc,sysfs),超级块是在使用时创建在内存中的。
struct super_block {
struct list_head s_list; //指向超级块链表的指针
dev_t s_dev; //包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301
unsigned char s_dirt; //脏位,标识是否超级块被修改
unsigned char s_blocksize_bits; //上面的size大小占用位数,例如512字节就是9 bits
unsigned long s_blocksize; //文件系统中数据块大小,以字节单位
loff_t s_maxbytes; //允许的最大的文件大小(字节数)
struct file_system_type *s_type; //文件系统类型
const struct super_operations *s_op; //指向某个特定的具体文件系统的用于超级块操作的函数集合
const struct dquot_operations *dq_op; //指向某个特定的具体文件系统用于限额操作的函数集合
const struct quotactl_ops *s_qcop; //用于配置磁盘限额的的方法,处理来自用户空间的请求
const struct export_operations *s_export_op;
unsigned long s_flags; //安装标识
unsigned long s_magic; //区别于其他文件系统的标识
struct dentry *s_root; //指向该具体文件系统安装目录的目录项
struct rw_semaphore s_umount; //对超级块读写时进行同步
struct mutex s_lock; //锁标志位,若置该位,则其它进程不能对该超级块操作
int s_count; //对超级块的使用计数
atomic_t s_active; //引用计数
#ifdef CONFIG_SECURITY
void *s_security;
#endif
const struct xattr_handler **s_xattr;
struct list_head s_inodes; /* all inodes */
struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */
#ifdef CONFIG_SMP
struct list_head __percpu *s_files;
#else
struct list_head s_files; //所有的已经打开文件的链表,这个file和实实在在的进程相关的
#endif
/* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */
struct list_head s_dentry_lru; //已修改的索引节点inode形成的链表,一个文件系统中有很多的inode,有些inode节点的内容会被修改,那么会先被记录,然后写回磁盘
int s_nr_dentry_unused; /* # of dentry on lru */
struct block_device *s_bdev; //指向文件系统被安装的块设备
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct list_head s_instances; //同一类型的文件系统通过这个子墩将所有的super_block连接起来
struct quota_info s_dquot; /* Diskquota specific options */
int s_frozen;
wait_queue_head_t s_wait_unfrozen;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; /* Filesystem private info */
fmode_t s_mode;
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;
/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char __rcu *s_options;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
};
- 目录项对象(dentry object)
和超级块和索引节点不同,目录项并不是实际存在于磁盘上的。在使用的时候在内存中创建目录项对象,其实通过索引节点已经可以定位到指定的文件,但是索引节点对象的属性非常多,在查找,比较文件时,直接用索引节点效率不高,所以引入了目录项的概念。
-
struct dentry { /* RCU lookup touched fields */ unsigned int d_flags; //目录项缓存标识,可取DCACHE_UNUSED、DCACHE_REFERENCED等 seqcount_t d_seq; /* per dentry seqlock */ struct hlist_bl_node d_hash; //内核使用dentry_hashtable对dentry进行管理,dentry_hashtable是由list_head组成的链表,一个dentry创建之后,就通过 d_hash链接进入对应的hash值的链表中。 struct dentry *d_parent; //父目录的目录项 struct qstr d_name; //目录项名称 struct inode *d_inode; //与该目录项关联的inode unsigned char d_iname[DNAME_INLINE_LEN]; //存放短的文件名 /* Ref lookup also touches following */ unsigned int d_count; //引用计数 spinlock_t d_lock; /* per dentry lock */ const struct dentry_operations *d_op; //目录项操作 struct super_block *d_sb; //这个目录项所属的文件系统的超级块 unsigned long d_time; //重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效。 void *d_fsdata; //文件系统私有数据 struct list_head d_lru; //最近未使用的目录项的链表 /* * d_child and d_rcu can share memory */ union { struct list_head d_child; //目录项通过这个加入到父目录的d_subdirs中 struct rcu_head d_rcu; } d_u; struct list_head d_subdirs; //本目录的所有孩子目录链表头 struct list_head d_alias; //一个有效的dentry必然与一个inode关联,但是一个inode可以对应多个dentry,因为一个文件可以被链接到其他文件,所以,这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的。(inode中讲过) };
- 索引节点对象(inode object)
索引节点是VFS中的核心概念,它包含内核在操作文件或目录时需要的全部信息,每个索引节点都有一个唯一的索引节点号,唯一的标志文件系统中的文件。索引节点和超级块一样是实际存储在磁盘上的,当被应用程序访问到时才会在内存中创建。
struct inode {
/* RCU path lookup touches following: */
umode_t i_mode; //文件的类型和访问权限
uid_t i_uid; //文件拥有者标号
gid_t i_gid; //文件所在组标号
const struct inode_operations *i_op; //索引节点操作
struct super_block *i_sb; //inode所属文件系统的超级块指针
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned int i_flags; //索引节点的安装标识
unsigned long i_state; //索引节点的状态标识:I_NEW,I_LOCK,I_FREEING
#ifdef CONFIG_SECURITY
void *i_security;
#endif
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash; //指向hash链表指针,用于inode的hash表
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry; //指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件,那么就会有另一个dentry,这个链表就是将所有的与本inode有关的dentry都连在一起。
struct rcu_head i_rcu;
};
unsigned long i_ino; //索引节点号,每个inode都是唯一的
atomic_t i_count; //引用计数
unsigned int i_nlink; //与该节点建立链接的文件数(硬链接数)
dev_t i_rdev; //实际的设备标识
unsigned int i_blkbits; //块大小,字节单位
u64 i_version; //版本号
loff_t i_size; //inode所代表的的文件的大小,以字节为单位
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime; //文件最后一次访问时间
struct timespec i_mtime; //文件最后一次修改时间
struct timespec i_ctime; //inode最后一次修改时间
blkcnt_t i_blocks; //文件所占块数
unsigned short i_bytes; //文件中最后一个块的字节数
struct rw_semaphore i_alloc_sem;
const struct file_operations *i_fop; //文件操作
struct file_lock *i_flock;
struct address_space *i_mapping; //表示向谁请求页面
struct address_space i_data; //表示被inode读写的页面
#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; //指向字符设备文件指针(如果文件是字符设备时使用)
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
atomic_t i_writecount;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
void *i_private; /* fs or device private pointer */
};
- 文件对象(file object)
存放打开文件与进程之间进行交互的有关信息。这类信息今当进程访问文件期间存在于内核中
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; //文件对象链表指针linux/include/linux/list.h
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; //包含dentry和mnt两个成员,用于确定文件路径
#define f_dentry f_path.dentry //f_path的成员之一,当前文件系统的挂载根目录
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; //f_op; 与该文件相关联的操作函数
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count; //文件的引用计数(有多少进程打开该文件)
unsigned int f_flags; //对应于open时指定的flag
fmode_t f_mode; //读写模式:open的mod_t mode参数
loff_t f_pos; //该文件在当前进程中的文件偏移量
struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。
const struct cred *f_cred;
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;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
-
上述数据结构关联图