ramfs
Rev.5を表示中。最新版はこちら。
ramfsというのは、Linuxの要素コンポーネントであるVFSとページキャッシュで成せる技で、ユーザプロセスが見るファイル構造はVFSのそれを見ていて、VFSのストレージはページ゙キャッシュと見るとramfsの構造が見えてきます。ext2/ex3のような物理デバイス下のファイルシステムも、VFSから見ると、ある意味RAMディスクと言えなくはなく、物理デバイスは、そのバックアップと言う風に見ることができます。ramfsはfile-mmu.c(file-nommu.c)/inode.c/internal.hのファイルから構成されていて、file-mmu.c/file-nommu.cはinodeのコールバック関数を定義するだけで、実質的な処理はinode.cの11個の関数からのみで実現しています。file-mmu.c/file-nommu.cにつてはmmapにかかるコールバック関数が異なるようです。
VFSではファイルオープン/作成において、file構造体が作成されます。(ファイルIDはこの構造体のある配列のインデックスです。)file構造体を作成する時、かかるファイルのinodeを取得することになるわけですが、この時作成する種別に応じて(ファイル/ディレクトリ/リンク)適切なコールバック関数がinodeに設定されます。
これがfile-mmu.cで定義されている各構造体です。なおramfs_dir_inode_operationsにつてはmmuに依存しないと言う事のようでinode.cで共通の構造体として定義されています。
const struct file_operations ramfs_file_operations
const struct inode_operations ramfs_file_inode_operations
static const struct inode_operations ramfs_dir_inode_operations
const struct address_space_operations ramfs_aops
struct file_operationsはファイルの読み書きするための関数群で、inode_operationsはファイルを削除したりinodeを操作する処理の関数群です。なおinode_operationsにはファイル用とディレクトリ用それぞれ有しています。file_operationsは物理デバイスからの読み書きをする処理を定義します。
file_operationsの読み書き処理コールバック関数は、generic_file_aio_read/geric_file_aio_writeとramfsだからと言うのでなく、extファイルシステムでもカーネル内の共通の関数がコールされるよう設定しています。これはファイルの読み書きはまずキャッシュから読み書きし、キャッシュになければfile_operationsで設定されるコールバック関数がコールされるようになっているからです。
inode.cで実装されている関数群では、スーパブロックとinode取得にかかる処理しかありません。ramfsはキャッシュへの読み書きで完結するため、デバイスに依存する読み書き処理は必要ないからです。
ramfs_get_inode
ramfs_mknod
ramfs_mkdir
ramfs_create
ramfs_symlink
ramfs_fill_super
ramfs_get_sb
rootfs_get_sb
init_ramfs_fs
exit_ramfs_fs
module_init
ramfs_mknod/ramfs_mkdir/ramfs_create/ramfs_symlinkはディレクトリー用のinode_operationsに設定される関数です。ramfs_get_inodeはこれれの関数の下位の処理を行うものです。
new_inode関数でramfsのスーパブロックからinodeをアロケートし、通常(S_IFREG)、ディレクトリ(S_IFDIR)、リンク(S_IFLNK)に応じて、inode->i_op、inode->i_fopに、それぞれのコールバック関数群の構造体が設定されています。
struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev) { struct inode * inode = new_inode(sb); if (inode) { inode->i_mode = mode; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; inode->i_blocks = 0; inode->i_mapping->a_ops = &ramfs_aops; inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info; mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { default: init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_op = &ramfs_file_inode_operations; inode->i_fop = &ramfs_file_operations; break; case S_IFDIR: inode->i_op = &ramfs_dir_inode_operations; inode->i_fop = &simple_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); break; case S_IFLNK: inode->i_op = &page_symlink_inode_operations; break; } } return inode; }ramfsでもスーパブロックは必要です。これはファイルシステムを登録する時のパラメータとする必要がり、スーパブロック内にスーパブロックを操作する関数sper_operations構造、とかマウントしているdentryの情報を保持するためです。
init_ramfs_fsはmountコマンドでramfsを登録する時に呼ばれます。init_rootfsはシステム立ち上げ時の一時ルートとしてマウントする時に呼ばれます。両者の違いはマウントフラグでMS_NOUSERが設定されるかどうかの違いだけです。
init_ramfs_fs/init_rootfsでramfsを登録するとスパーブロックが作成されます。本来は実デバイスのスーパブロックを読み込んで設定するわけですが、ramfsは以下のようなスパブロックを作成しています。
static int ramfs_fill_super(struct super_block * sb, void * data, int silent) { struct inode * inode; struct dentry * root; sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = RAMFS_MAGIC; sb->s_op = &ramfs_ops; sb->s_time_gran = 1; inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); if (!inode) return -ENOMEM; root = d_alloc_root(inode); if (!root) { iput(inode); return -ENOMEM; } sb->s_root = root; return 0; }sb->s_op = &ramfs_opsでinode取得時にコールされるコールバック関数が、super_operations構造体のメンバーalloc_inodeとしてramfs_ops定義されています。これがnew_inode関数でコールされることになります。しかしramfsではこのメンバーは定義されていません。この場合スラブキャッシュからinode構造体のメモリを取得することになります。
物理デバイスとのやり取りは、スーパブロックではsuper_operations ramfs_ops、通常の場合struct address_space_operations ramfs_aopsのコールバック関数がコールされます。
通常の場合ramfs_get_inode関数で、inode->i_mapping->a_ops = &ramfs_aopsとしています。ramfs_aopsではramfsとして実装されていません。カーネル内の関数がコールされることになります。本関数は書き込むデータがキャッシュ上に無い時によばれます。従ってsimple_write_begin/simple_write_endはキャッシュページを確保し、そこに書き込むデータを設定するだけです。
simple_readpageも同じで、対象データがキャッシュが無い時に呼ばれるわけですが、RAMディスクの性格上、readするというのはすぜにwriteしていると言うことです。そうなるのそのデータは必ずキャッシュ上にあるわけですから本コールバック関数は必要ないと思われます。simple_readpageの処理はページを確保しそれをNULLで初期化してそのページを返すだけです。これはファイルホールを考慮しての処理化と解釈しています。
const struct address_space_operations ramfs_aops = { .readpage = simple_readpage, .write_begin = simple_write_begin, .write_end = simple_write_end, .set_page_dirty = __set_page_dirty_no_writeback, }スーパブロックのramfs_opsは、ramfs_fill_super関数でsb->s_op = &ramfs_opsで設定されています。定義されているコールバックは以下の2つです。
static const struct super_operations ramfs_ops = { .statfs = simple_statfs, .drop_inode = generic_delete_inode, };simple_statfsはファイルシステムのスーパブロック内の情報を取得するもので、generic_delete_inodeはinodeを削除する時に呼ばれます。スーパブロック内でそのファイルシステムの全inodeを管理するリストを有していてその処理を行っています。
このようにramfsは完全にページキャッシュに依存した実装になっており、従ってかかる機能はページキャッシュのそれに依存するとも言えます。ページキャッシュはメモリーの許す限り獲得される(たぶん)。したがってramfsのサイズは不定で、ユーザのあずかり知らぬところで物理メモリの許す限り増えていくと言う訳です。
追伸
我が美しき祖国、東関東大地震で被災された方へ、謹んで哀悼の意を表します。