1 VFS 文件系统
计算机体系由三个部分组成:计算,存储,网络,体现了物体流转的三个方面。
生产酒也是这样的一个过程,首先是收割小麦,将小麦发酵蒸馏形成了酒(处理),然后装在瓶子里,运送到仓库(传输)。最后将所有的酒存在仓库里(存储)
物理设备比如磁盘可以看作是土地,有了土地之后并不能使用,它还无法存储酒。需要在土地上建立仓库,并给每一个仓库通电,编号。这个过程可以理解为建立文件系统的过程。文件系统建立以后就可以往里面存酒了。什么样的酒存在哪个仓库需要有管理人员记录在本子上。当然仓库在存储的时候不一定存的都是酒,也可能存一些箱子,或者是小麦。
虚拟文件系统可以理解为建造仓库的标准。建造仓库的时候我们会告诉施工队,我要实现的功能有哪些,比如我希望仓库可以存储5吨的货物,仓库要有一个自动门,汽车来到验证车牌可以放行,仓库内部的温度要保存在5度左右。只要满足这几个要求和标准即可。至于施工队用什么样的材料建造房屋。用的是铁门还是合金门,都不是使用仓库的人要考虑的。
而在VFS则定义了虚拟文件系统,这个东西类似于建造仓库的标准。有了标准之后,所有的文件系统都需要按照这种标准来构建文件系统。
从上图可以看到,虚拟机文件系统介于具体的文件系统和C语言标准库之间,有了VFS之后,所有按照VFS接口开发的文件系统都可以接入linux,标准的接口提供了标准的操作,接口的设计理念一致贯穿于整个编程科学的发展。
2 VFS关键数据结构及关系
上面是虚拟文件系统的一个概览,虚拟文件系统定义了通用接口,所有具体的文件系统都必须实现这样的结构。task_struct是进程描述符,里面记录了进程的相关信息。一个进程可能操作某个文件,因此,task_struck成员内部有一个files,filtes对应files_struct里面记录了所有打开的file,file于file之间通过双向链表组织起来。
file内部包含了d_dentry,也就是目录项,通过目录项可以找到对应的inode,inode记录了文件的权限和属性。进程在操作文件的时候需要用到权限和属性,比如判断某个进程对某个文件是不是有写权限。inode里面有一个成员i_mapping,对应address_space。其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。至于什么是page cache和swap cache可以
查阅相关知识。以上就是VFS组件相互之间的关系,下面看看每一个具体的组件。
super_block
是文件系统的心脏,它存储了文件系统的全局信息,如硬盘已用空间,数据块可用空间。
文件系统所有的inode信息都链接到超级块上, 如下所示是超级块的数据结构,下面节选某些关键信息。
struct super_block {
struct list_head s_list; /* Keep this first */
dev_t s_dev; /* search index; _not_ kdev_t */
unsigned char s_blocksize_bits;
unsigned long s_blocksize;
loff_t s_maxbytes; /* Max file size */
struct file_system_type *s_type;
………..
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
void *s_fs_info; /* Filesystem private info */
/* Granularity of c/m/atime in ns (cannot be worse than a second) */
u32 s_time_gran;
#ifdef CONFIG_FSNOTIFY
__u32 s_fsnotify_mask;
struct fsnotify_mark_connector __rcu *s_fsnotify_marks;
#endif
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID */
unsigned int s_max_links;
fmode_t s_mode;
struct mutex s_vfs_rename_mutex; /* Kludge */
char *s_subtype;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
struct user_namespace *s_user_ns;
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
}
s_blocksize 代表文件系统的大小。
s_inodes 代表了所有的inode。
s_type 代表文件系统的类型。
Inode
代表一个具体的文件, 包含文件的大小,创建时间,文件的块大小等数据,以及对文件的读写函数,文件的读写缓存信息等。看一下inode的数据结构,这里只保留一些关键的数据信息,其他一些都去掉了。
struct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
unsigned long i_ino;
{
const unsigned int i_nlink;
unsigned int __i_nlink;
};
/* Misc */
unsigned long i_state;
struct rw_semaphore i_rwsem;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned long dirtied_time_when;
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
struct list_head i_wb_list; /* backing dev writeback list */
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
__u32 i_generation;
void *i_private; /* fs or device private pointer */
。。。。。。。
}
链表其实是linux中组织数据非常常用的数据结构,这个结构里面有三个链表头。
i_mapping是非常重要的一个结构,描述了文件地址。
block_device描述对应的块设备(块设备可以被看作一个文件系统来处理)。
inode_operations,该结构体定义了对文件的操作(如下代码所示),可以看到有获取属性,设置属性,重新命名等操作
i_mode,主要作用是用来区分文件类型,比如块设备,字符设备,目录, socket等。
super_block,指向超级块,也就是表示文件系统基本信息的数据结构。
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
int (*create) (struct inode *,struct dentry *, umode_t, bool);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec64 *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
}
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料!进群私聊管理领取 内核资料包(含视频教程、电子书、实战项目及代码)
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
dentry
目录项对象,代表一个目录项。目录项反应了文件系统的树状结构,目前主流的操作系统基本都是用树状结构来组织文件的。linux也不例外。dentry表示一个目录项,目录项下面又有子目录。
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
}
d_inode 这个成员志向inode,
super_block 指向对应的超级块。
dentry_operations 这个成员指向dentry对应的操作,如下所示
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *);
}
File
文件对象,代表进程打开的文件,描述的是进程和文件之间的关系。一个文件,在不同的进程中有不同的文件对象,如下图是文件的数据结构
从文件操作的角度来看文件系统不同结构体之间的关系。
文件系统的主要目的是解决用户对文件的操作,包括创建,删除,修改,存放。操作文件需要在内核中创建相对应的进程,有了进程之后,需要知道进程操作的文件是什么。那么反应进程和文件之间关系的就是file。file是实际发生操作时创建出来的对象,存储在内存里。file内部保存着文件的dentry,也就是目录项,除此之外还保存着file_operation,也就是对文件的读写,打开等操作。通过dentry还可以找到inode,那么就可以对inode执行mkdir等操作。super_block代表了一个文件系统,以及文件了解一个文件系统所需的基本数据。
参考资料:
《深入linux内核架构》
《linux内核探秘》