1 Bitmap实现
1.1 bitmap原理
简单的说,bitmap的工作原理就是使用一个位图,每个位对应md设备的一个数据块,在md设备有数据写入时,设置该数据块对应的位,等待写入完成后,再清除该数据块对应的位。
当需要进行数据同步/恢复时,参照bitmap的位图,只有设置了位对应的数据块才需要进行同步/恢复操作。
1.2 核心数据结构
1.2.1 Bitmap_info结构
此结构定义在mddev结构中,其核心部分为:
| 核心成员 | 描述 |
| struct file *file | 描述bitmap file,当指定为md自身时,为NULL |
| loff_t offset | 可以为负,不能为0,描述bitmap文件存放在超级块之前还是之后,为负,就在超级块前 |
| struct default_offset | 当md设备创建后,再指定bitmapfile时会使用 |
| unsigned long chunksize | 存放bitmap chunksize |
| int external | 有cli传入,这里传入internal时,此值为0 |
1.2.2 Bitmap_page结构
| 核心成员 | 描述 |
| void *map | 指向一个page的指针起始位置,一个page中每2B的大小保存一个16位的bimtap_counter描述一个chunk的状态 |
| unsigned in hijacked:1 | 当map申请失败时会被使用 |
| unsigned int count:31 | 描述在这个page中有dirty位的bit个数 |
1.2.3 Bitmap结构
以下列出bitmap结构的核心部分:
| 核心成员 | 描述 |
| struct bitmap_page *bp | 申请为一个数组,每个元素对应一个bitmap内存页 |
| unsigned long pages | 描述bp数组元素的个数 |
| unsigned long chunkshift | 通过bitmap-chunk计算出来的shift值 |
| unsigned long chunks | 底层磁盘大小计算出来的chunk个数 |
| spinlock_t lock | Bitmap的自旋锁,保护filemap 的page |
| struct page *sb_page | 存放md的bitmap的superblock的信息 |
| struct page **filemap | 描述整个bitmap file的信息,以页的形式保存 |
| unsigned long *filemap_attr | 每4bit对应filemap的一页 |
| unsigned long file_pages | 描述bitmap file在filemap中用了多少个page |
| unsigned long flags | 描述bitmap的状态 |
1.2.4 Bitmap_super_t结构
此结构描述超级块的信息,详细信息如下:
| 成员 | 描述 |
| __le32 magic | 0 - 4 宏BITMAP_MAGIC,定义成0x6d746962 |
| __le32 version | 4 - 8 the bitmap major for now, could change... |
| __u8 uuid[16] | 8 - 24 128 bit uuid - must match md device uuid |
| __le64 events | 24 - 32 event counter for the bitmap (1) |
| __le64 events_cleared | 32 - 40 event counter when last bit cleared (2) |
| __le64 sync_size | 40 - 48 the size of the md device's sync range(3) |
| __le32 state | 48 - 52 bitmap state information |
| __le32 chunksize | 52 - 56 the bitmap chunk size in bytes |
| __le32 daemon_sleep | 56 - 60 seconds between disk flushes |
| __le32 write_behind | 60 - 64 number of outstanding write-behind writes |
| __le8 pad[256 - 64] | set to zero |
以上值在创建md设备时,由cli部分进行赋值。在kernel中,是直接通过读取bitmap file对应位置的信息保存在sb_page中,然后通过内存映射,将这些值读出来的。
值得关注的是,event记录md设备的最后一个事件,这个事件被记录在md的超级块的events和mddev结构中events中,在此磁盘加载时,可以比较这两个结构中的events确定bitmap文件是否已经过期。
1.3 Bitmap文件
Bitmap磁盘文件可以存在在md设备之外,单独指定一个磁盘/文件,此时mddev结构中的bitmap_file表示这个bitmap文件。
Bitmap磁盘文件也可以存放在md设备自身,bitmap文件相对于md的超级块的位置由bitmap_offset指定,当这个值为负值时,表示bitmap文件存放在超级块之前。这里假设bitmap_offset大于0,则表示bitmap磁盘文件在超级块之后。
Bitmap文件状态如下,记录在bitmap->flags和bitmap->sb->state中:
1. BITMAP_STALE = 0x002:此状态被设置,表示bitmap file已经过期;
2. BITMAP_WRITE_ERROR = 0x004:此状态在发生写错误时,被设置;
3. BITMAP_HOSTENDIAN = 0x8000:
当bitmap->sb.version == BITMAP_MAJOR_HOSTENDIAN(3)时被设置
下面以bitmap文件存放在md设备自身为例,此时bitmap_file=NULL。
磁盘文件由超级块和位图组成,超级块大小为256(见前面结构bitmap_super_t),随后紧跟位图。位图中,一个位对应md设备中一段条带段,这个大小由超级块sb->chunksize字段确定。位被设置,表示对应的chunk需要同步;位被清除,表示对应chunk状态一致,无需同步。
1.4 Bitmap位图结构
1.4.1 位图结构说明
Bitmap中共有以下两个位图结构:
1. 磁盘位图,md中一级位图,保存在bitmap->filemap数组中,而bitmap->filemap_attr描述每个bitmap->filemap数组元素的状态;
说明:在md设备创建时,整个bitmap磁盘文件会被映射到内存,保存在filemap中,描述底层磁盘大小的位图,在这里1bit对应底层磁盘一个chunk(计算公式后面会提到);其中filemap_attr数组每4bit对应filemap的一页(最小为sizeof(unsigned long))。Bitmap->filemap[0]被保存在bitmap->sb_page中,描述超级块信息。
2. 内存位图,md中二级位图,保存在bitmap->bp数组中,每一项数组元素中的map指针指向一个页,该页保存bitmap的一个内存位图结构(1个page描述2048个chunk);
说明:每个bp数组所描述的一个页(4096B)代表的2048个chunk,均有以下三个状态(状态占用2B 16位),状态保存在bitmap_count_t *bmc中:
a) NEEDED_MASK:1<<(COUNTER_BITS-1)
b) RESYNC_MASK:1<<(COUNTER_BITS-2)
c) COUNTER_MAX:RESYNC_MASK-1
注:COUNTER_BITS 16
1.4.2 位图示例图
1.4.2.1 内存位图
如图:
bitmap->bp内存位图
这里以bitmap_chunksize=512K,mddev->chunk=64K=128sector为例,当前mddev->dev_sector=10485760-2048-128=10483584=blocks,其计算公式为:
1. bitmap->chunkshift=fzz(~bitmap_chunksize)=19;
2. 整个磁盘大小用bitmapchunk表达,可以算出chunk个数和需要的page个数:
a) chunks=(blocks+(bitmap_chunksize*1024>>9)-1)>>(bitmap->chunkshift-9)=(10483584+1024-1)/1024=10238;
说明:将chunksize换算成sector单位,使用此公式就可以算出需要的bitmap chunk个数chunks
b) pages=(chunks + (PAGE_SIZE <<3)/ COUNTER_BITS - 1)/((PAGE_SIZE << 3)/ COUNTER_BITS)=(10238+2048-1)/2048=5;
说明:用一个page(4096B)代表2048个chunk,每个chunk占用2B,2B 16bit长度用于描述每个chunk的状态
一个chunk对应md中的一个条带,在位图中一个chunk占用16bit:
1. 最高一位为是否需要同步标志,标志md对应条带是否需要同步;
2. 后面一位标志指针对应条带是否正在进行同步操作;
3. 低14位是一个计数器,表示对应条带内正在进行写操作的个数,其中0、1、2为特殊中,当为3时表示有1个写操作,4时有2个写操作,以此类推。
如下图所示:
* +--------------+----------+-----------------------------------------+
* | resync | resync | counter |
* | needed | active | |
* | (0-1) | (0-1) | (0-16383) |
* +--------------+----------+-----------------------------------------+
1.4.2.2 磁盘位图
如图:
bitmap->filemap磁盘位图
其计算公式如下:
1. Bitmap->filemap 的page个数计算公式:
Num_pages=((chunks + sizeof(bitmap_super_t)<<3 + 7) / 8) + PAGE_SIZE- 1) / PAGE_SIZE;
这个公式表示一个page的每一个size(0-4095B)代表8个chunks,相当于一个bit代表一个chunk;page指向bitmap->filemap[page_index];chunk的计算方法见内存位图部分。
2. bitmap->filemap_attr:可以用来描述bitmap->filemap[page]的状态,这里为每个page申请了4bit的内存空间,不过此部分计算时,最少申请的长度为unsigned long,filemap的状态有:
BITMAP_PAGE_DIRTY=0; //表示该bit位对应的数据需要往下刷
BITMAP_PAGE_CLEAN=1; //表示该bit位需要清除
BITMAP_PAGE_NEEDWRITE=2;//表示该bit刚刚被清除,需要向下刷
1.5 Bitmap初始化
本部分开始介绍bitmap的初始化,其实现函数为bitmap_create,此函数在md设备创建时,或者指定一个bitmap文件时被调用。主要完成bitmap从磁盘到内存的加载。
Bitmap文件在磁盘中可能有两个位置,分别以文件指针file或者超级块之间的偏移来指定,这里以bitmap文件在md设备内举例说明。
1.5.1 函数bitmap_create
此函数流程如下:
函数说明:
1. 函数bitmap_read_sb:先调用函数read_sb_page,从mddev->disks链表头开始遍历所有的成员盘,从底层设备读取超级块的信息。如果读取成功,则此函数直接返回。
假设创建raid时指定三块成员盘为disk0、disk1、disk2,那么在这里只读了disk2的数据就会返回了。
读取时:
a) bio->bi_sector=rdev->sb_start+offset+index* (PAGE_SIZE/512)=8+8+0=16;
b) bio->bi_size=roundup(sizeof(bitmap_super_t),bdev_logical_block_size(rdev->bdev))=roundup(256+4096)=4096;
c) page->index=0;
数据读取后:
a) 将page的引用计数加1
b) page->private=NULL
c) 将读出来的数据赋值给bitmap->sb_page,并经过内存映射,得到bitmap_super_t *sb;判断sb->events<mddev->events,如果是,说明此bitmap数据已经失效,需要将sb->state标记上BITMAP_STALE。
2. 计算bitmap位图大小(公式见5.4.2.2),得出需要的page个数,然后给bitmap赋值:
bitmap->chunks = chunks; // 把dev_sectors分成chunks个部分,每个大小为bitmap_chunk
bitmap->pages = pages; //计算出来给bp数组申请的page个数
bitmap->counter_bits = COUNTER_BITS = 16
再按照算出来的pages个数,申请bitmap->bp结构。
3. 函数bitmap_init_from_disk:从底层磁盘读取bitmap,下一节详细介绍。
4. 函数bitmap_update_sb:调用函数write_sb_page,将bitmap信息写入每一个active成员盘(raid_disks>=0&&!FAULT)中;
1.5.2 函数bitmap_init_from_disk
此函数从底层磁盘读取bitmap,其传入参数为:
1. Bitmap:bitmap结构
2. Start:在磁盘完好没有降级或者 bitmap没有失效时start=mddev->recovery_cp,否则start=0.
下面为此函数流程:
函数说明:
1. File_page_index计算当前chunk在哪个filemap page中,计算公式为:
(Chunk+(sizeof(bitmap_super_t) << 3)) >> (PAGE_SHIFT + 3)
2. File_page_offset计算当前chunk在该page中的偏移,计算公式为:
(Chunk+(sizeof(bitmap_super_t) << 3)) & ((PAGE_SIZE<<3)- 1)
1.6 Bitmap的设置
Bitmap在md设备处理写访问请求时,调用了函数bitmap_startwrite设置bitmap内存信息,包括内存中的bitmap结构,以及内存中bitmap磁盘文件的映射filemap;在md设备实际将写请求提交给底层设备前,调用函数bitmap_unplug将bitmap信息写入磁盘。
除以上两个函数外,bitmap_start_sync,bitmap_end_sync,bitmap_endwrite等函数也和bitmap的一些标志设置有关。
1.6.1 函数bitmap_startwrite
函数在md设备处理写请求时(make_requestàadd_stripe_bio)调用。函数主要完成:
1. 如果是延迟写,增加延迟写计数;
2. 由于一个写操作的数据长度可能对应在内存位图结构bitmap_page中多个chunk,对于每个这样的chunk,有以下处理(这里传入的大小为STRIPE_SECTORS):
a) 获取该chunk对应的内存结构bp[page],返回bp数组指向的内容*bmc(表示该chunk的状态):调用函数bitmap_get_counter实现,如果找不到对应的bp[page],则申请一个新的page;
b) 如果该bitmap的*bmc达到最大值,说明md设备上对此chunk进行写的次数太多了,磁盘等待了太久,就需要启动设备的队列处理;
c) 如果bitmap上没有正在写操作(*bmc==0),设置此chunk在磁盘位图结构filemap对应page的bit位,再将此page对应的filemap_attr设置为BITMAP_PAGE_DIRTY位,最后设置*bmc=2;如果*bmc==1,也将此值赋值为2;
说明:
i. 设置filemap对应page的bit位,调用函数ext2_set_bit(bit, kaddr)实现
ii. 设置对应page的filemap_attr位,先算出此page对应在filemap_attr中的位置
(page->index<<2),然后将此值加上BITMAP_PAGE_DIRTY得到一个新的值tmp,然后调用__set_bit(tmp,bitmap->filemap_attr)进行位设置。
d) 将*bmc ++,此时此值为3,表示当前chunk写了1次(*bmc为0,表示没有写入操作;为1和2,特殊状态;为3,有1个写入,以此类推);
1.6.2 函数bitmap_unplug
此函数只在md.c中设置bitmap_set_bits接口中 以及 raid5.c中raid5d函数中调用。函数实现功能为检查每个filemap,然后将信息写入底层磁盘。流程如下:
流程说明:
1. 遍历bitmap所有的filemap页,如有标记为脏的页,或者需要写入的页,需要调用函数write_page将该页写入磁盘;
2. 如果有脏页,需要等待mddev->biolist(保存bitmap未处理完成的bio)所有bio处理结束函数才能返回;
3. 函数write_page,实际调用函数write_sb_page处理;此函数会先判断下发的sector是否越界,没有越界,调用函数md_super_write将数据写入底层磁盘,公式如下(成员盘rdev前data_offset=2048存放超级块和bitmap数据):
rdev->sb_start+offset+page->index*(PAGE_SIZE/512)+size/512>rdev->data_offset
4. 如果只有BITMAP_PAGE_NEEDWRITE标记的页,是不需要等待的。因为bit的清除并不是很关键,即使这个信息丢失,最多不过是多余的同步操作而已,没有副作用。而bit设置则需要可靠写入磁盘后方可进行md设备数据的写入,否则在数据写入底层磁盘过程中,bitmap写入底层磁盘前,md设备出现异常,可能导致数据不一致而bitmap不能发觉。
1.6.3 函数bitmap_endwrite
此函数在函数handle_failed_stripe和函数handle_stripe_clean_event中调用,第一个函数只有在raid5设备有两块以及以上成员盘失效时被调用,这里不考虑;第二个函数在hanle_stripe5中写数据完成后调用,会循环所有的成员盘,然后逐个调用此函数。
函数bitmap_endwrite实现功能如下:
1. 调用函数bitmap_get_counter,取出对应chunk在内存位图结构bitmap_page中的状态*bmc;
2. 将*bmc递减,表示一个写入操作完成
3. 检查*bmc值,如果小于3,设置对应filemap页标记BITMAP_PAGE_CLEAN
1.6.4 函数bitmap_start_sync
此函数在md设备需要恢复、同步时调用(函数sync_request中)。
主要调用函数bitmap_get_counter,获取对应sector在bitmap->bp[page].map[pageoff]中的状态bitmap_counter,检测此状态:
1. 如果NEED标志或者RESYNC标志被设置,就认为该数据块需要同步;如果NEED标志被设置,且md设备工作完好,需要清除NEED标志,设置RESYNC标志
2. 如果以上两个标志都没有设置或者sector在bitmap->bp[page].map不存在,则认为该数据块不需要同步。
1.6.5 函数bitmap_end_sync
此函数同样也在函数sync_request中调用。
主要调用函数bitmap_get_counter,获取对应sector在bitmap->bp[page].mp[pageoff]中的状态bitmap_counter,检测此状态:
1. 如果RESYNC标志被设置,清除该标志
2. 如果数据恢复、同步被终止,设置NEED标志
3. 如果bitmap_counter值小于3,表示当前page内对应的sector没有写入操作,设置该page对应的filemap_attr中的标记BITMAP_PAGE_CLEAN,表示该页需要清除bit位。
1.6.6 函数bitmap_store
此函数在sys接口bitmap_set_bits中设置需要恢复的数据段时被调用,此接口支持的输入格式为:echo “0 7-11 50 52-100 ” > /sys/block/mdx/md/bitmap_set_bits,其中“0 7-11 50 52-100 ”数值段代表bitmap_chunk号,md设备共有多少个chunk可以根据mddev->devsectors值和输入的bitmap_chunksize计算得出(参考5.4.2.2);
函数流程为:
1. 获取其中一段数据段,如7-11,chunk=7,end_chunk=11
2. 将chunk的值和end_chunk的值传入函数bitmap_dirty_bits:
a) 从chunk开始,设置该chunk代表磁盘位图filemap中的状态为BITMAP_PAGE_CLEAN,并且为该chunk在内存位图bp中申请一个page,并将内存位图中描述此chunk的状态*bmc设置成NEEDED_MASK
b) 设备该chunk在磁盘位图filemap中对应的bit位,并将其对应在filemap中的page设置为BITMAP_PAGE_DIRTY标记
c) 修改当前恢复位置mddev->recovery_cp为此chunk对应的第一个sector位置
3. 调用函数bitmap_unplug(见5.6.2)
1.7 Bitmap的清除
上一节中所列出的函数,涵盖了全部的bitmap核心函数。上面的流程中只有bit的设置,但没有bit的真正清除。对于可以清除的bit,最多只是设置了filemap对应页的CLEAN标记,没有实际的清除filemap中的bit和写入磁盘操作。
Bitmap的bit清除操作是在bitmap_daemon_work函数,此函数在raid5d中的md_check_recovery函数中调用,而且是一进函数就被调用。因为raid5d是一个守护进程,所以此函数在每次守护进程运行时,都会被调用。
1.7.1 函数bitmap_daemon_work
1.7.1.1 函数实现流程
流程图如下:
图一主流程
图二流程1
图三流程2
图四流程3
图五流程4
函数流程说明:
1. 检查bitmap_daemon_work是否睡眠足够长时间(当前时间-(上次运行时间+5s)),没有睡够就退出了;睡眠一段时间可以将一定时间内的写盘操作集中批量处理;
2. 遍历整个底层磁盘的trunks,获取每个trunk对应在mdev->filemap数组中的page,然后处理如下:
a) 如果该page没有设置BITMAP_PAGE_CLEAN(简称CLEAN)标记,再检查有没有设置BITMAP_PAGE_NEEDWRITE(简称NEED)标记:
i. 没有NEED标记,则跳过此page
ii. 有NEED标记,先清除NEED标记,然后将此page的内容写入底层磁盘(不需要等待bio完全返回),然后跳过此page
b) 如果该page设置了CLEAN标记,清除CLEAN标记;在本次bitmap_daemon_work执行中,清除该page的CLEAN标记只有这一次机会;如果在本次bitmap_daemon_work执行中,再次设置了CLEAN标记,也只能等待下次再执行时被清除了。
c) 获取该chunk对应的内存位图结构bitmap_page(二级位图)中对应map(指向page指针)中的状态*bmc(bitmap_counter),
i. 如果bmc存在,且bmc的状态NEED、RESYNC没有设置(*bmc=2,表明该chunk对应所在的内存结构中的md数据段已经没有写访问),设置*bmc=1,并且将该chunk对应的磁盘位图结构filemap(一级位图)中对应的page设置标记CLEAN;
ii. 如果bmc存在,且*bmc=1,则设置*bmc=0,调用函数bitmap_count_page,减少此chunk对应内存位图结构bitmap_page中的计数count,如果计数count==0,则释放该内存bitmap_page中申请的page;调用函数ext2_clear_bit清除该chunk对应磁盘位图结构filemap中的bit位;
iii. 如果bmc不存在,继续跳转到内存位图结构bitmap_page中下一个map所对应的起始chunk;
d) 当磁盘位图结构中每个page中的chunk处理完成,都会判断该page是否设置了标记BITMAP_PAGE_NEEDWRITE(前提条件是该页的CLEAN标记在处理时被设置,然后刚刚在此流程中清除):
i. 标记被设置,则清除NEED标记,然后将该页写入底层磁盘;
ii. 标记没有被设置,则设置NEED标记;
1.7.1.2 函数实现原理
这个函数逻辑上比较绕,但可以看到真正执行磁盘位图结构filemap中每个chunk清除的工作是在该chunk对应的内存位图结构bitmap_page的*bmc=1后进行的,而写盘操作是在filemap页的状态为NEEDWRITE后进行的。所以要理清逻辑关系,还需要对每个chunk的*bmc的值和filemap的状态变化以及相互的关系做一个详细的了解:
1. *bmc是用来描述每个chunk对应在内存位图结构bitmap_page中的状态,共占16 bit:
a) 最高位为NEED_MASK,表示需要进行数据恢复;
b) 第二位为RESYNC_MASK,表示数据恢复到当前chunk被终止,需要下次从此位置开始接着恢复;
c) 低14位是一个计数器counter,表示当前chunk被写的次数
描述写次数的计数器Counter其值也分0、1、2以及大于2:
a) 0、1、2比较特殊,这三个值都说明该chunk没有写操作进行,真正的写操作是从2开始累加的(bitmap_startwrite中);但是0表示该chunk没有设置,1表示已经设置,2表示所有写操作刚刚结束
b) 从3开始,描述被写的次数,为3表示写1次,一次类推;
2. Filemap页的属性,CLEAN表示该页有bit需要清除;在CLEAN被清除后,会被设置NEEDWRITE属性,再次走进此daemon时,检测到NEEDWRITE标志被设置,会先清除此标记,然后把bitmap数据写入底层磁盘。
1.7.2 函数bitmap_count_page
此函数传入参数说明:
1. struct bitmap *bitmap:bitmap结构;
2. sector_t offset:单位sector;
3. int inc:可以传入1或者-1,用于加/减bitmap->bp[page].count计数,表示此page对应有一个写或者一个写完成。
函数会在内部调用函数bitmap_checkfree,当判断到bitmap->bp[page].count==0时,表示此bitmap->bp[page]已经完全处理,这个时候可以释放掉申请的page空间,bitmap->bp[page]->map=NULL。
注:在写流程中调用函数bitmap_startwrite时会给bitmap->bp[page]->map申请page空间。
1.8 状态变迁图
下面是描述一个chunk对应在一级位图和二级位图中的状态变迁图:
下面再单独说一下*bmc的状态在数据恢复过程中的变化:
本文深入探讨了MD设备中Bitmap的实现原理与工作机制,详细解释了核心数据结构、位图结构及其状态变迁等内容。
1400

被折叠的 条评论
为什么被折叠?



