前面分析了,正常情况下,barrier request的插入方向是ELEVATOR_INSERT_BACK。在把barrier request加入设备请求队列末尾之前,需要调用elv_drain_elevator把调度算法中的请求队列中的request都“排入”设备的请求队列。注意,elv_drain_elevator调用的是while (q->elevator->ops->elevator_dispatch_fn(q, 1));。它设置了force dispatch,因此elevator调度算法必须强制把所有缓存在自己调度队列中的request都分发到设备的请求队列。比如AS调度算法,如果在force dispatch情况下,它会终止预测和batching。这样,当前barrier request必然是插入设备请求队列的最后一个request。不然,如果AS可能出于预测状态,它可能延迟request的处理,即缓存在调度算法队列中的request排不干净。现在,barrier request和之前的request都到了设备的请求队列,下面就是调用设备请求队列的request_fn来处理每个请求。(blk_remove_plug(q)之前的注视不是很明白,需要进一步分析) 上面是request barrier正常的插入情况。但是,如果在barrier request的处理序列中,某个request可能出现错误,比如设备繁忙,无法完成flush操作。这个时候,通过错误处理,该barrier request处理序列中的request需要requeue: ELEVATOR_INSERT_REQUEUE。因为一个barrier request的处理序列存在preflush,barrier,postflush这个顺序,所以当其中一个request发生requeue时,需要考虑barrier request处理序列当前的顺序,看究竟执行到了哪一步了。然后根据当前的序列,查找相应的request并加入队尾。3.处理barrier request 现在,barrier request以及其前面的request都被排入了设备请求队列。处理过程具体是在request_fn中,调用__elv_next_request来进行处理的。 static inline struct request *__elv_next_request(struct request_queue *q) { struct request *rq; while (1) { while (!list_empty(&q->queue_head)) { rq = list_entry_rq(q->queue_head.next); if (blk_do_ordered(q, &rq)) return rq; } if (!q->elevator->ops->elevator_dispatch_fn(q, 0)) return NULL; } } __elv_next_request每次取出设备请求队列中队首request,并进行blk_do_ordered操作。该函数具体处理barrier request。blk_do_ordered的第二个参数为输入输出参数,它表示下一个要执行的request。Request从设备队列头部取出,交由blk_do_ordered判断,如果是一般的request,则该request会被直接返回给设备驱动,进行处理。如果是barrier request,则该request会被保存,rq会被替换成barrier request执行序列中相应的request,从而开始执行barrier request的序列。 int blk_do_ordered(struct request_queue *q, struct request **rqp) { struct request *rq = *rqp; const int is_barrier = blk_fs_request(rq) && blk_barrier_rq(rq); if (!q->ordseq) { if (!is_barrier) return 1; if (q->next_ordered != QUEUE_ORDERED_NONE) { *rqp = start_ordered(q, rq); return 1; } else { … } } /* * Ordered sequence in progress */ /* Special requests are not subject to ordering rules. */ if (!blk_fs_request(rq) && rq != &q->pre_flush_rq && rq != &q->post_flush_rq) return 1; if (q->ordered & QUEUE_ORDERED_TAG) { /* Ordered by tag. Blocking the next barrier is enough. */ if (is_barrier && rq != &q->bar_rq)//the next barrier i/o *rqp = NULL; } else { /* Ordered by draining. Wait for turn. */ WARN_ON(blk_ordered_req_seq(rq) < blk_ordered_cur_seq(q)); if (blk_ordered_req_seq(rq) > blk_ordered_cur_seq(q)) *rqp = NULL; } return 1; } blk_do_ordered分为两部分。首先,如果barrier request请求序列还没开始,也就是还没有开始处理barrier request,则调用start_ordered来初始化barrier request处理序列。此外,如果当前正处于处理序列中,则根据处理序列的阶段来处理当前request。 初始化处理序列start_ordered static inline struct request *start_ordered(struct request_queue *q, struct request *rq) { q->ordered = q->next_ordered; q->ordseq |= QUEUE_ORDSEQ_STARTED; /* * Prep proxy barrier request. */ blkdev_dequeue_request(rq); q->orig_bar_rq = rq; rq = &q->bar_rq; blk_rq_init(q, rq); if (bio_data_dir(q->orig_bar_rq->bio) == WRITE) rq->cmd_flags |= REQ_RW; if (q->ordered & QUEUE_ORDERED_FUA) rq->cmd_flags |= REQ_FUA; init_request_from_bio(rq, q->orig_bar_rq->bio); rq->end_io = bar_end_io; /* * Queue ordered sequence. As we stack them at the head, we * need to queue in reverse order. Note that we rely on that * no fs request uses ELEVATOR_INSERT_FRONT and thus no fs * request gets inbetween ordered sequence. If this request is * an empty barrier, we don't need to do a postflush ever since * there will be no data written between the pre and post flush. * Hence a single flush will suffice. */ if ((q->ordered & QUEUE_ORDERED_POSTFLUSH) && !blk_empty_barrier(rq)) queue_flush(q, QUEUE_ORDERED_POSTFLUSH); else q->ordseq |= QUEUE_ORDSEQ_POSTFLUSH; elv_insert(q, rq, ELEVATOR_INSERT_FRONT); if (q->ordered & QUEUE_ORDERED_PREFLUSH) { queue_flush(q, QUEUE_ORDERED_PREFLUSH); rq = &q->pre_flush_rq; } else q->ordseq |= QUEUE_ORDSEQ_PREFLUSH; if ((q->ordered & QUEUE_ORDERED_TAG) || q->in_flight == 0) q->ordseq |= QUEUE_ORDSEQ_DRAIN; else rq = NULL; return rq; } start_ordered为处理barrier request准备整个request序列。它主要完成以下工作(1)保存原来barrier request,用设备请求队列中所带的bar_rq来替换该barrier request。其中包括从该barreir request复制request的各种标志以及bio。(2)根据设备声明的所支持的barrier 序列,初始化request序列。该序列最多可能包含三个request:pre_flush_rq, bar_rq, post_flush_rq。它们被倒着插入对头,这样就可以一次执行它们。当然,这三个request并不是必须得。比如,如果设备支持的处理序列仅为QUEUE_ORDERED_PREFLUSH,则只会把pre_flush_rq和bar_qr插入队列。又比如,如果barrier reques没有包含任何数据,则post flush可以省略。因此,也只会插入上面两个request。注意,pre_flush_rq和post_flush_rq都是在插入之前,调用queue_flush初始化得。除了插入请求序列包含的request,同时还需要根据请求序列的设置来设置当前进行的请求序列:q->ordseq。现在请求序列还没有开始处理,怎么在这儿设置当前的请求序列呢?那是因为,根据设备的特性,三个request不一定都包括。而每个request代表了一个序列的处理阶段。这儿,我们根据设备的声明安排了整个请求序列,因此知道那些请求,也就是那些处理阶段不需要。在这儿把这些阶段的标志置位,表示这些阶段已经执行完毕,是为了省略相应的处理阶段。现在,设备请求队列中的请求以及处理序列如下所示: Rq1re2 pre_flush_rqbar_rqpost_flush_rq 00000 QUEUE_ORDSEQ_DRAINQUEUE_ORDERED_PREFLUSH QUEUE_ORDSEQ_BARQUEUE_ORDERED_POSTFLUSH
|