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_FSstruct 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 */
#endifunsigned 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); spin_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_arrayfor (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读取segnoblk_off = le16_to_cpu(ckpt->cur_data_blkoff[type - CURSEG_HOT_DATA]); // 从CP读取blk_offblk_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找到对应的cursegmutex_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]; // 内存entrystruct 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_blocksit_blk = (struct f2fs_sit_block *)page_address(page); // 转换为blocksit = sit_blk->entries[SIT_ENTRY_OFFSET(sit_i, start)]; // 物理entryf2fs_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编号遍历每一个内存entrystruct 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); // 找出所有已经使用过的segif (segno >= TOTAL_SEGS(sbi))break;offset = segno + 1;valid_blocks = get_valid_blocks(sbi, segno, 0); // 得到了使用了多少个blockif (valid_blocks >= sbi->blocks_per_seg || !valid_blocks)continue;mutex_lock(&dirty_i->seglist_lock);__locate_dirty_segment(sbi, segno, DIRTY); // 将其设置为dirtymutex_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;

F2FS 之 元数据相关推荐

  1. F2FS文件系统架构与原理分析(五)——元数据组织及管理

    from : http://blog.chinaunix.net/uid-28989651-id-3911126.html 1. 元数据区域 元数据区域包含以下几种元数据(参见博文:F2FS文件系统架 ...

  2. F2FS源码分析-1.4 [F2FS 元数据布局部分] Segment Infomation Table-SIT结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  3. F2FS源码分析-1.6 [F2FS 元数据布局部分] Segment Summary Area-SSA结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

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

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  5. F2FS源码分析-1.2 [F2FS 元数据布局部分] Superblock结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  6. f2fs系列文章fsck(五)

    fsck_verify通过前面的检查结果来修正元数据. 首先是对nid的检查情况进行查看,f2fs_fsck中的nat_area_bitmap从开始的读取f2fs_nat_block中的所有的f2fs ...

  7. F2FS 基础知识二

    1.文件索引树结构 大多数现代文件系统都喜欢使用 B-trees 或类似的结构来管理索引(index)以定位文件中的 blocks.大多数文件系统中通过使用"extents"来减少 ...

  8. F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  9. F2FS源码分析-2.3 [F2FS 读写部分] F2FS的一般文件读流程分析

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

最新文章

  1. python怎么做乘法表_python怎么写乘法表
  2. 23种计模式之Python实现(史上最全最通俗易懂)内容整改中
  3. Python综合应用(1)--名片管理系统开发
  4. LeetCode 1471. 数组中的 k 个最强值(排序)
  5. Redhat5下haproxy+keepalived+nginx配置笔记
  6. php zip 归档使用工具,PHP 压缩与归档扩展 Zip_编程学问网
  7. 【华为云技术分享】Linux内核发展史 (3)
  8. 自定义Json解析工具
  9. 枯燥的计算机组成原理课.....!!! 看来只能自己看书消化了...!!!
  10. 数学基础知识总结 —— 4. 常见函数图像
  11. STM32USB虚拟串口驱动 支持win10下载
  12. Java算法——地图单点坐标判断是否存在于某个区域
  13. 8款免费的Windows数字签名软件【附下载】
  14. Android 气泡碰撞
  15. 分三种情况C语言编程,浅谈C语言教学的现状及几点建议.doc
  16. 如何提高领英添加好友邀请通过率的细节和方法技巧
  17. springboot报错(三) webjars被拦截或找不到
  18. 基于人脸的常见表情识别 Task03笔记
  19. SharePoint 2007部署过程
  20. c语言编译器uwp版,Win10技巧:注册UWP版文件资源管理器

热门文章

  1. matlab分段函数
  2. 2017最新版SCI期刊目录发布
  3. 公共关系学试题及答案
  4. 利用CSS3动画让图片动起来
  5. LoRa网关在智慧农业应用
  6. 百度seo,时间因子有用吗?
  7. 多数人不知道但却超好的东东
  8. 已设置了参考编号的编码规则定义,但是新增单据的时候,没有出来自动编号是什么原因?...
  9. sqlserver排名函数
  10. C# Task阻塞方式和延续操作