关于c:01-OCFS2文件系统磁盘布局分析

2次阅读

共计 8337 个字符,预计需要花费 21 分钟才能阅读完成。

文件系统实质上是把文件存入磁盘,因而,了解文件系统的磁盘整体布局对于了解一款文件系统十分的重要,文件系统的所有性能都是围绕磁盘布局和对其治理开展的,上面咱们一起看一下 OCFS2 集群文件系统的磁盘整体布局。
一、格式化 format:
mkfs.ocfs2 -b 4K -C 1M -N 32 -J size=128M -L “xxx” -T vmstore –fs-features=indexed-dirs,refcount –cluster-stack=o2cb –cluster-name=ocfs2 fs.img

格式化参数解释:

-b, –block-size block-size 文件系统执行 IO 的最小单元,本例取 block 4KB=4096=2^10
-C, –cluster-size cluster-size 为文件数据调配空间的最小单元,本例取 cluster 1MB=2^20
-N, –node-slots number-of-node-slots 节点槽位,指向一系列的系统文件,每个槽位被一个节点惟一应用。槽位的个数示意该 volume 可同时被多少个节点 mount。
-J, –journal-options options OCFS2 采纳预写日志 WAL(Write-Ahead Log),应用了 JBD2,用户可配置,依据文件系统类型和卷大小决定,默认值:datafiles 类型 64MB,vmstore 类型 128MB,mail 类型 256MB
-L, –label volume-label 对卷做标签,为了方便管理。
-T filesystem-type Valid types are mail, datafiles and vmstore.
mail:用作邮件服务器存储,将大量的元数据变为大量的小文件,应用大一点的日志 journal 比拟无益。
datafiles:倡议较少地全副调配大文件,要求较少的元数据扭转,因而,应用大一点的日志 journal 有益。
vmstore:正如名字所示,被稠密调配大文件的虚拟机镜像
–fs-features=[no]sparse…  指定使能或禁用某些个性,比方:稠密文件、非写入范畴、备份超级块
–fs-feature-level=feature-level Valid values are max-compat, default and max-features. 默认反对稠密文件、非写入范畴、内联数据。

查看根目录和系统目录
通过 debugfs.ocfs2,能够看到系统文件,本例格式化指定 -N 32,因而,一些带 slot 号的系统文件,如:local_allock:0000~0031,如下截图仅显示局部,其余已将其省略,不影响剖析。

通过格式化的参数,能够失去如下根底数据信息,这些信息能够不便的进行计算。

 1 block 4KB = 4096 = 2^10
 1 cluster 1MB = 2^20
 1 cluster = 256 blocks = 2^8

二、SuperBlock
对于一个文件系统而言 SuperBlock 是最重要的,它形容了一个文件系统的根本数据信息,含有许多重要的要害信息,文件系统被 mount 操作后,首先要读取 SuperBlock。格式化后,能够看到 SuperBlock 的信息如下:

其中,有一个要害的数据参数来自格式化命令行,如:Block Size Bits: 12、Cluster Size Bits: 20 等,而 SuperBlock Blkno 是要提前规定好的,前面的一些要害信息都是由此 SuperBlock Blkno 计算而来,比方:First Cluster Group Blknum: 256,进而计算出 Root Blknum: 513、System Dir Blknum: 514。
> #define OCFS2_SUPER_BLOCK_BLKNO 2

SuperBlock 在磁盘上的数据结构如下:

格式化最重要的就是规定好磁盘如何划分和布局,因而,须要先划分簇组 Cluster Groups。

三、上面剖析 OCFS2 磁盘的整体布局:
OCFS2 磁盘的整体布局次要形容如何实现对整个磁盘的治理,OCFS2 将磁盘划分为若干个 Cluster Group 进行治理,称为簇组(Cluster Group),在 Ext4 文件系统中叫块组(Block Group),用处都是一样的。如下图所示:

