文件系统浅析
一、文件系统定义
什么是文件系统?按照维基百科定义:即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
简单来说,文件系统可以理解成一种将flash(磁盘)抽象成文件夹,供上层用户管理和使用的机制。
1.1 文件系统框架
以linux系统为例,操作系统在初始化的时候就要初始化文件系统,将flash(硬盘)里面存储的数据mount成文件系统。那么初始化文件系统又是值什么呢?
实际文件系统维护的就是一个super_block块,这个super_block块里面会记录我们创建的文件信息,如文件名、文件链接数、inode以及在flash中的存储位置等信息。此外,它也存在自己的备份机制。以ext3文件系统为例。
struct ext3_super_block {
unsigned int s_inodes_count; /* inodes 计数 */
unsigned int s_blocks_count; /* blocks 计数 */
unsigned int s_r_blocks_count; /* 保留的 blocks 计数 */
unsigned int s_free_blocks_count; /* 空闲的 blocks 计数 */
unsigned int s_free_inodes_count; /* 空闲的 inodes 计数 */
unsigned int s_first_data_block; /* 第一个数据 block */
unsigned int s_log_block_size; /* block 的大小 */
signed int s_log_frag_size; /* 可以忽略 */
unsigned int __u32 s_blocks_per_group; /* 每 block group 的 block 数量 */
unsigned int s_frags_per_group; /* 可以忽略 */
unsigned int s_inodes_per_group; /* 每 block group 的 inode 数量 */
unsigned int s_mtime; /* Mount time */
unsigned int s_wtime; /* Write time */
unsigned short s_mnt_count; /* Mount count */
signed short s_max_mnt_count; /* Maximal mount count */
unsigned short s_magic; /* Magic 签名 */
unsigned short s_state; /* File system state */
unsigned short s_errors; /* Behaviour when detecting errors */
unsigned short s_minor_rev_level; /* minor revision level */
unsigned int s_lastcheck; /* time of last check */
unsigned int s_checkinterval; /* max. time between checks */
unsigned int s_creator_os; /* 可以忽略 */
unsigned int s_rev_level; /* Revision level */
unsigned short s_def_resuid; /* Default uid for reserved blocks */
unsigned short s_def_resgid; /* Default gid for reserved blocks */
unsigned int s_first_ino; /* First non-reserved inode */
unsigned short s_inode_size; /* size of inode structure */
unsigned short s_block_group_nr; /* block group # of this superblock */
unsigned int s_feature_compat; /* compatible feature set */
unsigned int s_feature_incompat; /* incompatible feature set */
unsigned int s_feature_ro_compat; /* readonly-compatible feature set */
unsigned char s_uuid[16]; /* 128-bit uuid for volume */
unsigned char s_volume_name[16]; /* volume name */
unsigned char s_last_mounted[64]; /* directory where last mounted */
unsigned int s_algorithm_usage_bitmap; /* 可以忽略 */
unsigned char s_prealloc_blocks; /* 可以忽略 */
unsigned char s_prealloc_dir_blocks; /* 可以忽略 */
unsigned short s_padding1; /* 可以忽略 */
unsigned char __u8 s_journal_uuid[16]; /* uuid of journal superblock */
unsigned int s_journal_inum; /* 日志文件的 inode 号数 */
unsigned int s_journal_dev; /* 日志文件的设备号 */
unsigned int s_last_orphan; /* start of list of inodes to delete */
unsigned int s_reserved[197]; /* 可以忽略 */
};
1.1.1 单文件读取
以下将介绍单个文件读写方式。从图中可以看出,在linux中对单个文件的操作是先去找到文件的inode节点信息,然后在通过super_block上查找到对应inode的block数据块,进而获取文件内容。
1.1.2 文件夹下文件读取
针对文件夹里面的文件,则是需要先找到文件夹的inode,从inode对应的block中找到记录文件夹内文件的indoe,进而再重复上述单文件流程。因此文件存储越深,理论上需要IO的次数就越多。因此,为解决IO耗时问题,文件系统引入了d_entry的数据结构,对文件进行管理。
注:d_entry只有在对文件进行操作过一次后才会维护本文件的数据结构,因此对外体现就是首次打开文件慢,后续再次打开KPI明显降低。
1.1.3 d_entry数据结构
为提高多级文件索引效率,文件系统中引入了d_entry的数据结构,对文件的属性进行管理。它记录文件的名称,父目录,子目录等信息,形成我们看到的层级树状结构。与inode不同的是,dentry只存在于内存,flash(磁盘)上并没有对应的实体文件,因此目录项也就不会涉及回写磁盘。因此极大的降低了多级目录下读取文件的IO次数。
通常文件系统在创建文件时(mkdir/touch)时便会去查找和创建一个d_entry。
long do_mkdirat(int dfd, const char __user *pathname, umode_t mode)
{
//为该目录创建dentry
dentry = user_path_create(dfd, pathname, &path, lookup_flags);
...//省略
if (!error)
//调用对应文件系统的mkdir为该目录创建inode,对于ext4调用的是ext4_mkdir
error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
...
}
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; //通过该变量挂载到全局dentry哈希表dentry_hashtable上
struct dentry *d_parent; //父目录的目录项对象
struct qstr d_name; //目录项名称
struct inode *d_inode; //指向目录对应的inode结构
unsigned char d_iname[DNAME_INLINE_LEN]; //存放短文件名
struct lockref d_lockref; //使用计数
const struct dentry_operations *d_op; //目录项的操作函数
struct super_block *d_sb; //目录项对应的超级块
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; //文件系统私有数据
union {
struct list_head d_lru; //通过该变量链接到超级块的s_dentry_lru链表
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; //通过该变量链接到父目录的d_subdirs中
struct list_head d_subdirs; //本目录所有子目录链表
union {
//通过该变量链接到inode结构的i_dentry中,
//一个inode可对应多个dentry,因为存在软链接
struct hlist_node d_alias;
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
};
1.2 虚拟文件系统VFS
VFS从代码角度来说,是为适配各种文件系统,从而做的一层抽象。像上述中的d_entry也是属于在VFS构建。同样,VFS也有自己的super_block,由于每种文件系统的超级块的格式不同,因此需要向虚拟文件系统注册文件系统类型file_system_type,并且实现 mount 方法用来读取和解析超级块。
struct super_block {
struct list_head s_list; /* Keep this first 指向超级块链表的指针*/
dev_t s_dev; /*具体文件系统的块设备描述符*/
unsigned char s_blocksize_bits;
unsigned long s_blocksize; /*以字节为单位的数据块的大小*/
loff_t s_maxbytes; /* Max file size */
struct file_system_type *s_type; /*文件系统类型,每个文件系统只有一个结构体*/
const struct super_operations *s_op; /*指向super_block操作的函数集合*/
alloc_inode () /*创建并初始化一个inode*/
write_inode() /*将inode同步到磁盘*/
sync_fs() /*同步文件系统元数据到磁盘*/
list_head s_inodes; /* all inodes */
list_head s_inodess_dirty; /* dirty inodes */
void *s_fs_info; /* Filesystem private info 具体文件系统的私有数据*/
}
1.2.1 文件系统类型
常见的文件系统类型用如下几种。
按磁盘划分:ext3、ext4、XFS、NTFS、APFS
闪存(flash)和SSD:FAT、exFAT、jffs2
NAS:NFS、SMB、AFP(存储与服务器分离)
1.2.2 虚拟文件系统架构
参考链接:
https://blog.csdn.net/u010039418/article/details/115254325
https://blog.csdn.net/u010039418/article/details/114806339