Q:块设备层保序的不足?
A:块设备层提供保序后带来的最大问题是对IO性能的影响.因为保序开始后,在函数__elv_next_request()->blk_do_ordered()中,会等待当前阶段的步骤完毕后再派发新的request,此时不属于保序过程的其他request无法被执行,整个队列的IO基本处于停滞状态.鉴于此,社区在改进方面达成共识:由文件系统自己来处理IO的次序,而不是将保序工作推给块设备层,块设备层不再提供保序,只是提供冲刷磁盘缓存的接口供上层使用.下面以submit_bio(WRITE_FLUSH_FUA)为例对3.3.7冲刷功能的状态机进行分析:
1.块设备层收到上层提交的带WRITE_FLUSH_FUA标志的bio后,为此bio单独分配fs_request,并且调用blk_insert_flush()开始处理fs_request.
2.blk_insert_flush()调用blk_flush_policy()判断fs_request需要做哪些冲刷的步骤,可能有3步:REQ_FSEQ_PREFLUSH(在数据请求以前冲刷磁盘缓存),REQ_FSEQ_DATA(写入数据请求),REQ_FSEQ_POSTFLUSH(在数据请求之后冲刷磁盘缓存),如果磁盘不支持FUA,那么可以使用REQ_FSEQ_POSTFLUSH代替,假定我们分析的场景中,磁盘不支持FUA,则最终我们的冲刷策略为3步都做(policy=111).
3.blk_insert_flush()初始化request的完成函数为flush_data_end_io()后,调用REQ_FSEQ_ACTIONS & ~policy把可以跳过的步骤对应的位置填1,调用blk_flush_complete_seq()开始冲刷过程.
4.进入到blk_flush_complete_seq(),参数seq表示完成的步骤,将已完成步骤更新到rq->flush.seq中,调用blk_flush_cur_seq()看下一步是哪步,在当前的场景中为前刷REQ_FSEQ_PREFLUSH,因此将request加入到pending队列,调用blk_kick_flush()请求使用磁盘的冲刷request.
5.进入到blk_kick_flush(),初始化队列自带的冲刷request,设置回调函数为flush_end_io(),将冲刷request加入到磁盘的分发队列执行.
6.冲刷request执行完毕,在回调函数flush_end_io()中,从pending队列中拿到原始的fs_request,当前fs_request所处的阶段为REQ_FSEQ_PREFLUSH,现在冲刷request已经执行完毕,调用blk_flush_complete_seq(fs_request,REQ_FSEQ_PREFLUSH)完成之.
7.再次进入到blk_flush_complete_seq(),fs_request->flush.seq更新为REQ_FSEQ_PREFLUSH,下一步为REQ_FSEQ_DATA,将fs_request加入到磁盘的分发队列执行.
8.fs_request执行完毕,回调函数flush_data_end_io()会调用blk_flush_complete_seq(fs_request,REQ_FSEQ_DATA)完成REQ_FSEQ_DATA步.
9.第3次进入到blk_flush_complete_seq(),当前fs_request->flush.seq更新为REQ_FSEQ_PREFLUSH|REQ_FSEQ_DATA,下一步为REQ_FSEQ_POSTFLUSH,于是重复5,6步再使用一次冲刷request.
10.最后一次进入到blk_flush_complete_seq(),当前fs_request->flush.seq更新为REQ_FSEQ_PREFLUSH|REQ_FSEQ_DATA|REQ_FSEQ_POSTFLUSH,下一步即是终点,将fs_request从pending队列中移除,调用__blk_end_request_all()最终完成fs_request.
Q:冲刷的过程中request_queue为什么要使用双缓冲队列来存放fs_request.
A:双缓冲队列可以做到只执行一次冲刷请求就可以完成多个fs_request的冲刷要求.队列自带的冲刷request在执行的过程中,blk_insert_flush()可以被调用多次,来自上层的fs_request被添加到pending1队列,等待冲刷request的下一次执行,当冲刷requst可以再次被执行时,pending1队列不再接收新的fs_request(fs_request被加入到pending2队列),冲刷request执行完毕后,pending1队列所有的fs_request的PREFLUSH/POSTFLUSH执行完毕.
参考资料: