Superblock 结构
Superblock保存了F2FS的核心元数据的结构,包括磁盘大小,元区域的各个部分的起始地址等。
Superblock在元数据区域的物理结构
Superblock区域是由两个struct f2fs_super_block结构组成,互为备份。
Superblock物理存放区域结构
f2fs_super_block是F2FS对Superblock的具体数据结构实现,它保存在磁盘的最开始的位置(其实偏移了0x400),F2FS进行启动的时候从磁盘的前端直接读取出来。
struct f2fs_super_block
{
__le32 magic; /* Magic Number */
__le16 major_ver; /* Major Version */
__le16 minor_ver; /* Minor Version */
__le32 log_sectorsize; /* log2 sector size in bytes */
__le32 log_sectors_per_block; /* log2 # of sectors per block 一般是3,因为 1 << 3 = 8 */
__le32 log_blocksize; /* log2 block size in bytes 一般是12,因为 1 << 12 = 4096 */
__le32 log_blocks_per_seg; /* log2 # of blocks per segment 一般是8,因为 1 << 8 = 512 */
__le32 segs_per_sec; /* # of segments per section */
__le32 secs_per_zone; /* # of sections per zone */
__le32 checksum_offset; /* checksum offset inside super block */
__le64 block_count; /* total # of user blocks */
__le32 section_count; /* total # of sections */
__le32 segment_count; /* total # of segments */
__le32 segment_count_ckpt; /* # of segments for checkpoint */
__le32 segment_count_sit; /* # of segments for SIT */
__le32 segment_count_nat; /* # of segments for NAT */
__le32 segment_count_ssa; /* # of segments for SSA */
__le32 segment_count_main; /* # of segments for main area */
__le32 segment0_blkaddr; /* start block address of segment 0 */
__le32 cp_blkaddr; /* start block address of checkpoint */
__le32 sit_blkaddr; /* start block address of SIT */
__le32 nat_blkaddr; /* start block address of NAT */
__le32 ssa_blkaddr; /* start block address of SSA */
__le32 main_blkaddr; /* start block address of main area */
__le32 root_ino; /* root inode number */
__le32 node_ino; /* node inode number */
__le32 meta_ino; /* meta inode number */
__u8 uuid[16]; /* 128-bit uuid for volume */
__le16 volume_name[MAX_VOLUME_NAME]; /* volume name */
__le32 extension_count; /* # of extensions below */
__u8 extension_list[F2FS_MAX_EXTENSION][F2FS_EXTENSION_LEN];/* extension array */
__le32 cp_payload;
__u8 version[VERSION_LEN]; /* the kernel version */
__u8 init_version[VERSION_LEN]; /* the initial kernel version */
__le32 feature; /* defined features */
__u8 encryption_level; /* versioning level for encryption */
__u8 encrypt_pw_salt[16]; /* Salt used for string2key algorithm */
struct f2fs_device devs[MAX_DEVICES]; /* device list */
__le32 qf_ino[F2FS_MAX_QUOTAS]; /* quota inode numbers */
__u8 hot_ext_count; /* # of hot file extension */
__u8 reserved[314]; /* valid reserved region */
} __packed;
对于一个50MB大小的磁盘,格式化后的f2fs_super_block 的信息如下:
magic = -218816496
major_ver = 1
minor_ver = 10
log_sectorsize = 9
log_sectors_per_block = 3
log_blocksize = 12
log_blocks_per_seg = 9
segs_per_sec = 1
secs_per_zone = 1
checksum_offset = 0
block_count = 12800 # 50MB / 4KB = 12800
section_count = 17 # section只在main area应用,因此和main area一样
segment_count = 24
segment_count_ckpt = 2 # checkpoint用了2个segment
segment_count_sit = 2 # SIT也用了2个segment
segment_count_nat = 2 # NAT也用了2个segment
segment_count_ssa = 1 # SSA用了1个segment
segment_count_main = 17 # main area一共有17个可用的segment
segment0_blkaddr = 512
cp_blkaddr = 512 # checkpoint的地址
sit_blkaddr = 1536 # sit的地址
nat_blkaddr = 2560 # nat的地址
ssa_blkaddr = 3584 # ssa的地址
main_blkaddr = 4096 # main area的地址
root_ino = 3
node_ino = 1
meta_ino = 2
extension_count = 27
cp_payload = 0
feature = 0
encryption_level =
Superblock内存管理结构
如上一节所述,f2fs_super_block在内存中的对应的结构是struct f2fs_sb_info,它除了包含了struct f2fs_super_block的信息以外,还包含了一些额外的功能,如锁、SIT、NAT对应的内存管理结构等,简单如下所述:
struct f2fs_sb_info {
struct super_block *sb; /* pointer to VFS super block */
struct proc_dir_entry *s_proc; /* proc entry */
struct buffer_head *raw_super_buf; /* buffer head of raw sb */
struct f2fs_super_block *raw_super; /* raw super block pointer */
int s_flag; /* flags for sbi */
/* for node-related operations */
struct f2fs_nm_info *nm_info; /* node manager */
struct inode *node_inode; /* cache node blocks */
/* for segment-related operations */
struct f2fs_sm_info *sm_info; /* segment manager */
/* for bio operations */
struct f2fs_bio_info read_io; /* for read bios */
struct f2fs_bio_info write_io[NR_PAGE_TYPE]; /* for write bios */
/* for checkpoint */
struct f2fs_checkpoint *ckpt; /* raw checkpoint pointer */
struct inode *meta_inode; /* cache meta blocks */
struct mutex cp_mutex; /* checkpoint procedure lock */
struct rw_semaphore cp_rwsem; /* blocking FS operations */
struct rw_semaphore node_write; /* locking node writes */
struct mutex writepages; /* mutex for writepages() */
wait_queue_head_t cp_wait;
struct inode_management im[MAX_INO_ENTRY]; /* manage inode cache */
/* for orphan inode, use 0'th array */
unsigned int max_orphans; /* max orphan inodes */
/* for directory inode management */
struct list_head dir_inode_list; /* dir inode list */
spinlock_t dir_inode_lock; /* for dir inode list lock */
/* basic filesystem units */
unsigned int log_sectors_per_block; /* log2 sectors per block */
unsigned int log_blocksize; /* log2 block size */
unsigned int blocksize; /* block size */
unsigned int root_ino_num; /* root inode number*/
unsigned int node_ino_num; /* node inode number*/
unsigned int meta_ino_num; /* meta inode number*/
unsigned int log_blocks_per_seg; /* log2 blocks per segment */
unsigned int blocks_per_seg; /* blocks per segment */
unsigned int segs_per_sec; /* segments per section */
unsigned int secs_per_zone; /* sections per zone */
unsigned int total_sections; /* total section count */
unsigned int total_node_count; /* total node block count */
unsigned int total_valid_node_count; /* valid node block count */
unsigned int total_valid_inode_count; /* valid inode count */
int active_logs; /* # of active logs */
int dir_level; /* directory level */
block_t user_block_count; /* # of user blocks */
block_t total_valid_block_count; /* # of valid blocks */
block_t alloc_valid_block_count; /* # of allocated blocks */
block_t last_valid_block_count; /* for recovery */
u32 s_next_generation; /* for NFS support */
atomic_t nr_pages[NR_COUNT_TYPE]; /* # of pages, see count_type */
struct f2fs_mount_info mount_opt; /* mount options */
/* for cleaning operations */
struct mutex gc_mutex; /* mutex for GC */
struct f2fs_gc_kthread *gc_thread; /* GC thread */
unsigned int cur_victim_sec; /* current victim section num */
/* maximum # of trials to find a victim segment for SSR and GC */
unsigned int max_victim_search;
/*
* for stat information.
* one is for the LFS mode, and the other is for the SSR mode.
*/
#ifdef CONFIG_F2FS_STAT_FS
struct f2fs_stat_info *stat_info; /* FS status information */
unsigned int segment_count[2]; /* # of allocated segments */
unsigned int block_count[2]; /* # of allocated blocks */
atomic_t inplace_count; /* # of inplace update */
int total_hit_ext, read_hit_ext; /* extent cache hit ratio */
atomic_t inline_inode; /* # of inline_data inodes */
atomic_t inline_dir; /* # of inline_dentry inodes */
int bg_gc; /* background gc calls */
unsigned int n_dirty_dirs; /* # of dir inodes */
#endif
unsigned int last_victim[2]; /* last victim segment # */
spinlock_t stat_lock; /* lock for stat operations */
/* For sysfs suppport */
struct kobject s_kobj;
struct completion s_kobj_unregister;
};
它的初始化在init_sb_info函数完成:
static void init_sb_info(struct f2fs_sb_info *sbi)
{
struct f2fs_super_block *raw_super = sbi->raw_super;
int i, j;
sbi->log_sectors_per_block = le32_to_cpu(raw_super->log_sectors_per_block);
sbi->log_blocksize = le32_to_cpu(raw_super->log_blocksize);
sbi->blocksize = 1 << sbi->log_blocksize;
sbi->log_blocks_per_seg = le32_to_cpu(raw_super->log_blocks_per_seg);
sbi->blocks_per_seg = 1 << sbi->log_blocks_per_seg;
sbi->segs_per_sec = le32_to_cpu(raw_super->segs_per_sec);
sbi->secs_per_zone = le32_to_cpu(raw_super->secs_per_zone);
sbi->total_sections = le32_to_cpu(raw_super->section_count);
sbi->total_node_count = (le32_to_cpu(raw_super->segment_count_nat) / 2) * sbi->blocks_per_seg * NAT_ENTRY_PER_BLOCK;
sbi->root_ino_num = le32_to_cpu(raw_super->root_ino);
sbi->node_ino_num = le32_to_cpu(raw_super->node_ino);
sbi->meta_ino_num = le32_to_cpu(raw_super->meta_ino);
sbi->cur_victim_sec = NULL_SECNO;
sbi->max_victim_search = DEF_MAX_VICTIM_SEARCH;
sbi->dir_level = DEF_DIR_LEVEL;
sbi->interval_time[CP_TIME] = DEF_CP_INTERVAL;
sbi->interval_time[REQ_TIME] = DEF_IDLE_INTERVAL;
clear_sbi_flag(sbi, SBI_NEED_FSCK);
for (i = 0; i < NR_COUNT_TYPE; i++)
atomic_set(&sbi->nr_pages[i], 0);
for (i = 0; i < META; i++)
atomic_set(&sbi->wb_sync_req[i], 0);
INIT_LIST_HEAD(&sbi->s_list);
mutex_init(&sbi->umount_mutex);
for (i = 0; i < NR_PAGE_TYPE - 1; i++)
for (j = HOT; j < NR_TEMP_TYPE; j++)
mutex_init(&sbi->wio_mutex[i][j]);
init_rwsem(&sbi->io_order_lock); s
pin_lock_init(&sbi->cp_lock);
sbi->dirty_device = 0;
spin_lock_init(&sbi->dev_lock);
init_rwsem(&sbi->sb_lock);
}
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之后区域中,数据结构如下:
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,又称current segment(CURSEG),即当前正在用于分配的segment,如用户需要写入8KB数据,那么就会从active segments分配两个block提供给用户写入到磁盘中。F2FS为了提高数据分配的效率,根据数据的特性,一共定义了6个active segment。如multi-head logging特性所描述,这6个active segments对应了(HOT, WARM, COLD) X (NODE, DATA)的数据。
Active Segments与恢复相关的数据结构:
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。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区域。F2FS定义的journal结构如下,它主要保存了NODE以及SEGMENT的修改信息。如系统分配出一个block给用户,那么就要将这个block在bitmap中标记为已分配,防止其他请求使用。分两个区域存放journal是为了减轻频繁更新导致的系统性能下降。例如,当系统写压力很大的时候,bitmap就会频繁被更新,如果这个时候频繁将bitmap写入SSA,就会加重写压力。因此CP区域的Journal的作用就是维护这些经常修改的数据,等待CP被触发的时候才吸入磁盘,从而减少写压力。
struct f2fs_journal {
union {
__le16 n_nats;
__le16 n_sits;
};
/* spare area is used by NAT or SIT journals or extra info */
union {
struct nat_journal nat_j;
struct sit_journal sit_j;
struct f2fs_extra_info info;
};
} __packed;
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区域的信息涉及到系统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;
}
Segment Information Tabel — SIT结构:
Segment Infomation Table,简称SIT,是F2FS用于集中管理segment状态的结构。它的主要作用是维护的segment的分配信息,它的作用使用两个常见例子进行阐述:
1.用户进行写操作,那么segment会根据用户写入的数据量分配特定数目的block给用户进行数据写入,SIT会将这些已经被分配的block标记为"已经使用",那么之后的写操作就不会再使用这些block。
2.用户进行了覆盖写操作以后,由于F2FS异地更新的特性,F2FS会分配新block给用户写入,同时会将旧block置为无效状态,这样gc的时候可以根据segment无效的block的数目,采取某种策略进行回收。
综上所述,SIT的作用是维护每一个segment的block的使用状态以及有效无效状态。
SIT在元数据区域的物理结构:
从结构图可以知道,SIT区域由N个struct f2fs_sit_block组成,每一个struct f2fs_sit_block包含了55个struct f2fs_sit_entry,每一个entry对应了一个segment的管理状态。每一个entry包含了三个变量: vblocks(记录这个segment有多少个block已经被使用了),valid_map(记录这个segment里面的哪一些block是无效的),mtime(表示修改时间)。
SIT物理存放区域结构
从上图所示,SIT的基本存放单元是struct f2fs_sit_block,它结构如下:
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;
由于一个block的尺寸是4KB,因此跟根据sizeof(struct f2fs_sit_entry entries)的值,得到SIT_ENTRY_PER_BLOCK的值为55。struct f2fs_sit_entry entries用来表示每一个segment的状态信息,它的结构如下:
struct f2fs_sit_entry {
__le16 vblocks; /* reference above */
__u8 valid_map[SIT_VBLOCK_MAP_SIZE]; /* bitmap for valid blocks */
__le64 mtime; /* segment age for cleaning */
} __packed;
第一个参数vblocks表示当前segment有多少个block已经被使用,第二个参数valid_map表示segment内的每一个block的有效无效信息; 由于一个segment包含了512个block,因此需要用512个bit去表示每一个block的有效无效状态,因此SIT_VBLOCK_MAP_SIZE的值是64(8*64=512)。最后一个参数mtime表示这个entry被修改的时间。
SIT内存管理结构
SIT在内存中对应的管理结构是struct sm_info,它在build_segment_manager函数进行初始化:
int build_segment_manager(struct f2fs_sb_info *sbi)
{
struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);
struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
struct f2fs_sm_info *sm_info;
int err;
/* 分配空间 */
sm_info = kzalloc(sizeof(struct f2fs_sm_info), GFP_KERNEL);
/* 初始化一些地址信息,基础信息 */
sbi->sm_info = sm_info;
INIT_LIST_HEAD(&sm_info->wblist_head);
spin_lock_init(&sm_info->wblist_lock);
sm_info->seg0_blkaddr = le32_to_cpu(raw_super->segment0_blkaddr);
sm_info->main_blkaddr = le32_to_cpu(raw_super->main_blkaddr);
sm_info->segment_count = le32_to_cpu(raw_super->segment_count);
sm_info->reserved_segments = le32_to_cpu(ckpt->rsvd_segment_count);
sm_info->ovp_segments = le32_to_cpu(ckpt->overprov_segment_count);
sm_info->main_segments = le32_to_cpu(raw_super->segment_count_main);
sm_info->ssa_blkaddr = le32_to_cpu(raw_super->ssa_blkaddr);
/* 初始化内存中的entry数据结构 */
err = build_sit_info(sbi);
/* 初始化可用segment的数据结构 */
err = build_free_segmap(sbi);
/* 恢复checkpoint active segment区域的信息,参考checkpoint结构那一节 */
err = build_curseg(sbi);
/* 从磁盘中将SIT物理区域记录的 物理区域sit_entry与只存在于内存的sit_entry建立联系 */
build_sit_entries(sbi);
/* 根据checkpoint记录的恢复信息,恢复可用segment的映射关系 */
init_free_segmap(sbi);
/* 恢复脏segment的映射关系 */
err = build_dirty_segmap(sbi);
/* 初始化最大最小的修改时间 */
init_min_max_mtime(sbi);
return 0;
}
build_sit_info用于初始化内存区域的entry,这里需要注意的是注意区分内存entry以及物理区域的entry:
static int build_sit_info(struct f2fs_sb_info *sbi)
{
struct f2fs_super_block *raw_super = F2FS_RAW_SUPER(sbi);
struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
struct sit_info *sit_i;
unsigned int sit_segs, start;
char *src_bitmap, *dst_bitmap;
unsigned int bitmap_size;
/* 分配空间给sit_info */
sit_i = kzalloc(sizeof(struct sit_info), GFP_KERNEL);
/* 将sit_info归于sbi->sm_info进行管理 */
SM_I(sbi)->sit_info = sit_i;
/* 根据main area的segment的数目,给每一个segment在内存中分配一个entry结构 */
sit_i->sentries = vzalloc(TOTAL_SEGS(sbi) * sizeof(struct seg_entry));
/* 这个bitmap是segment的bitmap,作用是当segment全部block都没有使用过,
* 这个segment就需要标记free
*/
bitmap_size = f2fs_bitmap_size(TOTAL_SEGS(sbi));
/* 这个bitmap是记录segment是否为脏的bitmap,作用是当segment分配了一个block之后,
* 这个segment对应的entry信息就会改变,因此将这个segment标记为脏,之后需要通过某种策略
* 将数据写回到SIT区域
*/
sit_i->dirty_sentries_bitmap = kzalloc(bitmap_size, GFP_KERNEL);
/* 这里给每一个内存entry的记录block状态的bitmap分配空间,SIT_VBLOCK_MAP_SIZE=64 */
for (start = 0; start < TOTAL_SEGS(sbi); start++) {
sit_i->sentries[start].cur_valid_map
= kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);
sit_i->sentries[start].ckpt_valid_map
= kzalloc(SIT_VBLOCK_MAP_SIZE, GFP_KERNEL);
}
/* 获取SIT区域包含了多少个segment去存放f2fs_sit_block */
sit_segs = le32_to_cpu(raw_super->segment_count_sit) >> 1;
/* 从checkpoint中恢复bitmap的状态 */
bitmap_size = __bitmap_size(sbi, SIT_BITMAP);
src_bitmap = __bitmap_ptr(sbi, SIT_BITMAP);
dst_bitmap = kmemdup(src_bitmap, bitmap_size, GFP_KERNEL);
/* 初始化其他信息 */
sit_i->s_ops = &default_salloc_ops;
sit_i->sit_base_addr = le32_to_cpu(raw_super->sit_blkaddr);
sit_i->sit_blocks = sit_segs << sbi->log_blocks_per_seg;
sit_i->written_valid_blocks = le64_to_cpu(ckpt->valid_block_count);
sit_i->sit_bitmap = dst_bitmap;
sit_i->bitmap_size = bitmap_size;
sit_i->dirty_sentries = 0;
sit_i->sents_per_block = SIT_ENTRY_PER_BLOCK;
sit_i->elapsed_time = le64_to_cpu(sbi->ckpt->elapsed_time);
sit_i->mounted_time = CURRENT_TIME_SEC.tv_sec;
mutex_init(&sit_i->sentry_lock);
return 0;
}
build_free_segmap用于初始化segment的分配状态:
static int build_free_segmap(struct f2fs_sb_info *sbi)
{
struct f2fs_sm_info *sm_info = SM_I(sbi);
struct free_segmap_info *free_i;
unsigned int bitmap_size, sec_bitmap_size;
/* 给管理segment分配状态的free_segmap_info分配内存空间 */
free_i = kzalloc(sizeof(struct free_segmap_info), GFP_KERNEL);
/* 将sit_info归于sbi->sm_info进行管理 */
SM_I(sbi)->free_info = free_i;
/* 根据segment的数目初始化free map的大小 */
bitmap_size = f2fs_bitmap_size(TOTAL_SEGS(sbi));
free_i->free_segmap = kmalloc(bitmap_size, GFP_KERNEL);
/* 由于1 section = 1 segment,将sec map看作为根据segment map同等作用就好 */
sec_bitmap_size = f2fs_bitmap_size(TOTAL_SECS(sbi));
free_i->free_secmap = kmalloc(sec_bitmap_size, GFP_KERNEL);
/* 在从checkpoint恢复数据之前,将所有的segment设置为dirty */
memset(free_i->free_segmap, 0xff, bitmap_size);
memset(free_i->free_secmap, 0xff, sec_bitmap_size);
/* 初始化其他信息 */
free_i->start_segno =
(unsigned int) GET_SEGNO_FROM_SEG0(sbi, sm_info->main_blkaddr);
free_i->free_segments = 0;
free_i->free_sections = 0;
rwlock_init(&free_i->segmap_lock);
return 0;
}
build_sit_entries的作用是从SIT的物理区域存放的物理entry与内存的entry建立联系,首先看看物理entry和内存entry的差异在哪里。
// 物理entry
struct f2fs_sit_entry {
__le16 vblocks; /* reference above */
__u8 valid_map[SIT_VBLOCK_MAP_SIZE]; /* bitmap for valid blocks */
__le64 mtime; /* segment age for cleaning */
} __packed;
// 内存entry
struct seg_entry {
unsigned short valid_blocks; /* # of valid blocks */
unsigned char *cur_valid_map; /* validity bitmap of blocks */
unsigned short ckpt_valid_blocks;
unsigned char *ckpt_valid_map;
unsigned char type; /* segment type like CURSEG_XXX_TYPE */
unsigned long long mtime; /* modification time of the segment */
};
两者之间的差异主要是多了表示segment类型的type变量,以及多了两个与checkpoint相关的内容。
其实物理entry也包含了type的信息,但是为了节省空间,将type于vblocks存放在了一起,及vblocks的前10位表示数目,后6位表示type,他们的关系可以用f2fs_fs.h找到:
#define SIT_VBLOCKS_SHIFT 10
#define SIT_VBLOCKS_MASK ((1 << SIT_VBLOCKS_SHIFT) - 1)
#define GET_SIT_VBLOCKS(raw_sit) \
(le16_to_cpu((raw_sit)->vblocks) & SIT_VBLOCKS_MASK)
#define GET_SIT_TYPE(raw_sit) \
((le16_to_cpu((raw_sit)->vblocks) & ~SIT_VBLOCKS_MASK) \
>> SIT_VBLOCKS_SHIFT)
因此,内存entry实际上仅仅多了2个与checkpoint相关的信息,即ckpt_valid_blocks与ckpt_valid_map。在系统执行checkpoint的时候,会将valid_blocks以及cur_valid_map的值分别写入ckpt_valid_blocks与ckpt_valid_map,当系统出现宕机的时候根据这个值恢复映射信息。
继续分析build_sit_entries的代码,
static void build_sit_entries(struct f2fs_sb_info *sbi)
{
struct sit_info *sit_i = SIT_I(sbi);
struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_COLD_DATA);
struct f2fs_summary_block *sum = curseg->sum_blk;
unsigned int start;
/* 建立物理entry以及内存entry的关系 */
for (start = 0; start < TOTAL_SEGS(sbi); start++) {
struct seg_entry *se = &sit_i->sentries[start]; // 内存entry
struct f2fs_sit_block *sit_blk;
struct f2fs_sit_entry sit;
struct page *page;
int i;
// 先尝试在journal恢复
mutex_lock(&curseg->curseg_mutex);
for (i = 0; i < sits_in_cursum(sum); i++) {
if (le32_to_cpu(segno_in_journal(sum, i)) == start) {
sit = sit_in_journal(sum, i);
mutex_unlock(&curseg->curseg_mutex);
goto got_it;
}
}
mutex_unlock(&curseg->curseg_mutex);
// 如果恢复不了就从SIT恢复
page = get_current_sit_page(sbi, start); // 读取 f2fs_sit_block
sit_blk = (struct f2fs_sit_block *)page_address(page); // 转换为block
sit = sit_blk->entries[SIT_ENTRY_OFFSET(sit_i, start)]; // 物理entry
f2fs_put_page(page, 1);
got_it:
check_block_count(sbi, start, &sit);
seg_info_from_raw_sit(se, &sit); // 将物理entry的数据赋予到内存entry
}
}
init_free_segmap 从内存entry以及checkpoint中恢复free segment的信息:
static void init_free_segmap(struct f2fs_sb_info *sbi)
{
unsigned int start;
int type;
for (start = 0; start < TOTAL_SEGS(sbi); start++) { // 根据segment编号遍历每一个内存entry
struct seg_entry *sentry = get_seg_entry(sbi, start);
if (!sentry->valid_blocks) // 如果这个segment一个block都没有用过,则设置为free
__set_free(sbi, start);
}
/* 从checkpoint的curseg中恢复可用信息 */
for (type = CURSEG_HOT_DATA; type <= CURSEG_COLD_NODE; type++) {
struct curseg_info *curseg_t = CURSEG_I(sbi, type);
__set_test_and_inuse(sbi, curseg_t->segno); // 设置为正在使用的状态
}
}
init_dirty_segmap恢复脏segment的信息:
static void init_dirty_segmap(struct f2fs_sb_info *sbi)
{
struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
struct free_segmap_info *free_i = FREE_I(sbi);
unsigned int segno = 0, offset = 0;
unsigned short valid_blocks;
while (segno < TOTAL_SEGS(sbi)) {
/* find dirty segment based on free segmap */
segno = find_next_inuse(free_i, TOTAL_SEGS(sbi), offset); // 找出所有已经使用过的seg
if (segno >= TOTAL_SEGS(sbi))
break;
offset = segno + 1;
valid_blocks = get_valid_blocks(sbi, segno, 0); // 得到了使用了多少个block
if (valid_blocks >= sbi->blocks_per_seg || !valid_blocks)
continue;
mutex_lock(&dirty_i->seglist_lock);
__locate_dirty_segment(sbi, segno, DIRTY); // 将其设置为dirty
mutex_unlock(&dirty_i->seglist_lock);
}
}
Segment Sunmmary Area ---- SSA结构:
Segment Summary Area,简称SSA,是F2FS用于集中管理物理地址到逻辑地址的映射关系的结构,同时它也具有通过journal缓存sit或者nat的操作用于数据恢复的作用。映射关系的主要作用是当给出一个物理地址的时候,可以通过SSA索引得到对应的逻辑地址,主要应用在GC流程中; SSA所包含的journal可以缓存一些sit或者nat的操作,用于避免频繁的元数据更新,以及宕机时候的数据恢复。
SSA在元数据区域的物理结构
从结构图可以知道,SSA区域默认情况下由N个struct f2fs_summary_block组成,每一个struct f2fs_summary_block包含了512个struct f2fs_summary_entry,刚好对应一个segment。segment里面的每一个block对应一个的struct f2fs_summary_entry,它记录了物理地址到逻辑地址的映射信息。它包含了三个变量: nid(该物理地址是属于哪一个node的),version(用于数据恢复),ofs_in_node(该物理地址属于nid对应的node的第ofs_in_node个block)。
f2fs_journal属于journal的信息,它的作用是减少频繁地对NAT区域以及SIT区域的更新。例如,当系统写压力很大的时候,segment bitmap更新就会很频繁,就会对到SIT章节所提到的struct f2fs_sit_entry结构进行频繁地改动。如果这个时候频繁将新的映射关系写入SIT,就会加重写压力。此时可以将数据先写入到journal中,因此journal的作用就是维护这些经常修改的数据,等待CP被触发的时候才写入磁盘,从而减少写压力。也许这里会有疑问,为什么将journal放在SSA区域而不是NAT区域以及SIT区域呢?这是因为这种存放方式可以减少元数据区域空间的占用。
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;
struct f2fs_sit_block {
struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
} __packed;