ファイルのブロックデバイスは、ブロック単位で管理されます。セクタは物理デバイスで管理される単位です。通常HDでは、セクタは512バイトとなります。ブロックはそのサイズに応じた連続するセクタの集まり。と言うことです(ページサイズにも依存)。従ってカーネルはブロック単位で読み書きを行うことになります。
ファイルは、データのオフセットに応じたブロック位置を、そのiノードに有しています。従ってファイルの参照は、その管理するメモリ位置に対応するブロック位置を参照すればいいわけです。
カーネルでのその操作を効率的に行うために、request_queueと言うものを導入しています。一連のファイルデータのブロック位置はランダムに配置されています。ファイルの参照は非同期で行われます。カーネルは非同期で参照されるブロックをrequest_queue下で管理し、物理的に連続した一連のブロックとして、一気に参照することで、IO操作の効率化を計っています。
request_queueでリスト管理されるのはrequestとなり、requestで管理されるのはbioという物です。 bioは1ブロックのデバイスのブロック位置および対応するページフレーム内のオフセット等の情報を有した構造体です。このbioは物理的に連続するbio単位でrequestで管理される事になり、io操作ではrequest単位で読み書きが行われることになるわけです。これをエレベータ処理と言ったりするようです。
hd_init()はhdの初期関数です。blk_init_queue()でrequest_queueを取得し、ブロックデバイスのdisk->queueに設定します。blk_init_queue()の引数のコールバック関数do_hd_requestは、デバイスの参照のIO操作となり、エレベータ関数ではありません。
static int __init hd_init(void)
{
struct request_queue *hd_queue;
:
hd_queue = blk_init_queue(do_hd_request, &hd_lock);
if (!hd_queue) {
unregister_blkdev(HD_MAJOR, "hd");
return -ENOMEM;
}
for (drive = 0 ; drive < NR_HD ; drive++) {
struct gendisk *disk = alloc_disk(64);
:
disk->queue = hd_queue;
:
}
:
}
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
return blk_init_queue_node(rfn, lock, -1);
}
truct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
struct request_queue *uninit_q, *q;
uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);
if (!uninit_q)
return NULL;
q = blk_init_allocated_queue(uninit_q, rfn, lock);
if (!q)
blk_cleanup_queue(uninit_q);
return q;
}
request_queue取得の実態は、blk_init_allocated_queue()です。q->request_fnにはhd_initの引数のIO処理関数が設定されます。そしてbioをrequestに蓄えるエレベータ処理を設定するのが、blk_queue_make_request()で、その引数のblk_queue_bioが実態であり、このコールバック関数はq->make_request_fnに設定されることになります。従ってq->make_request_fnをコールすることで、bioをrequestに投入されることになるわけです。
struct request_queue *
blk_init_allocated_queue(struct request_queue *q, request_fn_proc *rfn,
spinlock_t *lock)
{
if (!q)
return NULL;
if (blk_init_free_list(q))
return NULL;
q->request_fn = rfn;
q->prep_rq_fn = NULL;
q->unprep_rq_fn = NULL;
q->queue_flags = QUEUE_FLAG_DEFAULT;
if (lock)
q->queue_lock = lock;
blk_queue_make_request(q, blk_queue_bio);
q->sg_reserved_size = INT_MAX;
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
return NULL;
}
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
q->nr_requests = BLKDEV_MAX_RQ;
q->make_request_fn = mfn;
blk_queue_dma_alignment(q, 511);
blk_queue_congestion_threshold(q);
q->nr_batching = BLK_BATCH_REQ;
blk_set_default_limits(&q->limits);
blk_queue_max_hw_sectors(q, BLK_SAFE_MAX_SECTORS);
q->limits.discard_zeroes_data = 0;
blk_queue_bounce_limit(q, BLK_BOUNCE_HIGH);
}
上記のエレベータ関数をコールするのが、generic_make_request()になります。
generic_make_request_checks()でbioの設定値が正しいチェックしたり、場合によってはフラッシュフラグ設定されてりとかで、requestに投入するかどうのかのチェックが行われます。またパーティションにかかるブロック位置を修正します。
current->bio_listですが(以前は無かったような)、そこにはrequest_queueがリストされています。まずそこでリストされているrequest_queue下のrequestにbioをマージできるかチェックしています。そして後半部で、このcurrent->bio_listから、bioを取り出して、デバイスのrequest_queueにマージするようになっています。
デバイスのrequest_queueの参照はロックを必要とします。bio単位に毎回ロックを取得するのは効率的でありません。したがってまずカレントプロセス下で管理するリストに蓄えておくと言うことです。この操作はロックを取得する必要はありません。そして実際のロックを必要とする操作で、一気に処理してしまおうという事のようです。とりあえず、bioをrequest_queueに投入するのは、q->make_request_fn(q, bio)となります。これはblk_queue_bio()です。
void generic_make_request(struct bio *bio)
{
struct bio_list bio_list_on_stack;
if (!generic_make_request_checks(bio))
return;
if (current->bio_list) {
bio_list_add(current->bio_list, bio);
return;
}
BUG_ON(bio->bi_next);
bio_list_init(&bio_list_on_stack);
current->bio_list = &bio_list_on_stack;
do {
struct request_queue *q = bdev_get_queue(bio->bi_bdev);
q->make_request_fn(q, bio);
bio = bio_list_pop(current->bio_list);
} while (bio);
current->bio_list = NULL; /* deactivate */
}
blk_queue_bio()でbioのrequest_queueへの投入の処理となります。まずblk_queue_bounce()で必要ならバウンスバッファが割り当てられます。これはbioのバッファページがDMA領域でない場合、bioの複製を作成し、そのバッファに、メモリプール下のDMAのページを割当てています。後はbioのセクタ位置から、request_queue下に、bioのセクタ位置が隣接するrequestを見付だし、見つかったら、そのrequestを拡張子します。見つからなければ、新たにrequestを作成し、そこにbioを登録して、request_queueにリストします。
void blk_queue_bio(struct request_queue *q, struct bio *bio)
{
const bool sync = !!(bio->bi_rw & REQ_SYNC);
struct blk_plug *plug;
int el_ret, rw_flags, where = ELEVATOR_INSERT_SORT;
struct request *req;
unsigned int request_count = 0;
blk_queue_bounce(q, &bio);
:
el_ret = elv_merge(q, &req, bio);
if (el_ret == ELEVATOR_BACK_MERGE) {
if (bio_attempt_back_merge(q, req, bio)) {
if (!attempt_back_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out_unlock;
}
} else if (el_ret == ELEVATOR_FRONT_MERGE) {
if (bio_attempt_front_merge(q, req, bio)) {
if (!attempt_front_merge(q, req))
elv_merged_request(q, req, el_ret);
goto out_unlock;
}
}
:
get_rq:
rw_flags = bio_data_dir(bio);
if (sync)
rw_flags |= REQ_SYNC;
req = get_request_wait(q, rw_flags, bio);
if (unlikely(!req)) {
bio_endio(bio, -ENODEV); /* @q is dead */
goto out_unlock;
}
init_request_from_bio(req, bio);
:
:
out_unlock:
spin_unlock_irq(q->queue_lock);
}
}