IO
调度器
当
IO
旅行到调度器的时候,发现自己受到的待遇竟然很不一样,有些
IO
倚仗着特权很快就放行了;有些
IO
迟迟得不到处理,甚至在有些系统中居然饿死!面对这样的现状,
IO
显然是很不高兴的,凭什么别人就能被很快送到下一个旅程,自己需要在调度器上耗费青春年华?这里是拼爹的时代,人家出身好,人家是读请求,人家就可以很快得到资源。咱们是写请求,出生贫寒�”>* may now be mergeable after it had proven unmergeable (above).
* We don't worry about that case for efficiency. It won't happen
* often, and the elevators are able to handle it.
*/
/* 采用bio对request请求进行初始化 */
init_request_from_bio(req, bio);
if
(test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags))
req->cpu = raw_smp_processor_id();
plug = current->plug;
if
(plug) {
/*
* If this is the first request added after a plug, fire
* of a plug trace. If others have been added before, check
* if we have multiple devices in this plug. If so, make a
* note to sort the list before dispatch.
*/
if
(list_empty(&plug->list))
trace
_block_plug(q);
else
{
if
(!plug->should_sort) {
struct request *__rq;
__rq = list_entry_rq(plug->list.prev);
if
(__rq->q != q)
plug->should_sort =
1
;
}
if
(request_count >= BLK_MAX_REQUEST_COUNT) {
/* 请求数量达到队列上限值,进行unplug操作 */
blk_flush_plug_list(plug,
false
);
trace
_block_plug(q);
}
}
/* 将请求加入到队列 */
list_add_tail(&req->queuelist, &plug->list);
drive_stat_acct(req,
1
);
}
else
{
/* 在新的内核中,如果用户没有调用start_unplug,那么,在IO scheduler中是没有合并的,一旦加入到request queue中,马上执行unplug操作,这个地方个人觉得有点不妥,不如以前的定时调度机制。对于ext3文件系统,在刷写page cache的时候,都需要首先执行start_unplug操作,因此都会进行request/bio的合并操作。 */
spin_lock_irq(q->queue_lock);
/* 将request加入到调度器中 */
add_acct_request(q, req, where);
/* 调用底层函数执行unplug操作 */
__blk_run_queue(q);
out_unlock:
spin_unlock_irq(q->queue_lock);
}
}
对于
blk_queue_bio
函数主要做了三件事情:
1)
进行请求的后向合并操作
2)
进行请求的前向合并操作
3)
如果无法合并请求,那么为
bio
创建一个
request
,然后进行调度
在
bio
合并过程中,最为关键的函数是
elv_merge
。该函数主要工作是判断
bio
是否可以进行后向合并或者前向合并。对于所有的调度器,后向合并的逻辑都是相同的。在系统中维护了一个
request hash
表,然后通过
bio
请求的起始地址进行
hash
寻址。
Hash
表的生成原理比较简单,就是将所有
request
的尾部地址进行分类,分成几大区间,然后通过
hash
函数可以寻址这几大区间。
Hash
函数是:
hash_long(ELV_HASH_BLOCK((sec)), elv_hash_shift)
一旦通过
hash
函数找到所有位于这个区间的
request
之后,通过遍历的方式匹配到所需要的
request
。具体该过程的实现函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
|
采用
hash
方式维护
request
,有一点需要注意:当一个
request
进行合并处理之后,需要对该
request
在
hash
表中进行重新定位。这主要是因为
request
的尾地址发生了变化,有可能会超过一个
hash
区间的范围。
如果后向合并失败,那么调度器会尝试前向合并。不是所有的调度器支持前向合并,如果调度器支持这种方式,那么需要注册
elevator_merge_fn
函数实现前向调度功能。例如
deadline
算法采用了红黑树的方式实现前向调度。如果前向调度无法完成合并。那么调度器认为该合并失败,需要产生一个新的
request
,并且采用现有
bio
对其进行初始化,然后加入到
request queue
中进行调度处理。
当
IO
利用
generic_make_request
来到块设备层之后,对其进行处理的重要函数
blk_queue_bio
主要任务是合并
IO
。由于不同的调度器有不同的合并方法、
IO
分类方法,所以,具体调度器的算法会采用函数注册的方式实现。
blk_queue_bio
仅仅是一个上层函数,最主要完成后向合并、调用调度器方法进行前向合并以及初始化
request
准备调度。
<待续>
本文出自 “
存储之道
” 博客,转载请与作者联系!