一つのIOの伝奇の一生(4)----ブロックの設備buffer cacheのメカニズム
ブロックデバイスbuffer cacheメカニズム
EXT 3ファイルIOが新しい旅に出る前に、EXT 3ファイルIOの仲間を紹介する必要があります.彼らは同じ旅に出ることになります.ただ、この仲間はEXT 3ファイルシステムのすばらしさを経験したことがないが、別の少し異なる風情を味わった.このパートナーは、ブロックデバイスの書き込み操作時に作成され、ブロックデバイスIOと呼ぶことができます.
多くのアプリケーションではブロックデバイスの操作が直接行われ、私たちがよく使うコマンドddはブロックデバイスの読み書きを行うことができます.たとえば、dd if=/dev/sda of=./abc bs=512 count=1コマンドは、/dev/sdaデバイスの最初のセクタを現在のディレクトリのabcファイルに読み込むことを実現することができる.ここまで読んで、1つのブロックデバイスファイルと1つのEXT 3ファイルにアクセスするのはいったいどんな本質的な違いがあるのだろうか.違いといえば、やはり1、2を挙げることができます.
1)EXT 3は汎用的なファイルシステムであり,ディスク上のデータの分散はメタデータの形式で記述されている.したがって、1つのEXT 3ファイルに対する読み書き操作は、メタデータとファイルデータの2種類に及ぶ.この2つのデータ型には相関があるため、EXT 3は、両者の間の動作の原子性を保証するために、通常、ログ方式で動作の原子性を保証する.
2)ブロックデバイスはEXT 3ファイルシステムほど複雑ではなく、書き込まれたデータは直接ディスクに保存され、メタデータ情報は一切存在しない.
したがって,ブロックデバイスとEXT 3ファイルシステムを比較すると,データの読み書き方式に差がある.両方の共通点は、ディスクへのアクセスのパフォーマンスに問題があり、メモリを使用してディスクのパフォーマンスを最適化できることです.EXT 3はpage cacheを用いてIO性能を最適化し、ブロック装置もpage cacheを用いて性能を最適化することができる.前述したように、各EXT 3ファイルにはradix treeがこのファイルの内容を維持するためのpage cacheがあり、裸のデバイスはEXT 3ファイルに等価であり、同じようにradix treeを使用してブロックデバイス上のデータをcacheメンテナンスすることができる.だから、この二つの間には大きな共通点があります.
そのため、Linuxは、ブロックデバイスIOの動作を実現する際にEXT 3と同様であり、ブロックデバイスにアクセスするこのサブシステムをbdevファイルシステムと呼ぶことができる.VFSはすべてのタイプのファイルアクセス機能を実現し、アプリケーションが呼び出すAPIは完全に同じである.ブロック・デバイスへのアクセスでは、VFSレイヤを介してbdevファイル・システムが提供する関連関数が呼び出されます.
ブロックデバイスを初期化するとinit_が呼び出されますspecial_inode関数は、このブロックデバイスのinodeを初期化します.void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
/* */
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
/* */
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lu
", mode, inode->i_sb->s_id,
inode->i_ino);
}
ユーザプログラムがopen関数を呼び出して指定したブロックデバイスを開くと、fileオブジェクトが初期化され、上記のinodeでfileオブジェクトが初期化されます.したがって、fileオブジェクトのファイル操作方法により、汎用的なブロックデバイス操作関数を呼び出すことができる.Linuxで定義されている汎用ブロックデバイスの操作関数は、次のように説明されています.const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = blkdev_aio_write,
.mmap = generic_file_mmap,
.fsync = blkdev_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
したがって、1つのブロックデバイスの書き込み操作に対してwrite関数によってカーネルに陥りdo_を実行するsync_write.do_sync_write関数でblkdev_が呼び出されますaio_write関数は、ブロックデバイスの非同期書き込みプロセスを実現する.blkdev_aio_write関数のコアは_generic_file_aio_write.ここまで,ブロックデバイスのすべての呼び出し関数はEXT 3ファイルシステムとほぼ同じであることが分かった.前に分析しましたが、_generic_file_aio_writeは2つのケースに分けられます:Direct_IOとBuffer_IO.buffer_についてIOは重要ないくつかのステップに分けられます.
1)write_begin
2)copy buffer
3)write_end
write_beginとwrite_end関数は、bdevファイルシステムについて、各クラスのファイルシステムに具体的に関連する方法であり、以下のように定義される.static const struct address_space_operations def_blk_aops = {
.readpage = blkdev_readpage,
.writepage = blkdev_writepage,
.write_begin = blkdev_write_begin,
.write_end = blkdev_write_end,
.writepages = generic_writepages,
.releasepage = blkdev_releasepage,
.direct_IO = blkdev_direct_IO,
};
EXT 3ファイルの場合、write_beginはログ操作を行い、ブロックデバイスファイルシステムにはこのような操作はなく、pageページの初期化に関するいくつかの作業しか行われません.write_の場合end関数では、EXT 3ファイルシステムはログを消去し、writebackデーモンスレッドにデータの書き込みを通知する必要があります.ブロックデバイスファイルシステムの場合write_end関数の主な仕事はpageページを汚いと識別し、このブロックデバイスの汚いページを処理するために書き込みスレッドに通知することです.ダーティ・ページの設定を実装する関数の説明は、次のとおりです.static int __block_commit_write(struct inode *inode, struct page *page,
unsigned from, unsigned to)
{
unsigned block_start, block_end;
int partial = 0;
unsigned blocksize;
struct buffer_head *bh, *head;
blocksize = 1 << inode->i_blkbits;
for(bh = head = page_buffers(page), block_start = 0;
bh != head || !block_start;
block_start=block_end, bh = bh->b_this_page) {
block_end = block_start + blocksize;
if (block_end <= from || block_start >= to) {
if (!buffer_uptodate(bh))
partial = 1;
} else {
set_buffer_uptodate(bh);
/* page inode , */
mark_buffer_dirty(bh);
}
clear_buffer_new(bh);
}
/*
* If this is a partial write which happened to make all buffers
* uptodate then we can optimize away a bogus readpage() for
* the next read(). Here we 'discover' whether the page went
* uptodate as a result of this (potentially partial) write.
*/
if (!partial)
SetPageUptodate(page);
return 0;
}
全体の分析から見ると、裸のブロックデバイスの書き込み操作におけるCacheのメカニズム、原理、EXT 3には本質的な違いはない.radix tree管理のpage cacheを採用しています.Direct_を使用しない場合IO方式では、まずpage cacheにデータを書き込み、writebackメカニズムでディスクにデータを返信します.メカニズム全体が全く同じです.異なる点は、EXT 3とブロックデバイスのキャッシュブロックサイズが異なることである.EXT 3の場合、キャッシュブロックサイズはpage sizeであり、ブロックデバイスの場合、キャッシュブロックサイズは一定のポリシーで得られる.buffer cacheのキャッシュブロックサイズについては、「LinuxにおけるBuffer cacheのパフォーマンスに関する問題を探る」を参照してください.
ブロックデバイスとEXT 3ファイルは大きく異なるように見えるが,システムが解決すべき問題は基本的に類似しているため,IO処理のメカニズムでは類似している.よし!本題に戻ると、これまでEXT 3ファイルIOおよびブロックデバイスIOの準備が完了し、writeback書き込みメカニズムはこれらのIOをすべて下位デバイスに書き込みました.これらのIOはいずれも短いpage cacheを離れ、一緒にブロックデバイス層に足を踏み入れ、ブロックデバイス層、すなわち公平で公平ではないスケジューリング処理過程に直面する.
本文は“記憶の道”のブログから出て、転載して作者と連絡してください!
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
/* */
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
/* */
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lu
", mode, inode->i_sb->s_id,
inode->i_ino);
}
const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = blkdev_aio_write,
.mmap = generic_file_mmap,
.fsync = blkdev_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
static const struct address_space_operations def_blk_aops = {
.readpage = blkdev_readpage,
.writepage = blkdev_writepage,
.write_begin = blkdev_write_begin,
.write_end = blkdev_write_end,
.writepages = generic_writepages,
.releasepage = blkdev_releasepage,
.direct_IO = blkdev_direct_IO,
};
static int __block_commit_write(struct inode *inode, struct page *page,
unsigned from, unsigned to)
{
unsigned block_start, block_end;
int partial = 0;
unsigned blocksize;
struct buffer_head *bh, *head;
blocksize = 1 << inode->i_blkbits;
for(bh = head = page_buffers(page), block_start = 0;
bh != head || !block_start;
block_start=block_end, bh = bh->b_this_page) {
block_end = block_start + blocksize;
if (block_end <= from || block_start >= to) {
if (!buffer_uptodate(bh))
partial = 1;
} else {
set_buffer_uptodate(bh);
/* page inode , */
mark_buffer_dirty(bh);
}
clear_buffer_new(bh);
}
/*
* If this is a partial write which happened to make all buffers
* uptodate then we can optimize away a bogus readpage() for
* the next read(). Here we 'discover' whether the page went
* uptodate as a result of this (potentially partial) write.
*/
if (!partial)
SetPageUptodate(page);
return 0;
}