每个 Cluster Group 都由若干个 clusters 组成,蕴含对 Cluster Group 的形容信息 Descriptor+BitMap、Data Clusters。第一个 Cluster Group 比拟非凡,因为它含有 OCFS2 的元数据信息,蕴含 SuperBlock、零碎文件夹和系统文件。
Cluster Group 内磁盘空间的治理由一个形容簇组信息的 Block 来体现,由该 Block 残余空间用位图 BitMap 来治理整个簇组 Cluster Group。位图 BitMap 中的每一位 Bit 与该 Cluster Group 中的一个 cluster 对应,如果第 i 个 cluster 对应的 Bit 位在 BitMap 中为 1,示意该 cluster 曾经被调配进来,0 则示意没有被应用,OCFS2 通过位图 BitMap 来确认哪些 clusters 能够应用(本例中 1cluster=1MB)。

系统文件 global_bitmap
用来形容 Cluster Groups 信息的系统文件是 global_bitmap(global_bitmap 全局位图,用于实现对 cluster group 的全局治理,拜访时须要通过分布式锁进行爱护。)系统文件的磁盘数据结构是 struct ocfs2_dinode,如下图所示:

其中用来形容 Cluster Group 信息的是 struct ocfs2_dinode.id2.ocfs2_chain_list.ocfs2_chain_rec,如下图所示:

其中 chain_list 就是 Cluster Groups List,该磁盘数据结构记录了整个磁盘分为几个 Cluster Groups。每个 Cluster Group Chain 用 ocfs2_chain_rec 来形容,ocfs2_chain_rec.c_blkno 就是形容每个 Cluster Group 的 Block 块位图(BitMap),磁盘数据结构是 struct ocfs2_group_desc,用 1 个 Block 来示意,如下所示:

从 0x00~0x40 = 64B,1Block 4096B – 64B = 余 4032 B * 8 bits = 32256 bits,因而,一个 ocfs2_group_desc 可示意 32256 个 clusters,所以每个 cluster group 外面 cluster 数目,即:1 个 cluster group 含 32256 clusters = 32256MB = 31.5GB。一块磁盘 Disk 或卷 Volume 可用 cluster groups 的总数 = 总 clusters / 每个 cluster group 含 cluster 数目,最初一个 cluster group 里蕴含残余的 cluster 数目。本例中,80G Volume = 81920 clusters,(81920 clusters + 32256 clusters – 1)/ 32256 clusters = 3 cluster groups。
下图中显示了在本例中全局位图 global_bitmap 记录的 Cluster Groups 信息:一共有 3 个 cluster group,Group Chain: 0、1、2

图中的 blkno 256 / blkno 8257536 / blkno 16515072,每个 blkno 都是一个 struct ocfs2_group_desc,用来表述本 cluster group 的 bitmap 信息。

簇组描述符:ocfs2_group_desc
格式化信息中也显示 First Cluster Group Blknum: 256,那么 blkno 256 是怎么来的呢?blkno 8257536、blkno 16515072 又是怎么来的?接下来一步一步剖析。
首先,格式化首先确定 global_bitmap 的构造,重点是分 cluster group 和确定每个 cluster group 的 descriptor block。
依据 superblock blkno 2 计算出 first_cluster_group_blkno = blkno 256,而剩下的 cluster group N 都是每个 cluster group 的第一个 block。而 first cluster group 不是第一个 block 是因为 blkno 0, 1 被预留了,用来示意 ocfs version2,而 blkno 2 用来示意 SuperBlock,所以抉择了 global_bitmap chain 0 中下标是 1 的 cluster 的第 1 个 block 256。
如下截取要害代码所示:

s->global_bm = initialize_bitmap (s, s->volume_size_in_clusters,// 初始化全局 global_bitmap
 s->cluster_size_bits,//20 bits
 "global bitmap", tmprec);
 
/* to the next aligned cluster */
s->first_cluster_group = (OCFS2_SUPER_BLOCK_BLKNO + 1);//Block 3
s->first_cluster_group += ((1 << c_to_b_bits) - 1); //1<<8 = 256 - 1 = 255 +3 = 258
s->first_cluster_group >>= c_to_b_bits;//258>>8 = 1
s->first_cluster_group_blkno = (uint64_t)s->first_cluster_group << c_to_b_bits;// 1<<8 = Block 256
 
 chain = 1;
 blkno = (uint64_t) s->global_cpg << (s->cluster_size_bits - s->blocksize_bits);// 确定 chain 1 的 group descriptor blkno 32256 << 8 = blkno 8257536
 cpg = s->global_cpg;
 
 blkno += (uint64_t) s->global_cpg << (s->cluster_size_bits - s->blocksize_bits);// 确定下一个 chain 的 group descriptor blkno 16515072 = blkno 8257536 + 32256 << 8 = blkno 8257536 * 2
 chain++;// 以此类推

因为 Cluster Group 0 比拟非凡,因为这里记录了 SuperBlock 和系统文件,那么此时,须要指出的是在 Cluster Group 0 的位图 global_bitmap 中,哪些 bits 被占用了呢?后面已剖析这里的 1bit = 1cluster = 1MB = 256 blocks。SupberBlock blkno 2 在 global_bitmap 的第 0 位 cluster 中,所以 global_bitmap 第 0 位被置为 1,First Cluster Group Blkno 256 在 global_bitmap 的第 1 位 cluster 中,所以 global_bitmap 第 1 位被置为 1。

系统文件 inode
接下来剖析根目录 root_dir(513)、系统目录 system_dir(514)、各个系统文件的 inode 号是如何调配的。
首先确定系统文件从哪里开始申请 inode,首先要做的一件事就是初始化 global_inode_alloc 系统文件,如下图所示:

先从 global_bitmap 中申请总的系统文件所用的 Blocks 总数目,本例须要 203 个,通过如下代码算得须要 2bits,

num_bits = (bytes + bitmap->unit - 1) >> bitmap->unit_bits;//(need bytes + 1MB - 1) >> 20 = need cluster bits: (831488+1,048,576‬-1)/1,048,576‬ 约 =1.79 = 2bits

所以从 global_bitmap 中申请 2 个可用 bit 位,group chain 0 中 bits 可满足,又因为第 0,1 个 bit 的 cluster 曾经被 superblock 2 和 group descriptor blkno 256 所在的 cluster 位设置了,所以取得的是第 2,3 个 bit 位 cluster,即:2MB 地位处开始,这就是 blkno 512。如下截取的局部要害代码:

 /*
     * Now allocate the global inode alloc group
     */
 tmprec = &(record[GLOBAL_INODE_ALLOC_SYSTEM_INODE][0]);
 need = blocks_needed(s);// 计算目录和系统目录及系统文件总共须要的 Block 数目:203
 alloc_bytes_from_bitmap(s, need << s->blocksize_bits, // 从 global_bitmap 中申请这些 Block 数目 <<12 = need bytes
                                s->global_bm, // 当初 global_bitmap 的 group0 的 0,1/group1 的 0 /group2 的 0 位被占
                                &(crap_rec.extent_off), // 出参:从偏移 offset 2MB 处,即:blkno 512
                                &(crap_rec.extent_len)); // 出参:长度 2MB 大小可用于零碎 inode
 s->system_group =initialize_alloc_group(s, "system inode group", tmprec,
                           crap_rec.extent_off >> s->blocksize_bits, // 偏移 offset 2MB 处,即:blkno 512
                           0,crap_rec.extent_len >> s->cluster_size_bits, //2MB >> 20 = 2, 1 cluster = 256 bits, 2 clusters = 512bits
                           s->cluster_size / s->blocksize); //256, system_group 外面 1 bit = 1 block

接下来用这 2cluster=512bits,为系统文件 inode 生成一个子分配器 global_inode_alloc,用来为系统文件调配 inode,该磁盘数据结构也复用了 ocfs2_group_chain_list,依然是用到了位图 bitmap,这个位图 bitmap 是由 blkno 512 来形容的,每个 group chain 蕴含 2 clusters,256bits 示意一个 cluster,所以 1bit 示意一个 block 大小。blkno 512 是第 0 位 bit,随后申请到的系统文件 inode 都是以 blkno 512 位基准偏移,比方:根目录(root_dir)申请到第 1 位 bit 的系统文件就是 blkno 512 +1 = blkno 513,Sub Alloc Bit: 1。
申请到第 i 位 bit 的系统文件就是:blkno 512 +i = blkno (512+i),Sub Alloc Bit: i,i > 0
如下截取的局部要害代码为根目录和残余每个系统文件申请 inode:

