深入探讨Linux Ext2文件系统:结构、特点与性能优化策略
csdn 2023-12-04

下面以Linux的Ext2为例介绍文件系统的组成。

Ext2采用了分立式目录结构,即一个文件的目录分为目录项和索引节点两个部分。


Ext2的索引节点

在一个实际分立式目录的文件系统中,索引节点(inode)主要需要两部分内容来支持:一是inode结构;二是对于节点的操作函数。

Ext2的索引节点

Ext2的每个文件(或目录)都有唯一的i节点ext2_inode,它保存了一个文件所有与存储有关的属性。

Linux在文件include/linux/ext2_fs.h中定义的i节点结构ext2_inode如下:

struct ext2_inode {
	__le16	i_mode;		/* 文件模式 */
	__le16	i_uid;		/* 文件拥有者uid的低16位 */
	__le32	i_size;		/* 文件大小 */
	__le32	i_atime;	/* 最后访问时间 */
	__le32	i_ctime;	/* 创建时间 */
	__le32	i_mtime;	/* 修改时间 */
	__le32	i_dtime;	/* 删除时间 */
	__le16	i_gid;		/* 块组id的低16位 */
	__le16	i_links_count;	/* 链接计数,即文件别名的数目 */
	__le32	i_blocks;	/* 文件占用的存储块数 */
	__le32	i_flags;	/* 标志 */
	union {
		struct {
			__le32  l_i_reserved1;
		} linux1;
		struct {
			__le32  h_i_translator;
		} hurd1;
		struct {
			__le32  m_i_reserved1;
		} masix1;
	} osd1;				/* OS dependent 1 */
	__le32	i_block[EXT2_N_BLOCKS];/* 文件索引表 */
	__le32	i_generation;	/* File version (for NFS) */
	__le32	i_file_acl;	/* File ACL */
	__le32	i_dir_acl;	/* Directory ACL */
	__le32	i_faddr;	/* 碎片地址 */
	union {
		struct {
			__u8	l_i_frag;	/* 碎片数目 */
			__u8	l_i_fsize;	/* 碎片大小 */
			__u16	i_pad1;
			__le16	l_i_uid_high;	/* these 2 fields    */
			__le16	l_i_gid_high;	/* were reserved2[0] */
			__u32	l_i_reserved2;
		} linux2;
		struct {
			__u8	h_i_frag;	/* 碎片数目 */
			__u8	h_i_fsize;	/* 碎片大小 */
			__le16	h_i_mode_high;
			__le16	h_i_uid_high;
			__le16	h_i_gid_high;
			__le32	h_i_author;
		} hurd2;
		struct {
			__u8	m_i_frag;	/* 碎片数目 */
			__u8	m_i_fsize;	/* 碎片大小 */
			__u16	m_pad1;
			__u32	m_i_reserved2[2];
		} masix2;
	} osd2;				/* 与操作系统相关的数据 */
};

其中,最重要的成员i_mode和指针i_block[]。i_mode指定文件类型;而指针数组i_block[]则是文件索引表。

i_block[]指针数组的示意图如下:

i_block[]共有15项,其中前12项为直接指向文件数据块的指针,后3项分别为采用多级索引结构的“一次间接指针”、“二次间接指针”和“三次间接指针”。其作用与内存管理中的多级页表类似,便于大型文件的存储处理。

也就是说:如果文件比较小,其数据块少于12个,其数据块索引就放在i_block[]的前12项中,如果文件比较大,超过12个数据块就需要分配间接块来保存数据块索引。

Ext2的i节点操作函数

为了对Ext2的i节点进行操作,系统还定义了Ext2文件i节点的操作函数集:

struct inode_operations ext2_file_inode_operations = {
    .truncate = ext2_truncate,
#ifdef CONFIG_EXT2_FS_XATTR
    .setxattr = generic_setxattr,
    .getxattr = generic_getxattr,
    .listxattr = ext2_listxattr,
    .removexattr = generic_removexattr,
#endif
    .setattr = ext2_setattr,
    .permission = ext2_permission,
};

可以看到,这里操作集中没有熟悉的文件操作函数,这是因为这都是对磁盘文件的底层操作,文件还要再经一层乃至多层的封装才能变成我们所熟悉的函数。


Ext2的目录文件及目录项

Ext2的目录文件实质上是一个目录项列表,其中每一项都是一个ext2_dir_entry_2结构的数据。它所包含的主要信息:

  • 目录项中文件名所对应的文件i节点号;
  • 文件类型;
  • 文件名称。

在文件include/linux/ext2_fs.h中定义的目录项结构ext2_dir_entry_2如下:

