soft raid5阅读笔记之七--MD中的bitmap(网上资料)

在RAID1,RAID5等存在数据冗余的磁阵结构中,数据的完整性要求冗余的数据和原始数据的信息要保证一致。磁阵的写操作是一个过程,这个过程中发生异常,可能导致冗余数据和原始数据不一致,例如磁盘更换,写盘失败,系统掉电等。
    在系统要尽快使数据恢复一致,以使得磁阵后续可以正常工作,磁阵从数据不一致状态恢复的过程就是同步。
假设系统重新上电后,如何知道哪些数据出于不一致状态呢?如果没有其他信息的话,它只能假定所有数据都可能不一致,进行所有数据块的一致性恢复。对于RAID5,就是重新计算所有条带的效验和;对于RAID1,就是重新同步所有数据块到镜像盘。这种全局数据同步会导致系统开机过程及其漫长(或者就整个放任磁阵上电后处于不一致的状态)。
    专业的磁阵中,一般都会有NVRAM(非易失性内存),在对一个数据块写操作开始的时候,在NVRAM中保存写位置信息,写操作结束后,在NVRAM中删除这个信息。这样,在系统开机时,从NARAM中读出保存的写位置信息,这些位置信息记录了未成功写入的数据块,系统只需要对这些数据块进行同步即可,而不用同步磁阵全部数据。
      Bitmap的工作原理说来也是直截了当的,文件的内容就是一个位图,每个位对应磁阵的一个数据块(粒度可以做得比块更粗些),在磁阵数据写入前,设置该数据块对应的位,磁阵写入完成,则清除该数据块对应位。要进行同步时,参照bitmap,只有设置了的位对应的数据块才需要进行同步,是不是很简单?

     bitmap原理很明了,按照这个原理直接进行实施也是可以的,但直接这样实施的话,由于一次数据块的写入多了两次磁盘访问(bitmap的设置和清除),写入效率会受到较大影响,所以还需要考虑一些优化。

     优化主要是两方面的:bitmap的设置后批量写入;bitmap的延时清除。这两方面的优化原理上和磁盘缓存差不多,需要在内存中构建和磁盘bitmap文件对应的数据结构,bitmap操作首先在缓存中进行,必要时才进行真正的磁盘操作。
     下面来看看bitmap在磁盘上以及在内存中的结构。
     Bitmap文件的磁盘组织
     Bitmap磁盘文件可以存放在MD设备之外,此时MD结构中的bitmap_file表示这个bitmap文件;bitmap磁盘文件也可以存放在MD设备自身,这个bitmap相对于MD的超级块的位置由bitmap_offset指定(注意这个值也可以为负,表示Bitmap文件存放在超级块之前)。
磁盘文件由超级块和位图组成,超级块大小为256字节,随后紧跟位图。位图中,一个位对应磁阵中一段条带段,这个大小由超级块的chunksize字段确定,位被设置则表示该对应chunk需要同步,位被清零表示对应chunk状态一致,无需同步。
超级块

typedef struct bitmap_super_s {
       __le32 magic;        /*  0  BITMAP_MAGIC */
       __le32 version;      /*  4  the bitmap major for now, could change... */
       __u8  uuid[16];      /*  8  128 bit uuid - must match md device uuid */
       __le64 events;       /* 24  event counter for the bitmap (1)*/
       __le64 events_cleared;/*32  event counter when last bit cleared (2) */
       __le64 sync_size;    /* 40  the size of the md device's sync range(3) */
       __le32 state;        /* 48  bitmap state information */
       __le32 chunksize;    /* 52  the bitmap chunk size in bytes */
       __le32 daemon_sleep; /* 56  seconds between disk flushes */
       __le32 write_behind; /* 60  number of outstanding write-behind writes */
       __u8  pad[256 - 64]; /* set to zero */
} bitmap_super_t;


     值得关注的,event记录对应MD设备的最近一个事件,这个事件也记录在MD的超级块中,在从磁盘加载时,可以通过和MD结构中的event比对来确认bitmap文件是否已经过期。如果过期,则将bitmap内的位全部设置为1。
     Bitmap的内存结构
     Bitmap内存中结构比磁盘文件要复杂得多,首先来看主要部分:
     设备工作时,bitmap文件超级块也读入内存,保存在内存结构的sb_page中。
     设备工作时,整个bitmap磁盘文件被映射到内存,保存在内存结构的filemap中,对应filemap,bitmap内存结构有filemap_attr数组,每4个bit对应filemap的一页。
     Bp数组的每一个元素对应一个bitmap内存页,元素中的map指针指向存放内存bitmap的内存页,内存bitmap页保存bitmap内存结构。
     内存中一个bitmap结构占用16bit,一个bitmap对应磁阵的一个条带。最高一位为是否需要同步标志,标志磁阵对应的条带是否需要同步;后面一位标志指针对应条带是否正在进行同步操作。低14位是一个计数器,表示对应磁阵条带段内正在进行的写操作个数。
