F2FS源码分析-1.3 [F2FS 元数据布局部分] Checkpoint结构

F2FS源码分析系列文章
主目录
一、文件系统布局以及元数据结构
  1. 总体结构
  2. Superblock区域
  3. Checkpoint区域
  4. Segment Infomation Table区域(SIT)
  5. Node Address Table区域(NAT)
  6. Segment Summary Area区域(SSA)
二、文件数据的存储以及读写
三、文件与目录的创建以及删除(未完成)
四、垃圾回收机制
五、数据恢复机制
六、重要数据结构或者函数的分析

Checkpoint区域

Checkpoint是维护F2FS的数据一致性的结构,它维护了系统当前的状态,例如segment的分配情况,node的分配情况,以及当前的active segment的状态等。F2FS在满足一定的条件的情况下,会将当前系统的分配状态写入到Checkpoint中,万一系统出现突然宕机,这个是F2FS可以从Checkpoint中恢复到上次回写时的状态,以保证数据的可恢复性。F2FS维护了两个Checkpoint结构,互为备份,其中一个是当前正在使用的Checkpoint,另外一个上次回写的稳定的Chcekpoint。如果系统出现了宕机,那么当前的Checkpoint就会变得不可信任,进而使用备份Checkpoint进行恢复。

Checkpoint在元数据区域的物理结构

在这里插入图片描述

根据上述的结构图,Checkpoint区域由几个部分构成,分别是checkpoint元数据区域(f2fs_checkpoint)、orphan node区域、active segments区域。同时active segments区域在不同的情况下,会有不同的形式,目的是减少IO的写入。接下来分别讨论Checkpoint不同的部分。

Checkpoint元数据区域

F2FS使用数据结构f2fs_checkpoint表示Checkpoint结构,它保存在磁盘中f2fs_super_block之后区域中,数据结构如下。需要特别注意的是cur_node_segnocur_node_blkoffcur_data_segnocur_data_blkoff这几个变量。第一节提到,F2FS分为了6个log区域,分别对应hot node/data、warm node/data、cold node/data。F2FS必须定时执行Checkpoint去记录当前系统的log分配到哪个位置,否则在系统宕机的时候,会出现数据丢失等一致性问题,因此cur_xxx_segno以及cur_xxx_blkoff记录了上次Checkpoint时,系统正在使用的log的segment number,以及分配到这个segment的哪个位置。

struct f2fs_checkpoint {
	__le64 checkpoint_ver;		/* CP版本,用于比较新旧版本进行恢复 */
	__le64 user_block_count;	/* # of user blocks */
	__le64 valid_block_count;	/* # of valid blocks in main area */
	__le32 rsvd_segment_count;	/* # of reserved segments for gc */
	__le32 overprov_segment_count;	/* # of overprovision segments */
	__le32 free_segment_count;	/* # of free segments in main area */

	/* information of current node segments */
	__le32 cur_node_segno[MAX_ACTIVE_NODE_LOGS];
	__le16 cur_node_blkoff[MAX_ACTIVE_NODE_LOGS];
	/* information of current data segments */
	__le32 cur_data_segno[MAX_ACTIVE_DATA_LOGS];
	__le16 cur_data_blkoff[MAX_ACTIVE_DATA_LOGS];
	__le32 ckpt_flags;		/* Flags : umount and journal_present */
	__le32 cp_pack_total_block_count;	/* total # of one cp pack */
	__le32 cp_pack_start_sum;	/* start block number of data summary */
	__le32 valid_node_count;	/* Total number of valid nodes */
	__le32 valid_inode_count;	/* Total number of valid inodes */
	__le32 next_free_nid;		/* Next free node number */
	__le32 sit_ver_bitmap_bytesize;	/* Default value 64 */
	__le32 nat_ver_bitmap_bytesize; /* Default value 256 */
	__le32 checksum_offset;		/* checksum offset inside cp block */
	__le64 elapsed_time;		/* mounted time */
	/* allocation type of current segment */
	unsigned char alloc_type[MAX_ACTIVE_LOGS];

	/* SIT and NAT version bitmap */
	unsigned char sit_nat_version_bitmap[1];
} __packed;

Orphan node区域

这是一个动态的区域,如果没有orphan node list则不会占用空间。

Active Segments区域

Active Segments的定义

Active Segments,又称current segment(CURSEG),即当前正在用于进行数据分配的log对应的segment,如用户需要写入8KB数据,那么就会从active segments分配两个block提供给用户写入到磁盘中。F2FS为了提高数据分配的效率,根据数据的特性,一共定义了6个active segment。如第一章的总体结构这一节提到的multi-head logging特性所描述,这6个active segments对应了(how, warm, cold) X (node, data)的数据。

Active Segment与恢复相关的数据结构

