03 ext2文件系统物理结构剖析

在说ext2物理结构之前,要先认祖归宗。

首先,大部分现代文件系统的祖先都能追溯到BSD FFS(Fast File System 1983年 BSD 4.2版本),所以对文件系统的介绍不可能忽略它。从40年后今天来看,BSD FFS的设计理念还有很多值得学习的地方,对于现代文件系统可以说是产生的深远的影响。在它的设计框架里,有一个超级块,一个块位图,一个inode位图和一些预分配的inode表。这种设计可在许多现代文件系统里找到影子。

4.2BSD (August 1983) would take over two years to implement and contained several major overhauls. Before its official release came three intermediate versions: 4.1a from April 1982[13] incorporated a modified version of BBN’s preliminary TCP/IP implementation; 4.1b from June 1982 included the new Berkeley Fast File System, implemented by Marshall Kirk McKusick; and 4.1c in April 1983 was an interim release during the last few months of 4.2BSD’s development. Back at Bell Labs, 4.1cBSD became the basis of the 8th Edition of Research Unix, and a commercially supported version was available from mt Xinu.

From: History of the Berkeley Software Distribution – Wikipedia

一、物理结构

接下来开始正题ext2的物理结构,前面两节或多或少提到了ext2的结构,其实ext2和FFS非常类似,它把磁盘划分成几个固定大小的块组(block groups),每一个块组都像是一个微型的文件系统,拥有完整的超级块,块位图,inode位图和inode表。这样即使在大部分磁盘都损坏的情况下,文件系统检测程序仍然可以恢复部分文件。

ext2块结构布局
ext2块结构布局

上图是ext2的物理结构图,每个ext2文件系统由引导区和块组组成,引导区只有一个,但是块组有很多个,各个字段详细说明见下表:

区块区块含义使用说明
Boot Sector磁盘引导区1)引导区是供操作系统使用,文件系统直接跳过
2)引导区一般是1024字节,也就是1K,占用1个逻辑块
3)引导区后面都是块组,也就是从1024字节开始
以下是块组Block Group
超级块Super Block文件系统属性和控制信息1)块组的第一个块,超级块占用1个逻辑块
2)第一个块组必须有超级块
3)如果开启稀疏超级块特性,不是每个块组都有超级块,只有3、5、7三个数的次幂块组才有比如:9、25、27、49、81等等(见下方代码)
Block Group Descriptor块组描述符1)描述块组中数据块位图位置、inode位图位置、inode表位置、以及空闲块和inode数量、目录数量
2)所谓位置信息,就是块号,通过br_read()就能读取相应的块
3)有超级块的块组,就有块组描述符,目前格式化工具,两者是同时存在或没有
Block Bitmap数据块位图1)一个二进制位序列
2)0表示空闲,1表示被使用
3)下标位置就是数据块相对位置
Inode BitmapInode位图1)一个二进制位序列
2)0表示空闲,1表示有Inode
3)下标位置,在inode表中可以读取inde信息
Inode TableInode表存储具体的inode持久化信息
Data Block数据块具体的数据

注:稀疏超级块判断逻辑代码

// balloc.h

/**
 *	ext2_bg_has_super - number of blocks used by the superblock in group
 *	@sb: superblock for filesystem
 *	@group: group number to check
 *
 *	Return the number of blocks used by the superblock (primary or backup)
 *	in this group.  Currently this will be only 0 or 1.
 */
int ext2_bg_has_super(struct super_block *sb, int group)
{
	if (EXT2_HAS_RO_COMPAT_FEATURE(sb,EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)&&
	    !ext2_group_sparse(group))
		return 0;
	return 1;
}

static inline int test_root(int a, int b)
{
	int num = b;

	while (a > num)
		num *= b;
	return num == a;
}

static int ext2_group_sparse(int group)
{
	if (group <= 1)
		return 1;
	return (test_root(group, 3) || test_root(group, 5) ||
		test_root(group, 7));
}

1.软盘(Floppy Disk)块结构示例

