Linux文件系统学习:io的plug过程-blk_flush_plug_list的情况(11)

  • A+
所属分类:Linux系统

在上篇文章中我们看到

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,就是在

io的plug过程:blk_flush_plug_list的情况

这边设置进来的东西,上面是我们的demo代码。

io的plug过程:blk_flush_plug_list的情况

ok,调用到了驱动的代码,没有问题了,最终的这个elv_next_request,进行那q里面,电梯排好的请求。

这就意味着,电梯算法处理的是rq,它是怎么进入的,

io的plug过程:blk_flush_plug_list的情况

 

进入也是没有问题的,

初始化也应该没有问题,这样的话,我大概就是可以去看,elv的代码了。

 

--- Linux文件系统学习系列笔记 ---

(原创笔记,转载请联系博主授权)

Linux文件系统学习:整体框架图(1)

Linux文件系统学习:初始化过程(2)

Linux文件系统学习:文件read流程分析(3)

Linux文件系统学习:文件read和BIO调度分析(4)

Linux文件系统学习:文件write过程分析(5)

Linux文件系统学习:io调度框架(6)

Linux文件系统学习:io的提交过程(7)

Linux文件系统学习:io的plug过程-启动篇(8)

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)

Linux文件系统学习:电梯算法简介(13)

Linux文件系统学习:电梯算法noop(14)

Linux文件系统学习:电梯算法deadline(15)

 

<欢迎关注微信公众号,第一时间查看最新内容>

神农笔记微信公众号

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:1   其中:访客  1   博主  0

    • 匿名 匿名 2

      整理的很全面,系统