* +--------+--------+------------------------------------------------+
* | resync | resync |               counter                          |
* | needed | active |                                                |
* |  (0-1) |  (0-1) |              (0-16383)                         |
* +--------+--------+------------------------------------------------+

 bitmap_create完成bitmap的初始化,该函数在磁阵启动时或者用户设置bitmap文件时被调用,主要完成bitmap从磁盘到内存的加载:

1. 前面说过,bitmap文件在磁盘上可能有两个位置,分别以文件指针或者与超级块之间的偏移来指定。
2.读入超级块,注意bitmap是以小尾格式存在磁盘上的。在读入过程中,判断bitmap是否过期(超级块的event和MD的event是否相同),并根据判断设置bitmap过期状态。
3.计算出bitmap在磁盘上的位图大小(以页为单位),并根据这个大小申请bp数组。
4.如果bitmap未过期而且磁阵完好,可以从MD当前的同步位置开始读入bitmap,而之前的由于已经同步完成,在bitmap中加以忽略。
5.调用bitmap_init_from_disk从磁盘中读入bitmap,该函数完成如下功能:
a) 申请filemap空间,大小和bitmap磁盘数据相同,用于保存从磁盘中读取的信息
b) 申请filemap_attr空间,每4个bit对应bitmap磁盘文件的一页
c)  Bitmap磁盘文件的每bit,对应磁阵中的一段数据块,对于bitmap每一位,执行下面操作:
     i. 找到该bit对应磁盘文件所在页,以及页中的偏移量
    ii. 如果该bit是新的页,则读入该页;如果bitmap过期,则设置该页的所有位(如果是第一页,当然要跳过超级块),并回写到磁盘上
   iii.如果该bit为1,则设置其对应的bitmap内存结构,具体是设置其NEED位,表明磁阵对应数据段需要同步。设置所在页有需要清除bit的标志(BITMAP_PAGE_CLEAN),这个标志我们后面讲到
d) 读入完成后,清除bitmap的过期标志
e) 如果bitmap有位被设置,说明此时有同步的需要,则设置需要同步标志,唤醒守护进程

   6. 根据bitmap信息设置守护进程的唤醒间隔时间。写入bitmap的超级块。因为可能是此时bitmap记录的事件等需要更新。    

Bitmap在磁阵处理写访问请求时,调用bitmap_startwrite设置bitmap内存的信息,包括内存中的bitmap结构,以及内存中bitmap磁盘文件的映射filemap;在磁阵实际将写请求实际提交给子设备前,调用bitmap_unplug将bitmap信息写入磁盘。
除这两个函数外,bitmap_start_sync,bitmap_end_sync,bitmap_endwrite等函数也和bitmap的一些标志设置有关。
bitmap_startwrite
该函数在磁阵处理访问请求时(make_request)调用。函数主要完成:
1. 如果是延迟写,则增加延迟写计数
2. 由于一个写操作涉及的数据段可能对应多个bitmap,对于每个这样的bitmap:
a)       获取该bitmap的内存结构,即bp数组指向的内容
b)       如果该bitmap的COUNTER达到最大值,说明磁阵上该数据区正在进行的写访问已经太多,磁阵等待太久,此时需要启动设备的队列处理。
c)       如果bitmap上没有正在进行的写操作(COUNT=0),设置filemap的对应bit。将内存bitmap结构COUNT直接设置为2。
d)       Bitmap内存结构COUNT累加。(也就是写操作计数为0时,表示没有写入操作;为1,2都是特殊状态,3表示有一个写入,依次类推)
bitmap_unplug
遍历bitmap的所有filemap页,如有标记为脏的页(BITMAP_PAGE_DIRTY ,bit置位时设置),或者为需要写入的(BITMAP_PAGE_NEEDWRITE ,bit清除时设置),则将该页写入磁盘。
如果有脏页,则等待写入结束后返回。
注意这里如果只有BITMAP_PAGE_NEEDWRITE标记的页,是不需要等待的,因为bit的清除并不是很关键,即使这个信息丢失,最多不过是多余的同步操作而已,没有副作用。而bit设置则需要可靠写入磁盘后方可进行磁阵条带的写入,否则在条带数据写入磁盘过程中,bitmap写入磁盘前,磁阵出现异常,则可能导致数据不一致而bitmap不能发觉。
bitmap_start_sync
在磁阵同步操作开始时,调用该函数。
这个函数获取bitmap的内存结构,如果NEED标志或者RESYNC标志被设置,就认为该数据块需要同步,此时如果磁阵工作完好,则清除NEED标志,设置RESYNC标志;如果NEED和RESYNC都没有设置,则认为该数据块不需要同步。
bitmap_end_sync
磁阵同步完成后,调用该函数。
这个函数获取bitmap的内存结构,如果RESYNC标志被设置,则清除该标志。如果同步是失败的,则设置NEED位。如果COUNT小于3,则设置该页file_attr的BITMAP_PAGE_CLEAN(下文简写为CLEAN),表示该页存在需要清除的bit位。
bitmap_endwrite
写操作完成后,取出对应数据段的bitmap内存结构,COUNT递减,如果COUNT小于3,则设置对应filemap页的CLEAN属性。
    如果写失败,则设置对应bitmap的NEED标志。

     在前面描述的设置流程中,涵盖了全部的bitmap用户能够看到的流程,这些流程中只有bit的设置,但没有bit的清除。对于可以清除的bit,最多只是设置了所在的filemap页CLEAN属性,没有实际的清除filemap中的bit和写入磁盘。
