バウンスバッファー
デバイスブロックとデバイスのやり取りは、bioに設定されているページとDMAで行っています。もしそのページがハイメモリ等の、デバイスとDMAでやり取りできないページだと、元のbioを複製した物に、DMAとやり取りするページを割り当てる事でDMA転送を行います。このbioをバウンスバッファーと言うそうです。新たに作成したbioには、bi_privateメンバーに、元のbioが設定されており、読み込みなら、新たに設定したブロックIO終了コールバック関数で、DMA転送されたデータを、元のbioのページに転送する事になります。
blk_queue_bounce()でバウンスバッファーのbioを作成します。bio_origが元のbioです。bio_empty_barrier()はbioがバリアがどうかチェックします。ブロックレベルのフラッシュみたいなものです。あるブロック群が完全に書き込まれてからでないと、次のブロック群は書き込むことができないというものです。これは、ジャーナルのように書き込み失敗時の復旧させるケースを想定しての機能です。 このbioは一種のフラグとして扱いとなります(たぶん)。
request_queue->bounce_gfpがGFP_DMAのページである必要がない場合(ZONE_NORMAL)、スラブをpage_poolとして、GFP_DMAのページである必要の場合、スラブをisa_page_poolとして__blk_queue_bounce()をコールする事になります。
なお、GFP_DMAのページである必要がない場合、blk_max_pfnはnormalページのマックス値です。もしデバイスがサポートできるページフレームがそれより大きいなら、そのページとDMA処理できる事になり、バウンスバッファーは不要です。
バウンスバッファーが必要な場合、bio_alloc()でbioを割り当てます。(*bio_orig)->bi_vcntは配列bio_vecの数です。そしてtoにpoolで指定されたスラブからページを割り当て、元のbio_origの各種設定で更新していきます。
もし書き込みなら、bio_origのページの内容を、バウンスバッファーのページに複写する必要があります。to->bv_page/from->bv_pageのリニアアドレスを求めて、memcpyしています。なおfrom->bv_pageでkmap()をコールしているのは、ページがハイメモリを想定してのことです。
次の__bio_for_each_segmentは、複数のbio_vecを有するケースで、バウンスバッファーを必要としないbio_vecの処理です。これは元のbio_origの対応するbio_vecの情報で設定するだけです。バウンスバッファーが必要なbio_vecは、上記ループでto->bv_pageが設定されていて、NULLという事はバウンスバッファーが必要でないと言うことです。
最後にbio自身を、bio_origのそれで設定していきます。なおこの時NOMALかDMAゾーンかによって、また読み込みか書き込みかによって、bi_end_ioのコールバック関数を適切に設定していきます。このコールバック関数でバウンスバッファーbioの開放、および必要なら、バウンスバッファーの内容をbio_origのバッファーに転写する事になります。
最後に、bi_end_ioのコールバック関数が参照するため、バウンスバッファーbioのbi_privateに元のbioを設定し、オリジナルのbioにバウンスバッファーbioを上書きすることで、以降bio_origはバウンスバッファーを有したbioと言う事です。
blk_queue_bounce()でバウンスバッファーのbioを作成します。bio_origが元のbioです。bio_empty_barrier()はbioがバリアがどうかチェックします。ブロックレベルのフラッシュみたいなものです。あるブロック群が完全に書き込まれてからでないと、次のブロック群は書き込むことができないというものです。これは、ジャーナルのように書き込み失敗時の復旧させるケースを想定しての機能です。 このbioは一種のフラグとして扱いとなります(たぶん)。
request_queue->bounce_gfpがGFP_DMAのページである必要がない場合(ZONE_NORMAL)、スラブをpage_poolとして、GFP_DMAのページである必要の場合、スラブをisa_page_poolとして__blk_queue_bounce()をコールする事になります。
なお、GFP_DMAのページである必要がない場合、blk_max_pfnはnormalページのマックス値です。もしデバイスがサポートできるページフレームがそれより大きいなら、そのページとDMA処理できる事になり、バウンスバッファーは不要です。
#define bio_barrier(bio) ((bio)->bi_rw & (1 << BIO_RW_BARRIER))
#define bio_empty_barrier(bio) (bio_barrier(bio) && !(bio)->bi_size)
void blk_queue_bounce(struct request_queue *q, struct bio **bio_orig)
{
mempool_t *pool;
if (bio_empty_barrier(*bio_orig))
return;
if (!(q->bounce_gfp & GFP_DMA)) {
if (q->bounce_pfn >= blk_max_pfn)
return;
pool = page_pool;
} else {
BUG_ON(!isa_page_pool);
pool = isa_page_pool;
}
__blk_queue_bounce(q, bio_orig, pool);
}
__blk_queue_bounce()で実際のバウンスバッファーのbioを割り当てます。bio_origのbio_vecを取り出し、そのページフレーム番号がrequest_queueの最大ページフレーム番号より大きい場合のみ、バウンスバッファーが必要です。なおbio_vec取り出しで、bio_for_each_segmentでループしているのは、ブロックサイズがページサイズより大きい場合があるからです。(通常は1つ)バウンスバッファーが必要な場合、bio_alloc()でbioを割り当てます。(*bio_orig)->bi_vcntは配列bio_vecの数です。そしてtoにpoolで指定されたスラブからページを割り当て、元のbio_origの各種設定で更新していきます。
もし書き込みなら、bio_origのページの内容を、バウンスバッファーのページに複写する必要があります。to->bv_page/from->bv_pageのリニアアドレスを求めて、memcpyしています。なおfrom->bv_pageでkmap()をコールしているのは、ページがハイメモリを想定してのことです。
次の__bio_for_each_segmentは、複数のbio_vecを有するケースで、バウンスバッファーを必要としないbio_vecの処理です。これは元のbio_origの対応するbio_vecの情報で設定するだけです。バウンスバッファーが必要なbio_vecは、上記ループでto->bv_pageが設定されていて、NULLという事はバウンスバッファーが必要でないと言うことです。
最後にbio自身を、bio_origのそれで設定していきます。なおこの時NOMALかDMAゾーンかによって、また読み込みか書き込みかによって、bi_end_ioのコールバック関数を適切に設定していきます。このコールバック関数でバウンスバッファーbioの開放、および必要なら、バウンスバッファーの内容をbio_origのバッファーに転写する事になります。
最後に、bi_end_ioのコールバック関数が参照するため、バウンスバッファーbioのbi_privateに元のbioを設定し、オリジナルのbioにバウンスバッファーbioを上書きすることで、以降bio_origはバウンスバッファーを有したbioと言う事です。
static void __blk_queue_bounce(struct request_queue *q, struct bio **bio_orig,
mempool_t *pool)
{
struct page *page;
struct bio *bio = NULL;
int i, rw = bio_data_dir(*bio_orig);
struct bio_vec *to, *from;
bio_for_each_segment(from, *bio_orig, i) {
page = from->bv_page;
if (page_to_pfn(page) <= q->bounce_pfn)
continue;
if (!bio)
bio = bio_alloc(GFP_NOIO, (*bio_orig)->bi_vcnt);
to = bio->bi_io_vec + i;
to->bv_page = mempool_alloc(pool, q->bounce_gfp);
to->bv_len = from->bv_len;
to->bv_offset = from->bv_offset;
inc_zone_page_state(to->bv_page, NR_BOUNCE);
if (rw == WRITE) {
char *vto, *vfrom;
flush_dcache_page(from->bv_page);
vto = page_address(to->bv_page) + to->bv_offset;
vfrom = kmap(from->bv_page) + from->bv_offset;
memcpy(vto, vfrom, to->bv_len);
kunmap(from->bv_page);
}
}
if (!bio)
return;
blk_add_trace_bio(q, *bio_orig, BLK_TA_BOUNCE);
__bio_for_each_segment(from, *bio_orig, i, 0) {
to = bio_iovec_idx(bio, i);
if (!to->bv_page) {
to->bv_page = from->bv_page;
to->bv_len = from->bv_len;
to->bv_offset = from->bv_offset;
}
}
bio->bi_bdev = (*bio_orig)->bi_bdev;
bio->bi_flags |= (1 << BIO_BOUNCED);
bio->bi_sector = (*bio_orig)->bi_sector;
bio->bi_rw = (*bio_orig)->bi_rw;
bio->bi_vcnt = (*bio_orig)->bi_vcnt;
bio->bi_idx = (*bio_orig)->bi_idx;
bio->bi_size = (*bio_orig)->bi_size;
if (pool == page_pool) {
bio->bi_end_io = bounce_end_io_write;
if (rw == READ)
bio->bi_end_io = bounce_end_io_read;
} else {
bio->bi_end_io = bounce_end_io_write_isa;
if (rw == READ)
bio->bi_end_io = bounce_end_io_read_isa;
}
bio->bi_private = *bio_orig;
*bio_orig = bio;
}