假设软盘容量是1.44MB,格式化成ext2文件系统后,每个block是1KB,一共一个引导区和一个块组,引导区占用1KB,块组0占用1439KB,按照上述结构示意图如下:

Block OffsetLength文字说明
Boot Sector 1KB
byte 0512 byte引导记录
byte 512512 byte附带引导记录数据
Block Group 0,Blocks 1~1439
byte 1024 1024 byte超级块super block
block 21 block块组描述符block descriptor
block 31 block块位图block bitmap
block 41 blockinode位图inode bitmap
block 423 blocksinode表inode table
block 281412 blocks数据块data blocks
ext2 on Floppy Disk

2.20MB大小块结构示例

20MB的ext2文件系统,每个Block是1KB,一共一个引导区和三个块组,引导区还是1KB,每个块组8192个块

Block OffsetLength文字说明
Boot Sector 1KB
byte 0512 byte引导记录
byte 512512 byte附带引导记录数据
Block Group 0,Blocks 1~8192,Total 8192
byte 1024 1024 byte超级块super block
block 21 block块组描述符block descriptor
block 31 block块位图block bitmap
block 41 blockinode位图inode bitmap
block 4214 blocksinode表inode table
block 2157974 blocks数据块data blocks
Block Group 1,Blocks 8193~16384,Total 8192
block 81931 block备份用超级块super block
block 81941 block块组描述符block descriptor
block 81951 block块位图block bitmap
block 81961 blockinode位图inode bitmap
block 8197214 blocksinode表inode table
block 84117974 blocks数据块data blocks
Block Group2,Blocks 16385~24576,Total 8192
block 163851 block块位图block bitmap
block 163861 blockinode位图inode bitmap
block 16387214 blocksinode表inode table
block 166017975blocks数据块data blocks

二、数据结构

1.磁盘超级块

磁盘超级块数据结构包含了各种数量,这些数量信息决定着磁盘上位置信息:

/*
 * Structure of the super block
 */
struct ext2_super_block {
	__le32	s_inodes_count;		/* Inodes count */
	__le32	s_blocks_count;		/* Blocks count */
	__le32	s_r_blocks_count;	/* Reserved blocks count */
	__le32	s_free_blocks_count;	/* Free blocks count */
	__le32	s_free_inodes_count;	/* Free inodes count */
	__le32	s_first_data_block;	/* First Data Block */
	__le32	s_log_block_size;	/* Block size */
	__le32	s_log_frag_size;	/* Fragment size */
	__le32	s_blocks_per_group;	/* # Blocks per group */
	__le32	s_frags_per_group;	/* # Fragments per group */
	__le32	s_inodes_per_group;	/* # Inodes per group */
        ......
}
字段含义说明
s_inodes_countinode总数量
s_blocks_count数据块总数量
s_free_blocks_count空闲块数量等于所有块组描述符中空闲块的和
s_free_inodes_count空闲inode数量等于所有块组描述符中空闲inode的和
s_first_data_block第一个数据块用于定位第一个文件描述符块
s_log_block_size块大小(也就是1024左移位数,也就是乘以2的次幂)使用方法:
blocksize = BLOCK_SIZE << s_log_block_size;

BLOCK_SIZE:1024
s_log_block_size为0:blocksize=1024
s_log_block_size为1:
blocksize=2048
s_log_block_size为2:
blocksize=4096
s_log_frag_size分片大小(也是1024左移位数,也就是乘以2的次幂)s_frag_size = EXT2_MIN_FRAG_SIZE << es->s_log_frag_size;

EXT2_MIN_FRAG_SIZE :1024
s_blocks_per_group每个块组块数每个块组最大是8192块
s_frags_per_group每个块组碎片数每个块组最大是8192个碎片
s_inodes_per_group每个块组inode数每个块组最多是8192个inode

2.块组描述符

块组描述符,记录了当前块组内块位图块号、inode位图块号、inode表块号、空闲块数量、空闲Inode数量、dir数量