static uint64_t
alloc_inode(State s, uint16_t suballoc_bit)
{

 uint64_t ret;
 uint16_t num;
 alloc_from_group(s, 1, s->system_group, &ret, &num);// 从 system_group 外面申请 1 bit, blkno 512 开始,共 256 bits,第 0 位 bit 曾经被 gb_blkno512 占了,所以这里申请到第 1 位 bit,即:blkno 513.
 *suballoc_bit = (int)(ret - s->system_group->gd->bg_blkno);//blkno 513 - blkno 512 = 1 suballoc_bit
 /* Did I mention I hate this code? */
 return (ret << s->blocksize_bits);// 返回 blkno 513 的字节处

}

root_dir_rec.fe_off = alloc_inode(s, &root_dir_rec.suballoc_bit);
root_dir->record = &root_dir_rec;
add_entry_to_directory(s, root_dir, “.”, root_dir_rec.fe_off, OCFS2_FT_DIR);// 所以根目录 inode blkno 513
add_entry_to_directory(s, root_dir, “..”, root_dir_rec.fe_off, OCFS2_FT_DIR);

for (i = 0; i < NUM_SYSTEM_INODES; i++) {// 为每个系统文件申请 inode

 if (hb_dev_skip(s, i))
     continue;
 if (feature_skip(s, i))
     continue;
 num = (system_files[i].global) ? 1 : s->initial_slots;
 for (j = 0; j < num; j++) {record[i][j].fe_off = alloc_inode(s, &(record[i][j].suballoc_bit));// 为每个带槽位号的系统文件申请 inode
     sprintf(fname, system_files[i].name, j);
     add_entry_to_directory(s, system_dir, fname,
                            record[i][j].fe_off,
                            S_ISDIR(system_files[i].mode) ? OCFS2_FT_DIR : OCFS2_FT_REG_FILE);
    }
}

如下图所示是根目录 root_dir 的 inode 信息:

其中能够看到内联数据:Inline Data。

Inline 模式:
如下图所示。即:1 个 block 除了 inode 形容信息外剩下的空间,由 struct ocfs2_dinode 可知,0xC0 之后就是 inline data,所以 inline data = 1block(4096 B) – 0xC0(192 B) – 0x08(8 B)= 3896 B。当数据小于 3896 B 时,数据会存在 inode 的 Inline Data 中;当数据大于 3896 B 时,则会通过 extent 的形式存储。

extent 模式:
当 inode 存储的数据大于内联空间 3896 B 时,就会应用 extent 模式来存储数据。很多文件是采纳了 extent 模式存储数据。extent 模式采纳 B + 树的形式存储数据,B+ 树是经典的存储数据的形式,Mysql 的存储引擎 InnoDB 也是用的 B + 树存储数据。
在 struct ocfs2_dinode 磁盘数据结构 0xC0 之后有 struct ocfs2_extent_list 和 ocfs2_extent_rec,这两个磁盘数据结构独特形容了该 inode 上面数据在 extent 模式下的散布状况。如下所示的 extent 磁盘数据结构:

其中,l_tree_depth 示意树深,该值为 0,示意该树节点是叶子节点,即:e_blkno 这个 block 块是用来存储数据的;如果该值非 0,示意该节点是非叶子节点,不是用来存储数据的,而是用来记录下一个存储地位的。l_count 示意 extent records 的数目,下图所示 count: 243 = (1block(4096 B) – 0xC0(192 B) – 0x10(16 B) ) / (16 B)= 3888 B / 16 B = 243。

如下图所示的两个系统文件 heartbeat 心跳文件、日志 journal:0000 文件都只占了一个 extent record,心跳系统文件 heartbeat 的 extent record 显示:数据存储从 block 1280 开始,占了 1 cluster(1MB)的大小。因为数据只占了 1cluster,所以在文件中的偏移(以 clusters 计)offset = 0。日志 journal:0000 系统文件从 block 1536 开始占了间断的 128 clusters,该 extent record 在文件中的偏移也是 offset=0。如果文件调配的 extent 空间是不间断的,则会呈现多个 extent records 记录,每一个 extent 片段信息(offset/clusters/blkno)都会显示在 extent records 中,它们独特形成该文件的数据内容。

总结
综上所述,本例中格式化之后的磁盘布局如下图所示:

正文完
 0