Linux Kernel(2.6)の実装に関するメモ書き

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)

表2 __generic_file_aio_read()の引数
引数
意味
*iocb 非同期I/Oを管理する構造体
generic_file_read()から呼び出される場合は同期型のkiocbが渡される。
*iov
Read先バッファを示すI/Oベクタ
nr_segs
I/Oベクタのエントリ数
*ppos
読み込み開始オフセット


__generic_file_aio_read()の処理概要
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)


表3 do_generic_file_read()の引数
引数
意味
*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)

表4 do_generic_mapping_read()の引数
引数
意味
*mapping
ファイルのアドレス空間(*1)
*_ra
ファイルの先読み(readahead)管理用データ
*filp
読み込み対象ファイル
*ppos
読み込み開始オフセット
*desc
Read Descriptor
actor
Read完了時のハンドラ
(*1) 各々のファイルは先頭から0ページ目、1ページ目、、、というように物理ページサイズ単位に区切られてディスクキャッシュされる(PageCache)。mappingはファイルのディスクキャッシュを管理するためのもの。詳細はPageCache参照。


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;

(*1) page_cache_readahead()でPageCacheが作られているので、通常はここでpageを取得できる。
(*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 System
PageCache
Read Ahead


最終更新 2007/02/16 18:52:39 - kztomita
(2007/02/06 20:36:32 作成)
添付ファイル
generic_file_read.png - kztomita


リンク
最近更新したページ
検索