/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
	__le32	bg_block_bitmap;		/* Blocks bitmap block */
	__le32	bg_inode_bitmap;		/* Inodes bitmap block */
	__le32	bg_inode_table;		/* Inodes table block */
	__le16	bg_free_blocks_count;	/* Free blocks count */
	__le16	bg_free_inodes_count;	/* Free inodes count */
	__le16	bg_used_dirs_count;	/* Directories count */
	__le16	bg_pad;
	__le32	bg_reserved[3];
};

三、各块读取/解析算法

1.读取超级块

读取超级块在上一篇《构造超级块流程》中已经提及,今天这里再复习一下。在挂载ext2文件系统时,就要构造和填充超级块,此时就要从文件系统上读取之前写入的超级块。

读取超级块调用函数sb_bread(sb,logic_sb_block),第一个参数:主要是提供两个信息,一个是bdev,也就是块设备信息,另一个就是块大小,默认是1024,第二个参数:块号,logic_sb_block默认是1,即第1块。

// super.c ext2_fill_super()
/*
 * If the superblock doesn't start on a hardware sector boundary,
 * calculate the offset.  
 */
if (blocksize != BLOCK_SIZE) {
	logic_sb_block = (sb_block*BLOCK_SIZE) / blocksize;
	offset = (sb_block*BLOCK_SIZE) % blocksize;
} else {
	logic_sb_block = sb_block;
}

if (!(bh = sb_bread(sb, logic_sb_block))) {
	ext2_msg(sb, KERN_ERR, "error: unable to read superblock");
	goto failed_sbi;
}
/*
 * Note: s_es must be initialized as soon as possible because
 *       some ext2 macro-instructions depend on its value
 */
es = (struct ext2_super_block *) (((char *)bh->b_data) + offset);
sbi->s_es = es;
sb->s_magic = le16_to_cpu(es->s_magic);

