generic_file_read()
1. 概要
ファイルシステム上のファイルをReadする汎用ルーチンとしてgeneric_file_read()がある。
generic_file_read()は、、、
- Virtual File Systemのレイヤから呼び出されファイルを読み込む。(*1)
- ext2,ext3などのほとんどのファイルシステムで使用される。(NFSでは使用されない)
- PageCache(ディスクキャッシュ)を介してReadを行う
- PageCacheがなかった場合は、各ファイルシステムのReadルーチンを呼び出してReadを行う。
(*1) 各ファイルシステムのstruct file_operationsのreadに本関数が登録されており、Virtual File Systemからfile->f_op->read()のようにして呼び出される。
VFS,PageCache,各FileSystemとの関係を図1に示す。
図1 generic_file_readの位置づけ
2. 処理概要
2.1 generic_file_read()
ファイルリード処理の大元の関数。
generic_file_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
表1 generic_file_read()の引数
引数 |
意味 |
---|---|
*filp |
読み込み対象ファイル |
*buf |
Readデータを格納するバッファ |
count |
バッファサイズ |
*ppos |
読み込み開始オフセット |
generic_file_read()の処理概要
/* Read先バッファのI/Oベクタ作成 */
local_iov = { .iov_base = buf, .iov_len = count };
init_sync_kiocb(&kiocb, filp);
ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&kiocb);
return ret;
2.2 __generic_file_aio_read()
__generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t *ppos)
引数 |
意味 |
---|---|
*iocb |
非同期I/Oを管理する構造体 generic_file_read()から呼び出される場合は同期型のkiocbが渡される。 |
*iov |
Read先バッファを示すI/Oベクタ |
nr_segs |
I/Oベクタのエントリ数 |
*ppos |
読み込み開始オフセット |
for (seg = 0; seg < nr_segs; seg++) { /* iovの中身のチェック */ } if (filp->f_flags & O_DIRECT) { /* O_DIRECT付きでopenしたファイルのRead処理 */ : } if (count) { for (seg = 0; seg < nr_segs; seg++) { /* I/OベクタからReadディスクリプタ作成 */ desc.written = 0; desc.arg.buf = iov[seg].iov_base; /* Readデータ格納アドレス */ desc.count = iov[seg].iov_len; /* ReadBufferサイズ */ : do_generic_file_read() } }
2.3 do_generic_file_read()
マクロ。filpからmappingと_raを取り出してdo_generic_mapping_read()を呼び出すだけ。
void do_generic_file_read(struct file * filp, loff_t *ppos, read_descriptor_t * desc, read_actor_t actor)
引数 |
意味 |
---|---|
*filp |
読み込み対象ファイル |
*ppos |
読み込み開始オフセット |
*desc |
Read Descriptor ReadBufferのどこにデータを書き込むか、何Byte残っているかを管理する。 |
actor |
Read完了時のハンドラ。 このルーチンでReadデータをKernel→User空間へコピーして、Read Descritorを更新する。 |
2.4 do_generic_mapping_read()
指定ファイルのデータをPageCacheからページを探し、最新版(Uptodate状態)のページがあった場合はそれを返す。PageCacheがなかったり最新状態でなければ、ディスクから読み込みPageCacheに格納して返す。
Readは基本的にpage_cache_readahead()で先読みを行うので、指定データより先のデータに対してもRead I/Oが発生することがある。
void do_generic_mapping_read(struct address_space *mapping, struct file_ra_state *_ra, struct file *filp, loff_t *ppos, read_descriptor_t *desc, read_actor_t actor)
引数 |
意味 |
---|---|
*mapping |
ファイルのアドレス空間(*1) |
*_ra |
ファイルの先読み(readahead)管理用データ |
*filp |
読み込み対象ファイル |
*ppos |
読み込み開始オフセット |
*desc |
Read Descriptor |
actor |
Read完了時のハンドラ |
do_generic_mapping_read()の処理概要
/* ファイルのi-node */ inode = mapping->host; /* Read開始位置をファイル先頭からのページ数に変換 */ index = *ppos >> PAGE_CACHE_SHIFT; /* ページ内でのオフセット */ offset = *ppos & ~PAGE_CACHE_MASK; /* ファイルサイズ */ isize = i_size_read(inode); /* ファイルの最後のページのインデックス */ end_index = (isize - 1) >> PAGE_CACHE_SHIFT; /* 1ページずつ読み込んでいく */ for (;;) { nr = PAGE_CACHE_SIZE; if (index >= end_index) { /* 最後のページに到達した */ if (index > end_index) goto out; /* nr:1〜PAGE_CACHE_SIZE */ nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1; if (nr <= offset) { goto out; } } nr = nr - offset; /* ファイルの先読み * ファイルのindex〜last_indexを先読みする。 * PageCacheにない部分のデータについてI/Oが発生する。 */ if (index == next_index) next_index = page_cache_readahead(mapping, &ra, filp, index, last_index - index); find_page: /* PageCacheからページを取得 (*1) */ page = find_get_page(mapping, index); if (unlikely(page == NULL)) { /* 指定ページがPageCacheになかった。(*2) * no_cached_pageへ飛んでPageCacheを作成する。 */ handle_ra_miss(mapping, &ra, index); goto no_cached_page; } if (!PageUptodate(page)) goto page_not_up_to_date; /* ページはあったが最新でない(*3) */ /* 取得したページキャッシュが最新状態 */ page_ok: /* 最新データ格納されたページが手に入った時の処理。 * (最新のPageCacheが見つかった or Readした) */ : /* actorは引数で指定される。 * generic_file_read()から呼び出される場合は、 * actorはfile_read_actor()となる。 * この関数はページの内容をユーザ空間にコピーする。 */ ret = actor(desc, page, offset, nr); /* 読み込んだ分だけindex,offsetを進める */ offset += ret; index += offset >> PAGE_CACHE_SHIFT; offset &= ~PAGE_CACHE_MASK; /* actorが正常終了してまだReadするデータが * 残っているのなら続ける */ if (ret == nr && desc->count) continue; goto out; page_not_up_to_date: /* 見つかったページの内容が最新(Uptodate状態) * でなかった場合の処理 */ lock_page(page); /* ロックを取得 */ : /* 別コンテキストでReadされている可能性もあるので * 再度、PageCacheが最新状態かチェック */ if (PageUptodate(page)) { goto page_ok; } /* それでもUptodate状態でなければ、以下のRead処理へ */ readpage: /* PageCacheへのRead処理を行う。 */ /* 該当FileSystemのRead処理を呼び出す。 * mapping->a_opsには対応するFileSystemの * address_space_operationsが登録されている。 * 例えばext3上のファイルならext3_ordered_aops * が登録されておりext3_readpage()を呼び出すことになる。 * (*4) */ error = mapping->a_ops->readpage(filp, page); : if (!PageUptodate(page)) { /* まだReadが終わっていない。 */ /* 再度Lockを取得してRead I/Oの完了待ちをする(*5) */ lock_page(page); /* I/O完了 */ if (!PageUptodate(page)) { : goto readpage_error; } /* Uptodate状態になっているのでReadが正常に終了している */ } : : goto page_ok; readpage_error: /* Readエラー */ desc->error = error; goto out; no_cached_page: /* PageCacheがなかった時の処理 * ページを割り当ててPageCacheに登録して * Read処理へ飛ぶ。 */ /* PageCache用のページを新規に取得 */ if (!cached_page) { cached_page = page_cache_alloc_cold(mapping); : } /* PageCacheに登録(*6) */ error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL); : /* Read処理へ */ goto readpage; } out: /* Readしたところまでオフセットを進める */ *ppos = ((loff_t) index << PAGE_CACHE_SHIFT) + offset;
(*2) ここに来たということはpage_cache_readahead()で作成されたPageCacheがメモリ不足などの理由により解放されてしまっていることを示す。handle_ra_miss()を呼び出して、raにキャッシュミスが発生したことを示すフラグを立てておく。これにより、先読み(ReadAhead)の量が少なくなりPageCacheの消費料を抑える。
(*3) PageCacheが存在するが内容が最新(Uptodate)でないということは、ReadAhead(先読み)や他のコンテキストでRead I/Oを開始したが、まだ読み込みが完了していないことになる。
(*4) Pageへのデータ読込み用ハンドラmapping->a_ops->readpageはlock_page()でページをロックした状態で呼び出す。mapping->a_ops->readpageはRead I/Oを開始してI/Oが完了するのを待たずにリターンする。この状態ではまだページのロックは持ったままだが、I/Oが完了するとハンドラmpage_end_io_read()が呼び出されロックが解除される。このため、この処理の延長でunlock_page()は呼び出していない。
(*5) (*4)により、I/Oが完了していなければページのロックが残ったままなので、ここで、再度ロックを取ることでI/O完了までブロックすることになる。
(*6) add_to_page_cache()でPageCacheにページを登録する際、SetPageLocked()でページがロック状態にされる。このため、readpageへ飛ぶまでにlock_page()は不要。
関連ページ
Virtual File SystemPageCache
Read Ahead