struct ext2_dir_entry_2 {
	__le32	inode;			/* 文件的i节点号 */
	__le16	rec_len;		/* 目录项的长度 */
	__u8	name_len;		/* 文件名的长度 */
	__u8	file_type;        //文件类型
	char	name[EXT2_NAME_LEN];	/* 文件名 */
};

结构中的域file_type描述文件类型。不同文件类型的取值用枚举定义如下:

enum {
	EXT2_FT_UNKNOWN,
	EXT2_FT_REG_FILE,            //普通文件
	EXT2_FT_DIR,                 //目录
	EXT2_FT_CHRDEV,              //字符设备文件
	EXT2_FT_BLKDEV,              //块设备文件
	EXT2_FT_FIFO,                //管道文件
	EXT2_FT_SOCK,                //Sock文件
	EXT2_FT_SYMLINK,
	EXT2_FT_MAX
};

按照通常的概念,目录文件应该是ext2_dir_entry_2类型的数组,但Ext2没有这样做。为了用户方便,结构ext2_dir_entry_2中的文件名是一个可以根据文件名的长度变化的数组,这种做法就使得各个目录项的长度并不相等,从而难以用数组来组成目录文件。所以Ext2的目录文件采用一个比较特殊的链表结构,如下图:

在这种结构中,目录项是连续存放的,而目录项的连接则通过结构ext2_dir_entry_2中的域rec_len来实现的,即程序通过rec_len作为偏移量来查找下一个目录项。

每个目录文件中的前两项为代表目录自身的“.”和代表其上一级目录(父目录)的“..”。

每当用户需要打开一个文件需要打开一个文件时,首先要指定待打开文件的路径和名称,文件系统会根据路径和名称搜索对应的目录项;然后用该目录项中的i节点号找到该文件的i节点;最后通过访问i节点结构中的i_block[]数据块来访问文件。

目录项、索引节点与文件数据块之间的关系如下所示:


Ext2在磁盘上的存储结构

Ext2文件系统把它所占用的磁盘空间分成若干个块组,如下所示:

每个块组的内部结构如下图所示:

每个块组中都有一个内容完全相同的块——超级块,这个块保存着Ext2整个文件系统的信息。在超级块的后面,依次排序有:用来描述本块组信息的块组描述符表、用来表示本组内存储块使用情况的存储块管理位图、用来记录本块组所有i节点被占用情况的i节点管理位图、本块组的i节点表以及用来存储各种文件的数据块五个部分。

Ext2的超级块

像一本书需要一个前言一样,文件系统也需要有个类似的说明部分,但它说明的是文件系统基本信息,目的是使文件系统的使用者(操作系统)可以了解文件系统的结构、类型等,这个说明部分叫做文件系统的超级块。不同的文件系统具有不同的超级块。系统管理员及系统可以利用超级块中的信息来对文件系统进行维护。

照理说,每个文件系统只要有一个超级块就够了,但Ext2为了保险起见,在每一个块组中都配置了一个超级块。在正常情况下,Ext2只使用第一个块组(块组0)中的超级块,而其他块组中的超级块只是一个备份。

在文件include/linux/ext2_fs.h中定义的超级块数据结构ext2_super_block如下:

struct ext2_super_block {
	__le32	s_inodes_count;		/* 文件系统中节点的总数 */
	__le32	s_blocks_count;		/* 文件系统中块的总数 */
	__le32	s_r_blocks_count;	/* 超级用户保留块的数目 */
	__le32	s_free_blocks_count;	/* 空闲块的总数目 */
	__le32	s_free_inodes_count;	/* 空闲索引节点总数 */
	__le32	s_first_data_block;	/* 第一个数据块 */

	__le32	s_log_block_size;	/* Block size */
	__le32	s_log_frag_size;	/* Fragment size */

	__le32	s_blocks_per_group;	/* 每个块组中的块数 */
	__le32	s_frags_per_group;	/* 每组中的片数 */
	__le32	s_inodes_per_group;	/* 每组中的节点数 */
	__le32	s_mtime;		/* 文件系统的安装时间 */
	__le32	s_wtime;		/* 对超级块写操作的左后时间 */
	__le16	s_mnt_count;		/* 文件系统的安装计数 */
	__le16	s_max_mnt_count;	/* 文件系统的最大安装数 */
	__le16	s_magic;		/* 幻数 */
	__le16	s_state;		/* 文件系统的状态 */
	__le16	s_errors;		/* Behaviour when detecting errors */
	__le16	s_minor_rev_level; 	/* minor revision level */
	__le32	s_lastcheck;		/* time of last check */
	__le32	s_checkinterval;	/* max. time between checks */
	__le32	s_creator_os;		/* OS */
	__le32	s_rev_level;		/* Revision level */
	__le16	s_def_resuid;		/* Default uid for reserved blocks */
	__le16	s_def_resgid;		/* Default gid for reserved blocks */
	__le32	s_first_ino; 		/* First non-reserved inode */
	__le16   s_inode_size; 		/* size of inode structure */

