F2FS 之 元数据

10 篇文章 0 订阅
5 篇文章 2 订阅

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;
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值