Bitmap的bit清除工作是在bitmap_daemon_work函数中进行的,这个函数又在md_check_recovery中被调用,而md_check_recovery是在磁阵的守护进程中首先被调用的,所以实际上守护进程每次运行时,都要调用到bitmap_daemon_work。
这个函数完成这样一些工作:
1.      如果bitmap_daemon_work没有睡够,则直接返回。这个睡眠是为了将一定时间内的写盘操作集中批量处理。
2.      对于每个bit,取出其对应的filemap页:
     i. 如果该页没有设置CLEAN和BITMAP_PAGE_NEEDWRITE(下文简写为NEEDWRITE)属性,则跳过该bit。如果没有设置CLEAN,而设置了NEEDWRITE,则将该filemap页写入磁盘,清除该页的NEEDWRITE标志,结束本bit的处理。
    ii.  如果该页设置了CLEAN标记,则清除该页的CLEAN标记。本次bitmap_daemon_work执行中,清除该页的CLEAN标记只有这一次的机会,如果在iv中又一次设置了该页的CLEAN标志,则只能等到下一次函数调用才会再清除它了。
   iii. 如果页中的所有bit都处理结束后,如果该页的CLEAN标记被清除过(如果没有清除过则不会记入oldpage中的),判断页的NEEDWRITE被设置,则清除该标志,将该页写入磁盘;否则设置NEEDWRITE标志,结束该页的处理。
   iv.获取该bit的内存结构,(如果NEED,RESYNC都没有设置,)COUNT为2,表明该bit对应的磁阵数据段已经没有写访问,设COUNT等于1,设置该bit对应的filemap页CLEAN属性;如果COUNT为1,设置COUNT等于0,清除filemap中对应bit。继续下一bit的处理。

这个函数的逻辑比较绕,但我们可以看到真正执行filemap中bit清除的工作是在该bit对应的内存结构的COUNT为1后进行的,而写盘操作是在filemap页的状态为NEEDWRITE后进行的。所以要理清逻辑关系,还需要对bit的COUNT和filemap的状态变化以及相互的关系做一个详细的了解:
1.  COUNT是bit对应的磁阵数据段上正在进行的写操作的计数器,但0,1,2三个值比较特殊,这三个值都说明该数据段上没有写操作进行,真正的写操作数是从2开始累加的(bitmap_startwrite)。但0表示bit没有设置,1表示bit已经设置,2表示所有写操作刚刚结束。在清除阶段,COUNT为2,则设置所在filemap页的CLEAN属性,表示该页有bit需要清除。
2.  filemap页的属性,CLEAN表示该页有bit需要清除;程序逻辑里,在CLEAN清除后,会设置页的NEEDWRITE标志。在NEEDWRITE标志设置后的下一次调用,则会把bitmap写入磁盘。
3.为简化分析,可以以一个bit,一个页来进行分析:
    a) 在磁阵对应内存段上写操作全部结束,bit对应的内存结构的COUNT为2时,此时设置filemap页CLEAN属性。
    b)  bitmap_daemon_work启动后,发现该页属性为CLEAN,则清除CLEAN标记。清除后,看到COUNT为2,又再次设置这个CLEAN标志,COUNT等于1。由于已经清除过一次CLEAN,所以filemap的页的NEEDWRITE标志被设置。对于这个bit和页来说,本次bitmap_daemon_work调用结束。
    c) 下一次再次调用到bitmap_daemon_work后,由于CLEAN标记被设置,所以这次再被清除。由于COUNT为1,所以设置COUNT等于0,清除filemap页bit位,并且filemap也的NEEDWRITE属性被设置,所以进行了写盘。
