本篇将在前面的基础上,再次通过剖析虚拟文件系统以及更下层文件系统结构,主要是基于结构篇深度剖析。
Linux内核的应用层就是以文件系统为核心而展开的。文件系统在整个内核架构中具有基础架构性质,字符设备、块设备这些设备驱动的概念都要依靠文件系统来实现。
进阶篇说到了存储设备分区,引导区、超级块区、inode table区以及数据块区。
引导区主要是为计算机开机服务的。我们这里略过,先看
1、超级块 super block
超级块(super block)代表了整个文件系统本身,代表一个具体的已安装文件系统,通常。超级块是对应文件系统自身的控制块结构,超级块保存了文件系统设定的文件块大小,超级块的操作函数,文件系统内所有的inode也都要链接到超级块的链表头,当然还包括其余的信息,通过超级块对象,我们可以找到这些必要的信息。
超级块是文件系统的心脏,是与文件系统相关的,在超级块中保存了全局文件信息,如硬盘已用空间、数据块可用空间、inode节点信息等,一个文件系统中有哪些资源都记录在其中。
struct super_block {
struct list_head s_list; /* Keep this first */
dev_t s_dev; /* search index; _not_ kdev_t */
unsigned long s_blocksize;//指定了文件系统的块大小
unsigned char s_blocksize_bits;
unsigned char s_dirt;
unsigned long long s_maxbytes; /* Max file size *///最大文件的尺寸
struct file_system_type *s_type;//指向邋file_system_type结构的指针
struct super_operations *s_op;//超级块操作函数集
struct dquot_operations *dq_op;
struct quotactl_ops *s_qcop;
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;//互斥锁 SMP safe
int s_count;
int s_syncing;
int s_need_sync_fs;
atomic_t s_active;
void *s_security;
struct xattr_handler **s_xattr;
struct list_head s_inodes; /* all inodes *///指向文件系统内的所有inode,通过它可以遍历所有inode对象
struct list_head s_dirty; /* dirty inodes *///所有dirty的inode对象
struct list_head s_io; /* parked for writeback */
struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */
struct list_head s_files;
struct block_device *s_bdev;//指向文件系统存在的块设备指针
struct list_head s_instances;
struct quota_info s_dquot; /* Diskquota specific options */
int s_frozen;
wait_queue_head_t s_wait_unfrozen;
char s_id[32]; /* Informational name */
void *s_fs_info; /* Filesystem private info */
/*
* 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 */
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
};
超级块的内容需要读取具体文件系统在硬盘上的超级块结构获得,超级块是具体文件系统超级块的内存抽象。
创建、管理和撤销超级块对象的代码位于文件fs/super.c 中。超级块对象通过 alloc_super() 函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存的超级块对象中。
从其结构可以看出,超级块包含了与文件系统相关的参数信息:包括inode表容量、文件系统中逻辑块的大小、以逻辑块计。文件系统的大小等。
2、目录项 dentry
首先有必要区分一下目录跟目录项。在Unix语义中,一切皆文件,所以目录也会被当成一个文件来处理,也就不存在目录对象,那么目录项又是什么?目录项代表的是路径中的一个组成部分,他可能包括一个普通文件,换言之,目录可以包含子目录(子文件)、目录是可以层层嵌套的,所以形成了文件路径,而文件路径中的每一部分,就是所谓的目录项。
对文件系统而言,通常,文件和目录是按树状结构来保存的,目录项就是反应文件系统的这种树状关系,在VFS里,目录本身也是一个文件,只是有点特殊。每个文件至少都有一个dentry,这个dentry链接到上级目录的dentry,根目录有一个dentry结构,而根目录里的文件和目录都链接到这个dentry,然后二级目录里的文件和目录,同样通过dentry链接到二级目录,这样层层链接,就形成了一棵dentry树,从树顶可以遍历整个文件系统的所有目录和文件。也就是说,因为这些关系,在Linux环境下你可以从根目录"/"出发,找到整个文件系统中的任何目录和文件,从文件你也可以cd .. 返回上层目录。
上面的数据结构其实很简单,简单的说来就是,一个节点里面含有指向父目录集和子目录集的指针。
为了加快对dentry的查找(如果VFS层遍历路径名中所有的元素并将它们逐个解析成目录项对象,还要到达最深层目录,将是一个非常费力的工作),内核使用了hash表来缓存dentry,成为dentry cache。前面的进阶篇里有说到。file结构中的f_dentry指针就是指向dentry cache中的dentry的。
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative *///指向一个inode结构,这个inode和dentry共同描述了一个普通文件或者目录文件
/*
* 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 *///dentry cache的hash链表
struct dentry *d_parent; /* parent directory *///指向父dentry
struct qstr d_name;//名字
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自身的链表头
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children *///子项(可能是目录,可能是文件)的链表头
struct list_head d_alias; /* inode alias list */
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是否是一个挂载点,如果是挂载点,该成员不为零
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
构建dentry树,重要的结构自然就是d_subdirs、d_child以及d_parent成员了。
d_subdirs 是子项的链表头,所有的子项都要链接到这个链表,d_child是dentry自身的链表头,需要链接到父dentry的d_subdirs成员。d_parent则是指向父dentry结构的指针。当移动文件的时候,需要把一个dentry结构从旧的父dentry的链表上脱离,然后链接到新的父dentry的d_subdirs成员,这样dentry结构之间就构成了一棵目录树。
3、索引节点inode
inode代表一个文件,inode保存了文件的大小,创建时间,文件的块大小等参数,以及对文件的读写函数、文件的读写缓存等信息。
索引节点对象包含了内核在操作文件或目录时需要的全部信息。仅当文件被访问时,才在内存中创建。
一个真实的文件可以有多个dentry,因为指向文件的路径可以有多个(考虑到文件的链接),但是inode只有一个。
struct inode {
struct hlist_node i_hash;
struct list_head i_list;//用于描述inode当前状态的链表
struct list_head i_sb_list;//用于链接到超级块中的inode链表
struct list_head i_dentry;//用于链接到dentry链表
unsigned long i_ino;//inode号
atomic_t i_count;//引用计数,递增递减均为原子操作
umode_t i_mode;//文件类型
unsigned int i_nlink;
uid_t i_uid;//用户。组等等相关的
gid_t i_gid;
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;//三个时间戳
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;//文件块的位数
unsigned long i_blksize;
unsigned long i_version;
blkcnt_t i_blocks;
unsigned short i_bytes;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
struct inode_operations *i_op;//inode操作函数集
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;//指向文件数据块的指针
......
};
4、文件对象 file
文件对象的作用是描述进程和文件交互的关系,需要注意的是,硬盘上并不存在这样一个文件结构,进程打开一个文件,内核就动态创建一个文件对象,同一个文件,在不同的进程中有不同的文件对象。
文件和文件对象是两个不同的概念,“文件”指的是硬盘上具体存在的文件,而“文件对象”是在内存里存在的文件结构。
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 dentry *f_dentry;//文件对应的dentry结构
struct vfsmount *f_vfsmnt;//文件所属于的文件系统的vfsmount对象
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;//文件预读位置
unsigned long f_version;
void *f_security;
/* 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;//文件的读写缓存
};
VFS是具体文件系统的抽象,VFS是依靠超级块、dentry、inode以及文件这些结构来发挥作用。