/* Read the specific block */
static inline struct buffer_head *
sb_bread(struct super_block *sb, sector_t block)
{
	return __bread_gfp(sb->s_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
}

2.读取块组描述符

在构造超级块时(同上一个流程),把所有块组描述符读入缓存,由于每个块组都有一个文件描述符,所以要先计算出组数,然后统一分配缓存空间,然后在循环读取每一个描述符,然后链接到内存的超级块的s_group_desc域(struct buffer_head ** s_group_desc)。

// super.c ext2_fill_super()

/* Calc group desc count */
sbi->s_groups_count = ((le32_to_cpu(es->s_blocks_count) -
			le32_to_cpu(es->s_first_data_block) - 1)
				/ EXT2_BLOCKS_PER_GROUP(sb)) + 1;
db_count = (sbi->s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) /
	   EXT2_DESC_PER_BLOCK(sb);

/* Allocate buffer space */
sbi->s_group_desc = kmalloc_array(db_count,
				   sizeof(struct buffer_head *),
				   GFP_KERNEL);

/* Read desc block */
for (i = 0; i < db_count; i++) {
        /* Locate the block no */
	block = descriptor_loc(sb, logic_sb_block, i);
	sbi->s_group_desc[i] = sb_bread(sb, block);
	if (!sbi->s_group_desc[i]) {
		for (j = 0; j < i; j++)
			brelse (sbi->s_group_desc[j]);
		ext2_msg(sb, KERN_ERR,
			"error: unable to read group descriptors");
		goto failed_mount_group_desc;
	}
}

3.读取块位图

在分配新块时、统计空闲块时等场景,需要读取块位图信息。读取之前,先要从块组描述符处获取块号信息,然后调用sb_getblk(sb, bitmap_blk)获取块位图信息,返回值是一个缓冲区首地址。

// balloc.c

/*
 * Read the bitmap for a given block_group,and validate the
 * bits for block/inode/inode tables are set in the bitmaps
 *
 * Return buffer_head on success or NULL in case of failure.
 */
static struct buffer_head *
read_block_bitmap(struct super_block *sb, unsigned int block_group)
{
	struct ext2_group_desc * desc;
	struct buffer_head * bh = NULL;
	ext2_fsblk_t bitmap_blk;

	desc = ext2_get_group_desc(sb, block_group, NULL);
	if (!desc)
		return NULL;
       bitmap_blk = le32_to_cpu(desc->bg_block_bitmap);
	bh = sb_getblk(sb, bitmap_blk);
	if (unlikely(!bh)) {
		ext2_error(sb, __func__,
			    "Cannot read block bitmap - "
			    "block_group = %d, block_bitmap = %u",
			    block_group, le32_to_cpu(desc->bg_block_bitmap));
		return NULL;
	}
	......

	ext2_valid_block_bitmap(sb, desc, block_group, bh);
	
	return bh;
}

4.读取inode位图

在分配新的inode时,也就是创建新文件时,需要读取inode位图信息,这个过程跟读取块位图类似,关键是从块组描述符中找到块号,然后调用sb_bread(sb, le32_to_cpu(desc->bg_inode_bitmap))读取块位图信息,返回值是一个缓冲区首地址。

/*
 * Read the inode allocation bitmap for a given block_group, reading
 * into the specified slot in the superblock's bitmap cache.
 *
 * Return buffer_head of bitmap on success or NULL.
 */
static struct buffer_head *
read_inode_bitmap(struct super_block * sb, unsigned long block_group)
{
	struct ext2_group_desc *desc;
	struct buffer_head *bh = NULL;

	desc = ext2_get_group_desc(sb, block_group, NULL);
	if (!desc)
		goto error_out;

	bh = sb_bread(sb, le32_to_cpu(desc->bg_inode_bitmap));
	if (!bh)
		ext2_error(sb, "read_inode_bitmap",
			    "Cannot read inode bitmap - "
			    "block_group = %lu, inode_bitmap = %u",
			    block_group, le32_to_cpu(desc->bg_inode_bitmap));
error_out:
	return bh;
}

5.根据Inode表,读取指定Inode

根据ino读取inode时,先要找到对应的块组,然后读取块组描述符,然后找到Inode表的块号,然后调用sb_bread(sb, block),最后根据inode偏移量,找到缓冲区的首地址。

static struct ext2_inode *ext2_get_inode(struct super_block *sb, ino_t ino,
					struct buffer_head **p)
{
	struct buffer_head * bh;
	unsigned long block_group;
	unsigned long block;
	unsigned long offset;
	struct ext2_group_desc * gdp;

	*p = NULL;
	......

	block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb);
	gdp = ext2_get_group_desc(sb, block_group, NULL);
	if (!gdp)
		goto Egdp;
	/*
	 * Figure out the offset within the block group inode table
	 */
	offset = ((ino - 1) % EXT2_INODES_PER_GROUP(sb)) * EXT2_INODE_SIZE(sb);
	block = le32_to_cpu(gdp->bg_inode_table) +
		(offset >> EXT2_BLOCK_SIZE_BITS(sb));
	if (!(bh = sb_bread(sb, block)))
		goto Eio;

	*p = bh;
	offset &= (EXT2_BLOCK_SIZE(sb) - 1);
	return (struct ext2_inode *) (bh->b_data + offset);

    .....
	
	return ERR_PTR(-EIO);
}

四、总结

ext2文件系统物理结构分为多个块组,一个块组默认有8192块,每一块默认是1024字节。一个块组中包含:超级块、块组描述符、块位图、inode位图、inode表、数据块。这样就构成了完整的ext2文件系统。

上一篇:构造超级块super_block

参考资料:

https://tldp.org/HOWTO/Filesystems-HOWTO-6.html

https://tldp.org/LDP/tlk/fs/filesystem.html

https://www.nongnu.org/ext2-doc/ext2.html#superblock

https://e2fsprogs.sourceforge.net/ext2intro.html

https://www.cnblogs.com/codetravel/p/4779430.html

https://blog.csdn.net/weixin_30652897/article/details/98523113

《03 ext2文件系统物理结构剖析》有2个想法

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注