	__le16	s_block_group_nr; 	/* 本超级块所在的块组号 */

	__le32	s_feature_compat; 	/* compatible feature set */
	__le32	s_feature_incompat; 	/* incompatible feature set */
	__le32	s_feature_ro_compat; 	/* readonly-compatible feature set */

	__u8	s_uuid[16];		/* 卷的128位uuid */
	char	s_volume_name[16]; 	/* 卷名 */

	char	s_last_mounted[64]; 	/* directory where last mounted */
	__le32	s_algorithm_usage_bitmap; /* For compression */
	__u8	s_prealloc_blocks;	/* Nr of blocks to try to preallocate*/
	__u8	s_prealloc_dir_blocks;	/* Nr to preallocate for dirs */
	__u16	s_padding1;
	__u8	s_journal_uuid[16];	/* uuid of journal superblock */
	__u32	s_journal_inum;		/* inode number of journal file */
	__u32	s_journal_dev;		/* device number of journal file */
	__u32	s_last_orphan;		/* start of list of inodes to delete */
	__u32	s_hash_seed[4];		/* HTREE hash seed */
	__u8	s_def_hash_version;	/* Default hash version to use */
	__u8	s_reserved_char_pad;
	__u16	s_reserved_word_pad;
	__le32	s_default_mount_opts;
 	__le32	s_first_meta_bg; 	/* First metablock block group */
	__u32	s_reserved[190];	/* Padding to the end of the block */
};

由上述定义中可知,Ext2中的超级块中主要具有如下一些内容:

  • 幻数。文件系统的一个标识,在安装文件系统时用于确认Ext2文件系统;
  • 文件系统的版本号;
  • 文件系统安装计数;
  • 超级块所在的块组号;
  • 数据块的大小;
  • 块组中数据块的数目;
  • 文件系统中空闲块的数目;
  • 文件系统中空闲索引节点的数目;
  • 文件系统中第一个索引节点的号码。

在Ext2文件系统中,第一个索引节点时根目录的入口。

块组描述符表

Ext2的一个块组可以看做文件系统空间的一个分区,与超级块的用途类似,为了向使用者提供块组的相关组织信息,每个块组有一个块组描述符表,其中主要提供块组的位图存放位置和i节点位图存放位置等信息。

Linux在文件include/linux/ext2_fs.h中定义的块组描述符表结构ext2_group_desc如下:

struct ext2_group_desc
{
	__le32	bg_block_bitmap;		/* 指向块组的块位图的指针 */
	__le32	bg_inode_bitmap;		/* 指向i节点位图的指针 */
	__le32	bg_inode_table;		/* i节点表的首地址 */
	__le16	bg_free_blocks_count;	/* 本组块空闲块的数目 */
	__le16	bg_free_inodes_count;	/* 本组块空闲i节点的数目 */
	__le16	bg_used_dirs_count;	/* 本组块分配给目录文件的i节点数目 */
	__le16	bg_pad;
	__le32	bg_reserved[3];
};

块组的块位图

Ext2文件系统用位图来记录块组中数据块的使用情况。数据块位图中的每一位表示该块组中每一个块的使用情况,如果为1,则表示该对应块已经被分配占用;如果为0,则表示该位还未被分配,是空闲块。

块组的i节点位图

Ext2文件系统用位图来记录块组中i节点使用情况。i节点位图中的每一位表示该块组中每一个i节点的使用情况,如果为1,则表示该结点已被分配占用;如果为0,则表示结点还未被分配,是空闲节点。

块组的i节点表

顾名思义,i节点表就是存放文件i节点的表格。每个块组中所有i节点都按i节点号的顺序存储在i节点表中。i节点表通常需要占用若干个数据块。


Ext2文件的用户操作函数集

作为一个为用户服务的文件系统,Ext2位用户提供的文件操作函数集如下:

const struct file_operations ext2_file_operations = {
	.llseek		= generic_file_llseek,
	.read		= do_sync_read,
	.write		= do_sync_write,
	.aio_read	= generic_file_aio_read,
	.aio_write	= generic_file_aio_write,
	.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext2_compat_ioctl,
#endif
	.mmap		= generic_file_mmap,
	.open		= generic_file_open,
	.release	= ext2_release_file,
	.fsync		= ext2_sync_file,
	.splice_read	= generic_file_splice_read,
	.splice_write	= generic_file_splice_write,
};



声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 对话周祖成教授 - 清华大学与西门子EDA的合作之旅


  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
下载排行榜
更多
评测报告
更多
广告