本篇将在前面的基础上,再次通过剖析虚拟文件系统以及更下层文件系统结构,主要是基于结构篇深度剖析。

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;//文件的读写缓存
};


super分区 android_链表


VFS是具体文件系统的抽象,VFS是依靠超级块、dentry、inode以及文件这些结构来发挥作用。