文件系统实质上是把文件存入磁盘,因而,了解文件系统的磁盘整体布局对于了解一款文件系统十分的重要,文件系统的所有性能都是围绕磁盘布局和对其治理开展的,上面咱们一起看一下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 3s->first_cluster_group += ((1 << c_to_b_bits) - 1); //1<<8 = 256 - 1 = 255 +3 = 258s->first_cluster_group >>= c_to_b_bits;//258>>8 = 1s->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中,它们独特形成该文件的数据内容。

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