我们知道,每个块设备程序都有一个请求队列与之关联。在块设备初始化时,会分配并初始化请求队列。在这个时候,我们便可以为块设备驱动程序指定特定的IO调度算法,默认情况下是强制使用系统默认的调度算法。
熟悉块设备驱动的人知道,内核是通过generic_make_request函数来不断转发bio,直到该bio被挂载到物理设备的请求队列中。generic_make_request函数会获取bio所指向bdev的请求队列,并通过请求队列的q->make_request_fn方法来下发bio。如果该bdev指向的是物理设备时,make_request_fn是由内核的__make_request函数来实现,通常IO调度也就是在该函数中发生。该函数过程分析如下(只列出与IO调度有关系的部分):
int __make_request(request_queue_t *q, struct bio *bio)
{
……
el_ret = elv_merge(q, &req, bio);
switch (el_ret) {
//前两种可以合并
case ELEVATOR_BACK_MERGE:
if (!ll_back_merge_fn(q, req, bio))
break;
……
//插入到链表尾部
req->biotail->bi_next = bio;
req->biotail = bio;
……
if (!attempt_back_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out;
case ELEVATOR_FRONT_MERGE:
……
//插入到链表头
bio->bi_next = req->bio;
req->bio = bio;
……
if (!attempt_front_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out;
//不能合并,需要新创一个request。
/* ELV_NO_MERGE: elevator says don't/can't merge. */
default:
;
}
……
}
elv_merge函数相当重要,它试图在请求队列中找到一个能够合并该bio的request,函数返回三个可能值:
ELEVATOR_NO_MERGE:队列已经存在的请求中不能包含bio结构,需要创建一个新请求。
ELEVATOR_BACK_MERGE:bio结构可作为末尾的bio而插入到某个请求中;
ELEVATOR_FRONT_MERGE:bio结构可作为某个请求的第一个bio被插入;
可将bio合并到request中
在elv_merge函数中,首先试图将bio合并到上一次被合并的req中。如果可以合并的话,返回结果。q->last_merge保存上一次合并的请求的指针。
if (q->last_merge) {
ret = elv_try_merge(q->last_merge, bio);
if (ret != ELEVATOR_NO_MERGE) {
*req = q->last_merge;
return ret;
}
}
否则,通过elv_rqhash_find函数在调度算法的hash表(即elevator_queue中的hash字段)中查找可以将bio插入到某个req末尾的请求是否存在,如果存在,则返回ELEVATOR_BACK_MERGE,表明bio可作为末尾的bio而插入到某个请求中。
__rq = elv_rqhash_find(q, bio->bi_sector);
if (__rq && elv_rq_merge_ok(__rq, bio)) {
*req = __rq;
return ELEVATOR_BACK_MERGE;
}
如果hash表中不存在这样的req