在使用raid1,raid5等磁盘阵列的时候,对于数据的可靠性有很高的要求,raid5在写的时候需要计算校验并写入,raid1则写源和镜像来保证数据的一致性,在写的过程中,有可能存在不稳定的因素,比如磁盘损坏,系统故障等,这样导致写入失败,在系统恢复后,raid也需要进行恢复,传统的恢复方式就是全盘扫描计算校验或者全量同步,如果磁盘比较大,那同步恢复的过程会很长,有可能再发生其他故障,这样就会对业务有比较大的影响。以raid1来说,在发生故障时,其实两块盘的数据很多都是已经一致的了,可能只有少部分不一致,所以就没必要进行全盘扫描,但是系统并不知道两块盘哪些数据是一致的,这就需要在某个地方记录哪些是已同步的,为此,就诞生了bitmap,简单来说,bitmap就是记录raid中哪些数据是一致的,哪些是不一致的,这样在raid进行恢复的时候就不用全量同步,而是增量同步了,从而减少了恢复的时间。
1. bitmap的使用
bitmap的使用比较简单,mdadm的帮助文档里有很详细的说明。bitmap分两种,一种是internal,一种是external。
internal bitmap是存放在raid设备的成员盘的superblock附近(可以在之前也可以在之后),而external是单独指定一个文件用来存放bitmap。
这里简单的介绍一下bitmap的使用。
mdadm: /dev/sdb appears to be part of a raid array:
查看md的状态
Personalities : [raid1]
- 其中的4KB chunk表示bitmap的chunk大小是4KB;
- 1/257 pages指的是bitmap所对应的内存位图(作为磁盘上的bitmap的缓存,提高对位图的操作效率),257是内存bitmap占的总page数,1表示已经分配的page数,内存bitmap是动态分配的,使用完后就可以回收。内存位图使用16bit来表征一个chunk,其中的14bit用来统计该chunk上正在进行的写io数(后面会有详细的介绍)。
- [4KB]表示已经分配的内存位图page总大小。
总的chunk数=md设备大小/bitmap的chunk大小
内存bitmap一个page可以表示的chunk数=PAGE_SIZE*8/16
上面给出的例子中总的chunk数为2097216KB/4KB=524304
struct bitmap {struct bitmap_page *bp; /* 指向内存位图页的结构*/……unsigned long chunks; /* 阵列总的chunk数 */……struct file *file; /* bitmap文件 */……struct page **filemap; /* 位图文件的缓存页 */unsigned long *filemap_attr; /* 位图文件缓存页的属性 */……};
struct bitmap_page {char *map; /* 指向实际分配的内存页*//** in emergencies (when map cannot be alloced), hijack the map* pointer and use it as two counters itself*/unsigned int hijacked:1;/** count of dirty bits on the page*/unsigned int count:31; /* 该页上有多少脏的chunk,每16bit表示一个chunk*/};
最高一位表示是否需要同步,后面一位表示是否正在同步,低14bit是counter,用来统计该chunk有多少正在进行的写io。
- 第0bit是BITMAP_PAGE_DIRTY,该bit为1表示内存bitmap中为脏,但是bitmap file中的对应位不为脏,因此对于有这种标记的page需要同步刷到磁盘(实际上是异步调用write_page,但是等到写完成)
- 第1bit是BITMAP_PAGE_PENDING,置位后表示内存bitmap中的脏位已经清0,但是此时外存bitmap file中的对应脏位没有清0,需要进行清0的操作,这是一个过渡状态,过渡到BITMAP_PAGE_NEEDWRITE。
- 第2bit是BITMAP_PAGE_NEEDWRITE,置位后表示需要进行同步,把内存位图缓存中的数据刷到外部位图文件中,所对于这种标记的page只需要异步写,因为即使写失败,最多带来额外的同步,不会带来数据的危害。
- 第3bit在代码中没有看到使用,猜测是预留的。
真正处理BITMAP_PAGE_DIRTY是在bitmap_unplug中,对于raid1来说,bitmap_unplug是在raid1.c中的flush_pending_writes函数中调用的,而flush_pending_writes是由raid1的守护进程raid1d调用的。flush_pending_writes会调用bitmap_unplug刷新bitmap到磁盘,然后遍历conf->pending_bio_list,取出bio来处理正常挂起的写io。(在raid1的make_request中会把mbio加到conf->pending_bio_list中)
if (!test_page_attr(bitmap, page, BITMAP_PAGE_PENDING)) {
int need_write = test_page_attr(bitmap, page,
BITMAP_PAGE_NEEDWRITE);
if (need_write)
clear_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE);spin_unlock_irqrestore(&bitmap->lock, flags);
if (need_write)
write_page(bitmap, page, 0);
spin_lock_irqsave(&bitmap->lock, flags);
j |= (PAGE_BITS – 1);
continue;
}
接着执行后续的,
这里会判断page是否为BITMAP_PAGE_NEEDWRITE,但是这个时候的page不是BITMAP_PAGE_NEEDWRITE,所以进入else的处理,
if (lastpage != NULL) {
if (test_page_attr(bitmap, lastpage,
BITMAP_PAGE_NEEDWRITE)) {
clear_page_attr(bitmap, lastpage,
BITMAP_PAGE_NEEDWRITE);
spin_unlock_irqrestore(&bitmap->lock, flags);
write_page(bitmap, lastpage, 0);
} else {
set_page_attr(bitmap, lastpage,
BITMAP_PAGE_NEEDWRITE);
bitmap->allclean = 0;
spin_unlock_irqrestore(&bitmap->lock, flags);
}
}
继续执行,bmc为2,会把bmc设置为1,并且再设置一次BITMAP_PAGE_PENDING
if (*bmc) {
if (*bmc == 1 && !bitmap->need_sync) {/* we can clear the bit */*bmc = 0;bitmap_count_page(bitmap,(sector_t)j << CHUNK_BLOCK_SHIFT(bitmap),-1);/* clear the bit */paddr = kmap_atomic(page, KM_USER0);if (bitmap->flags & BITMAP_HOSTENDIAN)clear_bit(file_page_offset(bitmap, j),paddr);else__clear_bit_le(file_page_offset(bitmap,j),paddr);kunmap_atomic(paddr, KM_USER0);} else if (*bmc <= 2) {
//进入这里把bmc设置为bmc=1*bmc = 1; /* maybe clear the bit next time */
set_page_attr(bitmap, page, BITMAP_PAGE_PENDING);
bitmap->allclean = 0;
}
这样就会走到下面的流程,把BITMAP_PAGE_PENDING清掉
if (*bmc == 1 && !bitmap->need_sync) {
/* we can clear the bit */*bmc = 0;bitmap_count_page(bitmap,(sector_t)j << CHUNK_BLOCK_SHIFT(bitmap),-1);/* clear the bit */
// 这里才是真正的位图文件缓存页bit位清0的地方paddr = kmap_atomic(page, KM_USER0);if (bitmap->flags & BITMAP_HOSTENDIAN)clear_bit(file_page_offset(bitmap, j),paddr);else__clear_bit_le(file_page_offset(bitmap,j),paddr);kunmap_atomic(paddr, KM_USER0);}
会走到下面的流程,清掉BITMAP_PAGE_NEEDWRITE,然后调用write_page刷到磁盘中,至此,清理操作才完成,
if (!test_page_attr(bitmap, page, BITMAP_PAGE_PENDING)) { int need_write = test_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE); if (need_write) clear_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE);spin_unlock_irqrestore(&bitmap->lock, flags);
if (need_write)
write_page(bitmap, page, 0);
spin_lock_irqsave(&bitmap->lock, flags);
j |= (PAGE_BITS – 1);
continue;
}