Raid1源代码分析--Barrier机制
本想就此结束Raid1的专题博客,但是觉得Raid1中自己构建的一套barrier机制的设计非常巧妙,值得单独拿出来分析。它保证了同步流程和正常读写流程的并发性,也为设备冻结/解冻(freeze/unfreeze)机制提供了保障。
Barrier的意思就是,在某个请求设置上barrier之后,要先挡住barrier请求之后到来的请求,并催促barrier请求之前还未完成的请求执行,等待之前的请求全部返回完成之后,开始处理barrier请求,直到barrier请求完成之后,才允许barrier请求之后到来的请求执行。
关于barrier机制的函数其实总共只有4个,并且每个函数代码都很短,但是设计却很精妙。先列举出4个函数的代码,后面结合同步与读写的并发、设备冻结解冻机制做详细分析。
static void raise_barrier(conf_t *conf)
{
spin_lock_irq(&conf->resync_lock); /* Wait until no block IO is waiting */
wait_event_lock_irq(conf->wait_barrier, !conf->nr_waiting,
conf->resync_lock,
raid1_unplug(conf->mddev->queue)); /* block any new IO from starting */
conf->barrier++; /* No wait for all pending IO to complete */
wait_event_lock_irq(conf->wait_barrier,
!conf->nr_pending && conf->barrier < RESYNC_DEPTH,
conf->resync_lock,
raid1_unplug(conf->mddev->queue)); spin_unlock_irq(&conf->resync_lock);
}
static void lower_barrier(conf_t *conf)
{
unsigned long flags;
spin_lock_irqsave(&conf->resync_lock, flags);
conf->barrier--;
spin_unlock_irqrestore(&conf->resync_lock, flags);
wake_up(&conf->wait_barrier);
}
static void wait_barrier(conf_t *conf)
{
spin_lock_irq(&conf->resync_lock);
if (conf->barrier) {
conf->nr_waiting++;
wait_event_lock_irq(conf->wait_barrier, !conf->barrier,
conf->resync_lock,
raid1_unplug(conf->mddev->queue));
conf->nr_waiting--;
}
conf->nr_pending++;
spin_unlock_irq(&conf->resync_lock);
}
static void allow_barrier(conf_t *conf)
{
unsigned long flags;
spin_lock_irqsave(&conf->resync_lock, flags);
conf->nr_pending--;
spin_unlock_irqrestore(&conf->resync_lock, flags);
wake_up(&conf->wait_barrier);
}
字段的语义:nr_pending表示正在进行处理但还未返回的正常IO的计数;
barrier表示处与将要处理到返回完成之间这个阶段的同步IO的计数;
nr_waiting表示正在等待处理的正常IO的计数;
为了便于理解,可以结合后面举出的实例来读下面这段文字描述。
1、同步IO与正常IO的并发性
每个正常读写请求到来,在真正处理它之前,先调用wait_barrier函数;在该请求处理完成返回之后,调用allow_barrier函数。
每个同步请求到来,在真正处理它之前,先调用raise_barrier函数;在该请求处理完成返回之后,调用lower_barrier函数。
这四个函数共同控制3个重要的变量:nr_waiting, barrier, nr_pending,来控制同步与正常读写的并发(这3个变量公式conf结构的成员,整个raid1系统都可见)。既保证了同步IO和正常IO的互斥,又能均衡同步IO和正常IO的执行进度。并且在基本平衡同步和正常IO的执行进度的前提下,相对而言偏向于优先相应正常IO请求。
Raid1在做同步的时候,需要互斥掉正常IO,让正常IO等待,不然就会影响同步的正确性;然而Raid1做同步的区域往往很大,耗时通常很长,如果要等待同步完全完成之后才相应正常IO的话,对于应用来说是不可忍受,甚至是灾难性的。那么该怎么办呢?原来,系统在做同步的时候,同步流程会走很多次,每一次同步一个chunk的区域(很多时候我们设置为64K)。所以在某次同步中,需要同步的区域可能很大,但是会被分成以chunk为粒度的众多小IO来分别走同步流程,进而完成全部的同步工作。所以可以使同步IO和正常IO穿插互斥执行,而不会使得在做很大区域的同步的时候,raid1长时间不响应正常IO。并且通过barrier机制的设计,使得raid1相对而言略微偏向于优先响应正常IO请求,这符合一个正常存储系统的运转方式。
1) 当有正常IO在处理的时候,来了一个或多个同步IO,那么raid1将让后续到来的正常IO等待,并尽快处理已经在做的IO。如果后续有正常IO等待,之后还有同步IO来,那么之后来的这些同步IO需要等待正在等待的正常IO处理完之后才处理,即到了下下一轮处理同步IO的那一批。
1.1 在同步线程中,因为此时nr_waiting == 0,nr_pending != 0,一个同步IO来了之后,在raise_barrier函数中,第6行不等待,然后11行将barrier++,在第14行的wait_event中等待,并通过第17行通知raid1赶紧处理正在做的IO请求;当还有同步IO来,同样执行上面的流程,需要递增barrier计数;
1.2 在make request线程中,后续如果有正常IO到来,则进到make_request函数之后马上执行wait_barrier函数,此时barrier != 0,所以第5行nr_waiting++,在第6行的wait_event中等待,同样通过第8行通知raid1赶紧处理正在做的IO请求;当还有正常IO来,同样执行上面的流程,需要递增nr_pending计数;
1.3 在这之后如果还有同步IO到来,则此时nr_waiting != 0,那么在raise_barrier函数中,在第6行的wait_event中等待,同样通过第8行通知raid1赶紧处理正在做的IO请求,barrier不加;barrier机制使得,这时到来的同步IO请求一定在前面正在等待的正常IO之后处理;当还有同步IO来,同样执行上面的流程,在6行等待。这时的这些同步IO要在1.2的正常IO处理完之后的下一批才会处理;
1.4 在这之后,如果还有正常IO到来,则加入到1.2中的那批正常IO中;如果还有同步IO到来,则加入到1.3中的那批同步IO中;
2)当已经在做的正常IO处理完成之后,处理1.1中正在等待的那些同步IO。如果这个过程中没有正常IO来,那么当还有同步IO到来的时候,会继续处理这些同步IO,并且同时处理的同步IO数量不能超过RESYNC_DEPTH(值为32)个;如果这个过程中有正常IO来,那么正常IO仍然需要等待,并且正常IO之后来的同步IO放到需要在下一轮处理同步IO时才处理。
2.1 每一个正常IO返回时都会调用allow_barrier函数,将nr_pending--;当正常IO全部返回时,nr_pending减为0,唤醒并触发raise_barrier函数第15行的继续执行的条件,这时在1.1中等待的同步IO进入处理流程;
2.2 如果接着还有同步IO到来,那么在raise_barrier函数中barrier++,两个wait_event都不等待,继续处理刚进来的同步IO;直到正在处理的同步IO的个数达到RESYNC_DEPTH个时,触发第14行的wait_event,等待正在处理的同步IO个数减至RESYNC_DEPTH && nr_pending == 0时才会触发处理;也就是说要如果在等待的过程中没有正常IO来,那么等到正在处理的同步IO个数小于RESYNC_DEPTH就可以执行因为RESYNC_DEPTH而等待的同步IO,但是如果在等待的过程中有正常IO来,这些因为等待RESYNC_DEPTH的同步IO则会延迟到下一轮才会被处理。
2.3 在这之后的情况,与1.2, 1.3 和1.4的处理方式一致;
3)当本轮要处理的同步IO全部都做完了,接着处理正在等待的正常IO;当后续还有正常IO来的时候,会继续处理这些正常IO;当再有同步IO到来时,则回到了1)中描述的场景,接着做1)中的处理;被延迟一轮处理的同步IO请求,会在接下来的一轮中处理。
3.1 本轮要处理的每一个同步IO返回时都会调用lower_barrier函数,将barrier--;当本轮同步IO全部返回时,barrier减为0,唤醒并触发wait_barrier函数第6行的继续执行的条件,这时在1)和2)中等待的正常IO进入处理流程,nr_pending++;并且nr_waiting--;
3.2 当nr_waiting减为0时,如果存在1)和2)中讲到的延迟到"下一轮"的同步IO,则这些IO终于满足raise_barrier函数中第6行的条件继续继续执行,barrier++,然后在第14行又等待,直到这一轮正常IO被处理完;如果有同步IO到来,则回到了1)中描述的场景。
优先处理正常IO表现在两个方面:
1) 在处理同步IO时,依次来了几个正常IO、同步IO、正常IO,那么两次正常IO会合成一批来处理(拓展到多次也一样);合完之后的顺序一定是正常IO、同步IO;
而在处理正常IO是,依次来了几个同步IO、正常IO、同步IO,那么两次同步IO不会合成一批来处理(拓展到多次也一样);合完之后的顺序一定是同步IO、正常IO、同步IO;
2)同步IO在处理时,有一个同时处理IO个数的上限值,默认为32个。
举例说明整个流程:
normal io nA arrive & work
normal io nB arrive & work
sync io sA arrive & wait at line 14
sync io sB arrive & wait at line 14
normal io nC arrive & wait
sync io sC arrive & wait at line 6
normal io nA back
sync io sD arrive & wait at line 6
normal io nB back
sync io sA work
sync io sB work
sync io sB back
sync io sE arrive & work
normal io nD arrive & wait
sync io sF arrive & wait at line 6
sync io sA back
normal io nE arrive & wait
sync io sE back
normal io nD work
normal io nE work
sync io sC wait at line 14
sync io sD wait at line 14
sync io sE wait at line 14
normal io nF arrive & work
normal io nD back
normal io nF back
normal io nE back
sync io sC work
sync io sD work
sync io sE work
normal io nG arrive & wait
sync io sC back
sync io sE back
sync io sD back
normal io nG work
normal io nG back
2、设备冻结/解冻机制的保障
设备的冻结和解冻都出现在读出错修复的流程中。在修复读出错之前,先冻结盘阵,然后整个盘阵只做读出错修复这一件事,修复完成之后(无论成功还是失败),解冻盘阵,盘阵恢复正常。
1、freeze_array函数在读出错流程开始时执行。它首先barrier++,nr_waiting++,挡住接着可能会新来的正常IO和同步IO,让它们均进入等待中;通知raid1尽快处理已经在做的IO,然后等待已经进入处理流程的IO完成;当整个raid1中只剩下读出错IO时,进入读出错的处理流程;
2、unfreeze_array函数在读出错处理流程结束时执行。它将barrier--,nr_waiting--,让等待的请求进入处理调度,并可以接收新请求,盘阵恢复正常。
转载请注明出处:http://www.cnblogs.com/fangpei/
Raid1源代码分析--Barrier机制的更多相关文章
- Raid1源代码分析--开篇总述
前段时间由于一些事情耽搁了,最近将raid1方面的各流程整理了一遍.网上和书上,能找到关于MD下的raid1的文档资料比较少.决定开始写一个系列的关于raid1的博客,之前写过的一篇读流程也会在之后加 ...
- nginx源代码分析--进程间通信机制 & 同步机制
Nginx源代码分析-进程间通信机制 从nginx的进程模型能够知道.master进程和worker进程须要通信,nginx中通信的方式有套接字.共享内存.信号.对于master进程,从外部接受信号, ...
- Raid1源代码分析--一些补充
Raid1的源码的读.写.同步,在本系列博客中都已经分析完成.除了barrier机制要专门拿出来分析(下一篇会写)以外,有一些问题值得思考和注意,分析如下. 1.freeze_array是如何做的? ...
- Raid1源代码分析--读流程(重新整理)
五.Raid1读流程分析 两个月前,刚刚接触raid1,就阅读了raid1读流程的代码,那个时候写了一篇博客.现在回过头看看,那篇的错误很多,并且很多地方没有表述清楚.所以还是决定重新写一篇以更正之前 ...
- Raid1源代码分析--写流程
正确写流程的总体步骤是,raid1接收上层的写bio,申请一个r1_bio结构,将其中的所有bios[]指向该bio.假设盘阵中有N块盘.然后克隆N份上层的bio结构,并分别将每个bios[]指向克隆 ...
- Raid1源代码分析--同步流程
同步的大流程是先读,后写.所以是分两个阶段,sync_request完成第一个阶段,sync_request_write完成第二个阶段.第一个阶段由MD发起(md_do_sync),第二个阶段由守护进 ...
- Raid1源代码分析--读流程
这篇博文不足之处较多,重新整理了一下,链接:http://www.cnblogs.com/fangpei/p/3890873.html 我阅读的代码的linux内核版本是2.6.32.61.刚进实验室 ...
- Raid1源代码分析--初始化流程
初始化流程代码量比较少,也比较简单.主要是run函数.(我阅读的代码的linux内核版本是2.6.32.61) 四.初始化流程分析 run函数顾名思义,很简单这就是在RAID1开始运行时调用,进行一些 ...
- hostapd源代码分析(二):hostapd的工作机制
[转]hostapd源代码分析(二):hostapd的工作机制 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004433 在我的上一 ...
随机推荐
- checkbox遍历操作, 提交所有选中项的值
<div class="content_list pad_10 hidden" > <h3>修改可配送地区</h3> <input typ ...
- [HAOI2006]聪明的猴子
/* 找出能连通所有点的一棵树 是的最大的边最小 很显然就是最小生成树. 堆优化prim. */ #include<iostream> #include<cstring> #i ...
- YII框架中php入口文件隐藏
Apache配置修改 主要修改下httpd文件中的两个地方 1.启用mod_rewrite.so模块,在Apache的配置文件中找到如下行,去掉前面的字符"#",保存 #LoadM ...
- memcache如何模糊查询
是新的方法,还是get方法本身就有这个功能? 需要用到递归遍历的方法,将所有的key-value扫描出来.
- ASP.NET 2.0服务器控件开发的基本概念(转载)
利用ASP.NET 2.0技术,创建Web自定义服务器控件并不是一件轻松的事情.因为,这需要开发人员了解并能够灵活应用多种Web开发技术,例如,CSS样式表.客户端 脚本语言..NET开发语言.服务器 ...
- C#判断程序是否以管理员身份运行,否则以管理员身份重新打开
/// <summary> /// 判断程序是否是以管理员身份运行. /// </summary> public static bool IsRunAsAdmin() { Wi ...
- Xcode 运行报错:“Your build settings specify a provisioning profile with the UUID ****** however, no such provisioning profile was found”
iOS开发中遇到"Your build settings specify a provisioning profile with the UUID ****** however, no su ...
- C#数组的使用
//计算数组中最大值,最小值,平均值和总和 //类中main最先执行 static void Main(string[] args) { //声明一个数组,数组长度一定固定就不能更改了 , , , , ...
- zepto源码研究 - zepto.js - 1
简要:网上已经有很多人已经将zepto的源码研究得很细致了,但我还是想写下zepto源码系列,将别人的东西和自己的想法写下来以加深印象也是自娱自乐,文章中可能有许多错误,望有人不吝指出,烦请赐教. 首 ...
- 趣味PAT--循环-19. 币值转换(20)
One visible minute on the stage is attributed to ten years of invisible practice off the stage. &quo ...