4.从流程分析看,这个逻辑存在一些缺陷,还是以一个bit的变化为例,如果在步骤b,c之间,该bit对应的数据段又发生了写操作,则c将不会清除bit位,此时的写盘也就没有必要。
5.  再者,CLEAN标记被清除一次后,就会设置NEEDWRITE标志,filemap中其他bit也存在再次设置CLEAN的可能。而这个再次设置显得混乱了,感觉应该在该页的所有bit处理完成后,再对页的CLEAN标记进行清除。
6. 另外,在bitmap_endwrite函数中,如果所有写请求完成后,COUNT已经为2,bitmap_endwrite函数也设置了filemap的CLEAN属性,和bitmap_daemon_work判断COUNT为2时再次设置CLEAN存在重复。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本系统的研发具有重大的意义,在安全性方面,用户使用浏览器访问网站时,采用注册和密码等相关的保护措施,提高系统的可靠性,维护用户的个人信息和财产的安全。在方便性方面,促进了校园失物招领网站的信息化建设,极大的方便了相关的工作人员对校园失物招领网站信息进行管理。 本系统主要通过使用Java语言编码设计系统功能,MySQL数据库管理数据,AJAX技术设计简洁的、友好的网址页面,然后在IDEA开发平台,编写相关的Java代码文件,接着通过连接语言完成与数据库的搭建工作,再通过平台提供的Tomcat插件完成信息的交互,最后在浏览器打开系统网址便可使用本系统。本系统的使用角色可以被分为用户和管理员,用户具有注册、查看信息、留言信息等功能,管理员具有修改用户信息,发布寻物启事等功能。 管理员可以选择任一浏览器打开网址,输入信息无误后,以管理员的身份行使相关的管理权限。管理员可以通过选择失物招领管理,管理相关的失物招领信息记录,比如进行查看失物招领信息标题,修改失物招领信息来源等操作。管理员可以通过选择公告管理,管理相关的公告信息记录,比如进行查看公告详情,删除错误的公告信息,发布公告等操作。管理员可以通过选择公告类型管理,管理相关的公告类型信息,比如查看所有公告类型,删除无用公告类型,修改公告类型,添加公告类型等操作。寻物启事管理页面,此页面提供给管理员的功能有:新增寻物启事,修改寻物启事,删除寻物启事。物品类型管理页面,此页面提供给管理员的功能有:新增物品类型,修改物品类型,删除物品类型。
以下是使用 Delphi-OpenCV 库在 Delphi XE 将 IplImage 对象转换为Bitmap 对象的详细代码: ```delphiuses OpenCV_Core, OpenCV_ImageProc, // Delphi-OpenCV 库单元 Vcl.Graphics; // VCL 图形单元 function IplImageToBitmap(const Image: pIplImage): TBitmap; var Depth, Channels: Integer; LineSize: Integer; ImageData, SrcLine, DestLine: Pointer; Bitmap: TBitmap; Row, Col: Integer; Data: Byte; begin Depth := Image.depth; Channels := Image.nChannels; LineSize := Image.width * Channels; // 分配 Bitmap 对象 Bitmap := TBitmap.Create; Bitmap.PixelFormat := pf24bit; Bitmap.Width := Image.width; Bitmap.Height := Image.height; // 按行遍历 IplImage 数据并转换为 TBitmap 数据 ImageData := Image.imageData; for Row := 0 to Image.height - 1 do begin SrcLine := ImageData + Row * Image.widthStep; DestLine := Bitmap.ScanLine[Row]; case Depth of IPL_DEPTH_8U: begin for Col := 0 to Image.width - 1 do begin Data := PByte(SrcLine + Col * Channels)^; PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_8S: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PShortInt(SrcLine + Col * Channels)^); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_16U: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PWord(SrcLine + Col * Channels)^ shr 8); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_16S: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PShortInt(SrcLine + Col * Channels)^ shr 8 + 128); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_32S: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PInteger(SrcLine + Col * Channels)^ shr 24); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_32F: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PSingle(SrcLine + Col * Channels)^ * 255); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; IPL_DEPTH_64F: begin for Col := 0 to Image.width - 1 do begin Data := Byte(PDouble(SrcLine + Col * Channels)^ * 255); PByte(DestLine + Col * 3)^ := Data; PByte(DestLine + Col * 3 + 1)^ := Data; PByte(DestLine + Col * 3 + 2)^ := Data; end; end; end; end; Result := Bitmap; end; ``` 使用方法: ```delphi var Image: pIplImage; Bitmap: TBitmap; begin // 加载图像到 Image 变量 Image := cvLoadImage('image.jpg'); // 将 IplImage 对象转换为 TBitmap 对象 Bitmap := IplImageToBitmap(Image); // 将 TBitmap 对象显示在 TImage 组件上 Image1.Picture.Assign(Bitmap); // 释放 IplImage 对象内存 cvReleaseImage(@Image); end; ``` 需要注意的是,由于 Delphi-OpenCV 库的 IplImage 对象是指针类型的,因此需要传入指针的指针作为参数。在使用完毕后,需要手动释放 IplImage 对象的内存。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值