CP的主要任务是维护数据一致性,因此CP的active segment区域的主要任务是维护Active Segment的分配状态,使系统宕机时候可以恢复正常。维护active segment需要维护三种信息,分别是f2fs_checkpoint的信息,以及该segment对应的journal和summary的信息。

  • f2fs_checkpoint中Active Segment信息:从上面给出的f2fs_checkpoint定义,cur_node_segno[MAX_ACTIVE_NODE_LOGS]cur_data_segno[MAX_ACTIVE_DATA_LOGS]表示node和data当前的Active Segment的编号(segment number, segno),系统可以通过这个编号找到对应的segment。MAX_ACTIVE_NODE_LOGS以及MAX_ACTIVE_NODE_LOGS分别表示data和node有多少种类型,F2FS默认情况下都等于3,表示、、即HOT、WARM、COLD类型数据。cur_node_blkoff[MAX_ACTIVE_NODE_LOGS]以及cur_data_blkoff[MAX_ACTIVE_DATA_LOGS]则分别表示当前active segment分配到哪一个block(一个segment包含了512个block)。

  • Segment对应的Journal信息:Journal在两处地方都有出现,分别是CP区域以及SSA区域。CP区域的journal主要用来保存active segment的修改信息,而SSA区域的则是持久化保存的所有的segment的journal信息。如系统分配出一个block给用户,那么就要将这个block所在的segment的bitmap中标记为已分配,防止其他写请求使用。分两个区域存放journal是为了减轻频繁更新导致的系统性能下降。例如,当系统写压力很大的时候,bitmap就会频繁被更新,如果这个时候频繁将bitmap写入SSA,就会加重写压力。因此CP区域的Journal的作用就是维护这些经常修改的数据,等待CP被触发的时候才回写到闪存设备,从而减少写压力,提高闪存寿命。(journal的实现参考第六章的journal这一节)

  • Segment对应的Summary信息:summary同样在CP区域和SSA区域有出现,它表示的是逻辑地址和物理地址的映射关系,这个映射关系会使用到GC流程中。summary与segment是一对一的关系,一个summary保存了一个segment所有的block的物理地址和逻辑地址的映射关系。summary保存在CP区域中同样是出于减少IO的写入。

Checkpoint内存管理结构

Checkpoint的内存管理结构是struct f2fs_checkpoint本身,因为Checkpoint一般只在F2FS启动的时候被读取数据,用于数据恢复,而在运行过程中大部分情况都是被写,用于记录恢复信息。因此,Checkpoint不需要过于复杂的内存管理结构,因此使用struct f2fs_checkpoint本身即可以满足需求。

另一方面,active segments,即F2FS的log,主要用于系统free block的分配,因此需要特定的管理结构struct curseg_info进行管理,它的定义如下:

struct curseg_info {
	struct mutex curseg_mutex;
	struct f2fs_summary_block *sum_blk;	/* 每一个segment对应一个summary block */
	struct rw_semaphore journal_rwsem;
	struct f2fs_journal *journal;		/*每一个segment对应一个 info */
	unsigned char alloc_type;
	unsigned int segno;			/* 当前segno */
	unsigned short next_blkoff;		/* 记录当前segment用于分配的下一个给block号 */
	unsigned int zone;			/* current zone number */
	unsigned int next_segno;		/* 当前segno用完以后,下个即将用来分配的segno */
};

从结构分析可以直到,curseg_info记录当前的segment的分配信息,当系统出现宕机的时候,可以从CP记录的curseg_info恢复当上一次CP点的状态。

每一种类型的active segment就对应一个struct curseg_info结构。在F2FS中,使用一个数组来表示:

struct f2fs_sm_info {
	...
	struct curseg_info *curseg_array; // 默认是分配6个curseg_info,分别对应不同类型
	...
}

struct f2fs_sm_info是SIT的管理结构,它也管理了CP最终的active segment的信息,是一个跨区域的管理结构。

struct f2fs_checkpoint通过get_checkpoint_version函数从磁盘读取出来:

static int get_checkpoint_version(struct f2fs_sb_info *sbi, block_t cp_addr,
		struct f2fs_checkpoint **cp_block, struct page **cp_page,
		unsigned long long *version)
{
	unsigned long blk_size = sbi->blocksize;
	size_t crc_offset = 0;
	__u32 crc = 0;

	*cp_page = f2fs_get_meta_page(sbi, cp_addr); // 根据CP所在的地址cp_addr从磁盘读取一个block
	*cp_block = (struct f2fs_checkpoint *)page_address(*cp_page); // 直接转换为数据结构

	crc_offset = le32_to_cpu((*cp_block)->checksum_offset);
	if (crc_offset > (blk_size - sizeof(__le32))) {
		f2fs_msg(sbi->sb, KERN_WARNING,
			"invalid crc_offset: %zu", crc_offset);
		return -EINVAL;
	}

	crc = cur_cp_crc(*cp_block);
	if (!f2fs_crc_valid(sbi, crc, *cp_block, crc_offset)) { // 比较CRC的值,进而知道是否成功读取出来
		f2fs_msg(sbi->sb, KERN_WARNING, "invalid crc value");
		return -EINVAL;
	}

	*version = cur_cp_version(*cp_block);
	return 0;
}

struct curseg_info则是通过build_curseg函数进行初始化:

