- A+
在上篇文章中我们看到
if (plug) { if (!request_count) trace_block_plug(q); else { if (request_count >= BLK_MAX_REQUEST_COUNT) {//如果这个队列的个数太多了,就通过下面的blk_flush_plug_list进行泄洪 blk_flush_plug_list(plug, false); trace_block_plug(q); } }
这个是在blk_queue_bio 中,也就是在提交请求的情况下,会调用blk_flush_plug_list 来unplug操作。
今天看看另外一种unplug操作,
调度时进行unplug(异步方式),
当发生内核调度时,当前进程sleep前,先将当前task的plug列表中的请求flush到派发队列中,并进行unplug。
主要代码流程如下:
schedule-> sched_submit_work -> blk_schedule_flush_plug()-> blk_flush_plug_list(plug, true) ->注意:这里传入的from_schedule参数为true,表示将触发异步unplug,即唤醒kblockd工作队列来进行unplug操作。后续的kblockd的唤醒周期在块设备驱动中设置,比如scsi中设置为3ms。 queue_unplugged-> blk_run_queue_async queue_unplugged(): void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule) { struct request_queue *q; unsigned long flags; struct request *rq; LIST_HEAD(list); unsigned int depth; flush_plug_callbacks(plug, from_schedule); if (!list_empty(&plug->mq_list)) blk_mq_flush_plug_list(plug, from_schedule); if (list_empty(&plug->list)) return; list_splice_init(&plug->list, &list); list_sort(NULL, &list, plug_rq_cmp); q = NULL; depth = 0; local_irq_save(flags); while (!list_empty(&list)) { rq = list_entry_rq(list.next);list 里面存放的就是要执行的rq,每一个遍历出来。这个 list_del_init(&rq->queuelist); BUG_ON(!rq->q); if (rq->q != q) { /* * This drops the queue lock */ if (q) queue_unplugged(q, depth, from_schedule); q = rq->q; depth = 0; spin_lock(q->queue_lock); } /* * Short-circuit if @q is dead */ if (unlikely(blk_queue_dying(q))) { __blk_end_request_all(rq, -ENODEV); continue; } /* * rq is already accounted, so use raw insert */ if (rq->cmd_flags & (REQ_FLUSH | REQ_FUA)) __elv_add_request(q, rq, ELEVATOR_INSERT_FLUSH); else __elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE); depth++; } static void queue_unplugged(struct request_queue *q, unsigned int depth, bool from_schedule) __releases(q->queue_lock) { if (from_schedule) blk_run_queue_async(q); else __blk_run_queue(q); spin_unlock(q->queue_lock); }
根据from_schedule 进行同步操作或者异步
void blk_run_queue_async(struct request_queue *q) { if (likely(!blk_queue_stopped(q) && !blk_queue_dead(q))) mod_delayed_work(kblockd_workqueue, &q->delay_work, 0); }
这里面就调入一个异步时钟,到见了它还是会调用到__blk_run_queue,进行request的操作的。
这里面异步时钟的逻辑,它里面有一套,大概可以比喻成handle 的消息处理机制,这一套我们先不讨论。
我们先看下这个异步时钟是在哪里设置的,
blk_init_queue--》blk_init_queue_node--》blk_alloc_queue_node
就是在初始化的时候做的,
struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id) { struct request_queue *q; int err; ..... setup_timer(&q->backing_dev_info.laptop_mode_wb_timer, laptop_mode_timer_fn, (unsigned long) q); setup_timer(&q->timeout, blk_rq_timed_out_timer, (unsigned long) q); INIT_DELAYED_WORK(&q->delay_work, blk_delay_work); }
中间的时钟消息逻辑我们先不看,我们知道,到时间以后,它会执行blk_delay_work
static void blk_delay_work(struct work_struct *work) { struct request_queue *q; q = container_of(work, struct request_queue, delay_work.work); spin_lock_irq(q->queue_lock); __blk_run_queue(q); spin_unlock_irq(q->queue_lock); }
OK,最终还是到__blk_run_queue,在进行下一步分析前我们先处理下逻辑。
就是一个list里面,有很多个bio请求,这些bio请求转换成驱动能认识的就是rq,什么时候对这个rq进行遍历,
一个就是实际请求的时候,同步策略,另外一个就是schedule的时候异步。
达到的条件是要达到MAX,我就开始遍历。每个rq。
rq的--->q,是由驱动设置进来的,q有可能有很多个驱动,如磁盘,usb或者其他block驱动
所以这里可以细化的,就是可以做一个list分类,每个驱动达到MAX,再来清理
那么我们作为学习,假设就是一个q来说,最终由__blk_run_queue,送入到那个地方进行运行,而驱动就使用elv_next_request,
拿取,rq。这里似乎还有点差距。我们在来看看__blk_run_queue,应该就没什么缝隙了。
void __blk_run_queue(struct request_queue *q) { if (unlikely(blk_queue_stopped(q))) return; __blk_run_queue_uncond(q); } inline void __blk_run_queue_uncond(struct request_queue *q) { if (unlikely(blk_queue_dead(q))) return; q->request_fn_active++; q->request_fn(q); q->request_fn_active--; }
OK,最终调用的是request_fn,就是在
这边设置进来的东西,上面是我们的demo代码。
ok,调用到了驱动的代码,没有问题了,最终的这个elv_next_request,进行那q里面,电梯排好的请求。
这就意味着,电梯算法处理的是rq,它是怎么进入的,
进入也是没有问题的,
初始化也应该没有问题,这样的话,我大概就是可以去看,elv的代码了。
--- Linux文件系统学习系列笔记 ---
(原创笔记,转载请联系博主授权)
Linux文件系统学习:io的plug过程-request请求(9)
Linux文件系统学习:io的plug过程-blk_init_queue(10)
Linux文件系统学习:io的plug过程-blk_flush_plug_list的情况(11)
Linux文件系统学习:io的plug过程-queuelist的问题(12)
<欢迎关注微信公众号,第一时间查看最新内容>
2019年4月22日 下午7:38 沙发
整理的很全面,系统