barrier linux,Linux Barrier I/O
发布日期:2021-10-21 18:59:11 浏览次数:3 分类:技术文章

本文共 4524 字,大约阅读时间需要 15 分钟。

I/O barrier请求用来保证I/O请求的顺序。其主要是针对那些需要保证顺序的写请求,比如日志的checkpoint。在请求队列中,那些排在barrier请求前的请求,必须在barrier请求开始之前完成。(这里所说的完成指数据写入物理介质,而不是保存在OS或者设备缓存中)。而那些排在barrier请求后的请求,只有在barrier请求完成后才能开始(这儿所说的完成,同样是指barrier请求的捎带数据写入物理介质)。

总的来说, I/O barrier请求拥有一下两个性质:

1.请求顺序

非barrier请求不能跨越barrier请求。barrier请求之前的请求必须先于barrier请求进行处理,barrier请求之后的请求必须在barrier请求完成后进行处理。

根据磁盘驱动器的特性,以上条件可以用以下三种方式来实现:

i.对于设备队列深度大于1(TCQ设备)并且支持ordered

tag的设备,块设备层只需要发送一个标为ordered的请求来作为barrier,底层驱动,控制器和磁盘驱动器负责确保请求的顺序。现在,大多数SCSI控制器/磁盘驱动器都应该支持这个特性。

ii. 对于设备队列深度大于1但是不支持ordered

tag的设备,块设备层确保barrier请求往设备分发前,之前的请求将先被处理完。块设备层也会延迟barrier请求之后的请求,直到barrier请求完成。老的SCSI控制器/磁盘驱动器以及SATA磁盘驱动器属于这类设备。

iii.

对于设备队列深度为1的设备,这种设备相当于ii类设备的特例。只要保证分发的顺序就够了(保证i/o调度器不打乱顺序)。较老的SCSI控制器/磁盘驱动器和IDE驱动器属于这类设备。

2. 强制刷新数据到物理介质

使用I/O

barrier的原因主要是保护文件系统的完整性。意外掉电或者其他事件使得磁盘驱动器无法正常工作,将造成磁盘缓存中数据的丢失。所以,I/Obarrier需要保证i/o请求真正被顺序写入了非易失性介质上。

这儿有四种情况:

i.无write-back缓冲,保证请求自身的顺序就足够了。

ii.有write-back缓存但没有刷新缓存的操作。这种情况下,无法保证物理介质的写入顺序。这种类型的设备不能支持I/Obarrier。

iii.有write-bach缓存,有刷新缓存的操作但无FUA(forced unit

access),这种情况下,我们需要两次缓存刷新操作:分别在barrier请求前后。

iv.有write-back缓存,刷新缓存操作和FUA。这是,我们只需要一次刷新操作来确保barrier请求之前的请求被写入物理介质。而barrier请求之后的刷新操作可以省略。因为我们可以指定barrier请求为FUA写,这样确保了barrier请求自身能被真正地写入物理介质。从而避免了第二次刷新。

怎样在驱动中支持barrier请求

---------------------------

所有barrier的处理都是在通用块层内部进行的。所有底层驱动需要实现自己的prepare_flush_fn函数,并且使用以下两个函数之一来说明自己支持哪种barrier类型以及怎样准备用于刷新缓存的请求。注意,“ordered”用来说明处理barrier请求的整个过程顺序,包括请求的分发和缓存的刷新。

typedef void (prepare_flush_fn)(struct request_queue *q, struct request *rq);

int blk_queue_ordered(struct request_queue *q, unsigned ordered,

prepare_flush_fn *prepare_flush_fn);

@q   : the queue in question

@ordered  : the ordered mode the driver/device supports

@prepare_flush_fn : this function should prepare @rq such that it

flushes cache to physical medium when executed

比如,SCSI磁盘驱动的prepare_flush_fn如下:

static void sd_prepare_flush(struct request_queue *q, struct request *rq)

{

memset(rq->cmd, 0, sizeof(rq->cmd));

rq->cmd_type = REQ_TYPE_BLOCK_PC;

rq->timeout = SD_TIMEOUT;

rq->cmd[0] = SYNCHRONIZE_CACHE;

rq->cmd_len = 10;

}

当前Linux中,支持以下7中顺序模式。以下表格显示了根据不同设备/驱动的特点需要采用哪种模式。在表格的最左边,QUEUE_ORDERED_前缀被省略了,以节约空间。

表格后面是各种模式的解释。注意,在QUEUE_ORDERED_DRAIN*中的描述使用了“=>”,而在QUEUE_ORDERED_TAG*的描述中使用了“->”。前者表示,前面的步骤必须在后面的步骤开始之前完成。而后者表示后面的步骤只要在前面步骤开始后就可以开始。

write-back cache ordered tag flush  FUA

-----------------------------------------------------------------------

NONE  yes/no  N/A  no  N/A

DRAIN  no  no  N/A  N/A

DRAIN_FLUSH yes  no  yes  no

DRAIN_FUA yes  no  yes  yes

TAG  no  yes  N/A  N/A

TAG_FLUSH yes  yes  yes  no

TAG_FUA  yes  yes  yes  yes

QUEUE_ORDERED_NONE

I/O barrier不需要或者不被支持

Sequence: N/A

QUEUE_ORDERED_DRAIN

请求以请求队列中分发的顺序进行排序,不需要刷新缓存操作。

Sequence: drain => barrier

QUEUE_ORDERED_DRAIN_FLUSH

请求以请求队列中分发的顺序进行排序,需要在barrier请求前和之后刷新缓存。

Sequence: drain => preflush => barrier => postflush

QUEUE_ORDERED_DRAIN_FUA

请求以请求队列中分发的顺序进行排序,之需要在barrier请求前刷新缓存。在barrier请求上使用FUA,省略barrier请求之后的刷新。

Sequence: drain => preflush => barrier

QUEUE_ORDERED_TAG

请求被ordered tag排序,不需要刷新缓存。

Sequence: barrier

QUEUE_ORDERED_TAG_FLUSH

请求被ordered tag排序,在barrier请求之前和之后,需要刷新缓存。

Sequence: preflush -> barrier -> postflush

QUEUE_ORDERED_TAG_FUA

请求被ordered

tag排序,在barrier请求之前需要刷新缓存。在barrier请求上使用FUA,省略barrier请求之后的刷新。

Sequence: preflush -> barrier

----------------

*SCSI层当前不能使用TAG ordering,即使磁盘驱动器,控制器和驱动支持这个特性。其主要原因是SCSI中间层的请求分发函数不是原子的。在分发请求时,它会释放请求队列的锁,转而获取SCSI host的锁。这样,就可能发生请求相对位置改变的情况。一但这个问题解决了,TAG ordering将会启用。

*当前,无论那种顺序模式被使用,某一时刻,只能有一个barrier请求被处理。所有I/O barrier都会被通用块层扣留起来,直到前面的I/O barrier处理完成。这和DRAIN顺序的设备没有什么不同。但是,对于TAG 顺序的设备,由于其命令时延较长,如果I/O barrier分发频

繁,传递多个I/O barrier到底层可能会有帮助。

*完成顺序。顺序的请求被顺序的分发,但不要求顺序完成。Barrier实现可以处理乱序完成的请求。也就是说,请求必须被顺序处理,但是硬件/软件完成路径允许以乱序进行通知。比如,当前SCSI中间层就没在错误处理时预设完成顺序。

*重新入队顺序。底层驱动可以自由重新入队任何请求。由于barrier序列在重新入队时必须保持顺序,通用电梯程序中保证插入的请求符合barrier顺序。参见blk_ordered_req_seq() 和 ELEVATOR_INSERT_REQUEUE handling in __elv_add_request()。

注意,在完成一个顺序序列后面的请求时,块设备不能重新入队之前的请求。当前,没有针对这种错误的检查。

*错误处理。当前,当在一个顺序序列中的请求发生错误时,通用块层会把错误报告给上层。不幸的是,这样做往往还不够。比如有如下的请求,使用QUEUE_ORDERED_TAG_FLUSH顺序模式。

[0] [1] [2] [3] [pre] [barrier] [post] < [4] [5] [6] ... >

still in elevator

假设[2],[3]是用来更新文件系统metadata的写请求(日志等)。[barrier]用来标识这些更新是有效的。考虑以下序列:

i.请求[0]~[post] 离开了请求队列,进入底层驱动。

ii. 过了一会儿,不幸发生了,磁盘驱动器出现故障,[2]失败。注意,此时[0],[1],和[3]都已经完成了。但是[pre]还没有完成,因为磁盘驱动器必须按照顺序来处理它。它在也会失败。

iii.此时,错误处理介入。它确定错误不能恢复,[2]操作失败,并重启操作。

iv.[pre][barrier][post]得到处理

v.掉电

问题在于,barrier请求本来认为文件系统更新请求[2]和[3]会被安全地写入物理介质。如果机器在barrier被写入后crash掉。文件系统的恢复代码可以依靠它。不幸的是,这种情况不再成立。也就是说,I/O barrier的成功,必须依赖前面某些请求的成功,这究竟是那些请求,只有上面的文件系统才知道。

要解决这个问题,可以通过实现一种方式,来告诉通用块层哪些请求影响之后的barrier请求,使底层驱动只有在得到通用块层通知后才进行错误处理。

转载地址:https://blog.csdn.net/weixin_29391349/article/details/117343256 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:android自定义popwindow,Android 用自定义PopupWindow实现自定义Toast
下一篇:android app分享功能,Android APP集成新浪微博分享功能

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年03月28日 04时18分28秒