static int build_curseg(struct f2fs_sb_info *sbi)
{
	struct curseg_info *array;
	int i;

	array = f2fs_kzalloc(sbi, array_size(NR_CURSEG_TYPE, sizeof(*array)),
			     GFP_KERNEL); // 根据active segment类型的数目分配空间
	if (!array)
		return -ENOMEM;

	SM_I(sbi)->curseg_array = array; // 赋值到f2fs_sm_info->curseg_array

	for (i = 0; i < NR_CURSEG_TYPE; i++) { // 为curseg的其他信息分配空间
		mutex_init(&array[i].curseg_mutex);
		array[i].sum_blk = f2fs_kzalloc(sbi, PAGE_SIZE, GFP_KERNEL);
		if (!array[i].sum_blk)
			return -ENOMEM;
		init_rwsem(&array[i].journal_rwsem);
		array[i].journal = f2fs_kzalloc(sbi,
				sizeof(struct f2fs_journal), GFP_KERNEL);
		if (!array[i].journal)
			return -ENOMEM;
		array[i].segno = NULL_SEGNO;
		array[i].next_blkoff = 0;
	}
	return restore_curseg_summaries(sbi); // 从f2fs_checkpoint恢复上一个CP点CURSEG的状态
}

static int restore_curseg_summaries(struct f2fs_sb_info *sbi)
{
	struct f2fs_journal *sit_j = CURSEG_I(sbi, CURSEG_COLD_DATA)->journal;
	struct f2fs_journal *nat_j = CURSEG_I(sbi, CURSEG_HOT_DATA)->journal;
	int type = CURSEG_HOT_DATA;
	int err;

	...
	for (; type <= CURSEG_COLD_NODE; type++) { // 按类型逐个恢复active segment的信息
		err = read_normal_summaries(sbi, type);
		if (err)
			return err;
	}
	...

	return 0;
}

static int read_normal_summaries(struct f2fs_sb_info *sbi, int type)
{
	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
	struct f2fs_summary_block *sum;
	struct curseg_info *curseg;
	struct page *new;
	unsigned short blk_off;
	unsigned int segno = 0;
	block_t blk_addr = 0;

	...
	segno = le32_to_cpu(ckpt->cur_data_segno[type]); // 从CP读取segno
	blk_off = le16_to_cpu(ckpt->cur_data_blkoff[type - CURSEG_HOT_DATA]); // 从CP读取blk_off
	blk_addr = sum_blk_addr(sbi, NR_CURSEG_DATA_TYPE, type); // 获取summary block地址	

	// 读取&转换结构
	new = f2fs_get_meta_page(sbi, blk_addr);
	sum = (struct f2fs_summary_block *)page_address(new);

	curseg = CURSEG_I(sbi, type); // 根据type找到对应的curseg
	mutex_lock(&curseg->curseg_mutex);

	/* 复制&恢复数据 */
	down_write(&curseg->journal_rwsem);
	memcpy(curseg->journal, &sum->journal, SUM_JOURNAL_SIZE);
	up_write(&curseg->journal_rwsem);

	memcpy(curseg->sum_blk->entries, sum->entries, SUM_ENTRY_SIZE);
	memcpy(&curseg->sum_blk->footer, &sum->footer, SUM_FOOTER_SIZE);
	curseg->next_segno = segno;
	reset_curseg(sbi, type, 0);
	curseg->alloc_type = ckpt->alloc_type[type];
	curseg->next_blkoff = blk_off; // 恢复上次的分配状态
	mutex_unlock(&curseg->curseg_mutex);
	f2fs_put_page(new, 1);
	return 0;
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
F2FS是Flash-Friendly File System的简称,是专门为闪存设备设计的文件系统。它采用了一些新的设计思路和数据结构,能够更好地充分利用闪存设备的性能和寿命。F2FS的代码架构可以分为以下几个部分: 1. F2FS核心模块:F2FS核心模块负责管理整个文件系统的元数据,包括超级块、inode、数据块、日志等。它也是整个F2FS文件系统的入口,提供了文件系统的操作接口。 2. Checkpoint模块:Checkpoint模块负责管理F2FS的日志和检查点机制。在F2FS中,所有的写操作都是先写入日志,然后再同步到数据块中。Checkpoint模块会定期将日志和数据同步到检查点中,以保证文件系统的一致性和可靠性。 3. GC模块:GC模块负责管理F2FS的垃圾回收机制。由于闪存设备的写入操作是有限制的,因此需要定期进行垃圾回收以释放已经不再使用的空间。 4. Compression模块:Compression模块负责对数据进行压缩和解压缩,以节省存储空间和提高读写性能。 5. Encryption模块:Encryption模块负责对文件和数据进行加密和解密,以保护用户数据的安全性。 6. Mount模块:Mount模块负责F2FS文件系统的挂载和卸载操作,以及文件系统的格式化和初始化等操作。 总之,F2FS的代码架构设计得非常清晰和模块化,各个模块之间相互独立,并且有很好的扩展性和灵活性。这也是F2FS能够在闪存设备中发挥出色性能和可靠性的重要原因之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值