どうやってI/Oの完了をうけとっているか(準備編)

だんだん、だれてきましたが、今度はどのようにio_submit()で発行したI/Oが完了したか、どうかを知るための仕組みを追っかけてみます。

簡単にこの仕組みを説明すると、まずio_setup()で非同期I/Oを処理するための特別なメモリをとります。io_submit()でリクエストされたI/Oはカーネルスレッドで実行され、完了した際にこの特別なメモリに対してアクセスをし、完了したことを通知します。

まず、io_setup()でどのようなことをしているのかを見てみます。
io_setup()関数は、第一引数に発行する最大のI/O要求数、第二引数にio_context_tのポインタを渡され、第一引数の数だけ非同期I/Oを処理できるように準備をし、AIOコンテキストを示すオブジェクトをさすアドレスを第二引数に格納します。AIOコンテキストとは、I/Oコンテキストの一種で、kioctxオブジェクトと対応しています。

kioctxのメンバには、AIOリングと呼ばれるデータ構造があります。このデータは、kioctxのring_info.mmap_baseというメンバに格納されます。このAIOリングは、循環リストになっており、カーネルがこのリングにI/Oの完了報告を書き込んでいきます。

では、このAIOリングを作成している周辺のソースを見ていきます。

io_setup()から、sys_io_setup()が呼ばれ、ioctx_alloc()が、io_setup()の第一引数の値を引数として呼ばれ、そのなかで、kmem_cache_zalloc()を使い、kioctxを作成します。そして、kioctxのメンバmax_reqsに引数を格納した後に、aio_setup_ring()で、AIOリングを作成します。

aio_setup_ring()のなかで、ring_info.mmap_baseにたいし、do_mmap()関数でプロセスに対し、無名メモリリージョンを作成します。

/* kernel 2.6.23 fs/aio.cより */
/* aio_setup_ring()内 */
/* infoには、koictxのring_infoのアドレスが代入されている */
133     info->mmap_base = do_mmap(NULL, 0, info->mmap_size,
134                   PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE,
135                   0);

カレントプロセスに対し、リニアアドレスの区間にメモリを作成し初期化を行ってくれます。リニアアドレス区間にメモリがとられることにより、プロセス空間からアクセスできるようになります。
通常、ユーザー空間から渡されたメモリに対し、カーネル空間がアクセスを使用とすると、コストが大きいのですが(カーネルは、カーネル自身はすごく信用をしておりBUGが無いものとして、ものを扱いますが、ユーザー空間のものは全然信用をしておらず、チェックが多くなります)、これにより、ユーザー空間からアドレスをもらうだけで、指定されたメモリに安価にアクセスを行うことができるのです。

あとは、kioctxのuser_idメンバに、このAIOリングのベースアドレスが格納され、これがio_setup()の第二引数に入ることになります。

/* kernel 2.6.23 fs/aio.cより */
/* aio_setup_ring()内 */
 154     ctx->user_id = info->mmap_base;

 /* sys_io_setup()内 ctxpは第二引数をさす */
 1266         ret = put_user(ioctx->user_id, ctxp);

put_user()は、カーネル空間にある値をプロセスアドレス空間に書き込むためのマクロです。反対は、get_user()マクロになります。

では、プロセス自身はどうやってこのリニアアドレス空間におかれたAIOリングの場所を覚えているのでしょうか?

それは、io_setup()でプロセスのメモリディスクリプタ構造体のメンバにあるioctx_listにAIOリングを記憶します。これにより、カーネル空間で簡単にアクセスすることが簡単に行えるのです。

/* kernel 2.6.23 fs/aio.cより */
/* ioctx_alloc()内 */
217     mm = ctx->mm = current->mm;
/* ctxは関数内で作成したkioctxのポインタが格納されている */
246     mm->ioctx_list = ctx;

currentマクロにより、現在のCPUで動作しているプロセスのプロセスディスクリプタを取得し、そのメンバのメモリディスクリプタを取得した後、そのioctx_listに代入しています。