ファイルサーバの雛形
何度も書いているが,Plan9ではすべての資源,サービスはファイルに抽象化される*1.そして,資源をファイルに抽象化する実体がファイルサーバである.UNIXとは異なり,ファイルサーバはカーネル内で動作するのではなく,ユーザアプリケーションである.今日はファイルサーバの実装について見ていこう.
まず,ファイルサーバのコードの雛型を示す.たったこれだけのコードだが,ちゃんとファイルサーバとして動作する*2.アプリとファイルサーバ間はパイプによって接続され,9Pプロトコルを使っておしゃべりする(正確にはカーネルを介して通信するので,アプリとカーネル間はシステムコールで,カーネルとファイルサーバ間は9Pプロトコルで通信することになる).このとき,パイプ接続を確立するために,srvデバイスが使われる.これは名前つきパイプのようなものだと思えばよい(詳細は「pipe(2),#|,#s」参照).
このコードで一番重要なデータ構造は,ファイルサーバが管理するファイルツリー,ファイル記述子,ファイル操作関数を保持するSrv構造体である.ファイルサーバはアプリからのリクエストをSrv.infdから入力して,レスポンスをSrv.outfdへ出力するという流れになる.ファイル操作関数は,「9Pプロトコルの概要」で説明したオペコードに対応したコールバック関数である.これら関数のプロトタイプはすべてvoid xxx(Req *r)の形式になっている.リクエストはReq構造体として渡され,関数の終わりにはレスポンスを返すために,respond(2)を呼ぶ.
#include <u.h> #include <libc.h> #include <fcall.h> #include <thread.h> #include <9p.h> void myread(Req *r) { respond(r, nil); } void mywrite(Req *r) { respond(r, nil); } void myopen(Req *r) { respond(r, nil); } void mycreate(Req *r) { respond(r, nil); } void destroyfile(File *f) { } Srv fs = { .open = myopen, .read = myread, .write = mywrite, .create = mycreate, }; void main(int argc, char **argv) { fs.tree = alloctree(nil, nil, DMDIR|0777, destroyfile); createfile(fs.tree->root, "ctl", nil, 0666, nil); createfile(fs.tree->root, "data", nil, 0666, nil); postmountsrv(&fs, "mysrv", nil, MREPL|MCREATE); exits(0); }
main関数では,alloctree(2)で自分が提供するファイルツリーを確保し,createfile(2)でdataとctlという名前のファイルを作っている*3.
postmountsrvはサーバプロセスをforkするためのラッパーライブラリで,子プロセスをrforkし,入出力用のファイル記述子を初期化する.マウントポイント(第三引数)は,明示的に指定しない場合,/srv/mysrvになる.もう少し具体的に書くと,まず通信用のパイプを作り,その一端をinfd,outfdに設定し,もう一端のファイル記述子(srvfd)を/srv/mysrvにpostする*4.そして,子プロセスはsrv(2)を実行し,イベントループに入る.
上記のコードからは9Pプロトコルをしゃべっているように感じられないが,大部分のプロトコルハンドリングはsrv(lib9p)で処理され,readやwriteなど,ファイルサーバ依存の部分だけがコールバックされる.最後に,親プロセスはexitsで終了する.
では,コンパイルしてみよう.
% 8c mysrv.c % 8l -o mysrv mysrv.8
mysrvを実行すると,/srv/mysrvというファイルができる.さらに,psを見ると,mysrvプロセスがread待ち状態であることがわかる.
% mysrv % ls -l /srv/mysrv --rw------- s 0 oraccha oraccha 0 Mar 1 2006 /srv/mysrv % ps | grep mysrv oraccha 296 0:00 0:00 116K Pread mysrv
/srv/mysrvはsrv(3)デバイスであり,これを適当なディレクトリにmountすることによって,ファイルサーバが提供する資源,サービスに対するファイルとしてのインタフェースが提供される.一般的にmount先は/nディレクトリ以下になる.
% mount /srv/mysrv /n/mysrv % cd /n/mysrv % ls -l --rw-rw-rw- M 42 oraccha oraccha 0 Sep 9 19:47 ctl --rw-rw-rw- M 42 oraccha oraccha 0 Sep 9 19:47 data
ファイルサーバを終了するときは,/n/mysrvをunmountして,/srv/mysrvをrmする.これでsrvのイベントループを抜け,ファイルサーバプロセスが終了する.
% unmount /n/mysrv % rm /srv/mysrv
*1:サービスをファイルに抽象化というと最初はぴんとこなかったが、Webサービスが一般的になった今日的には理解しやすくなったかもしれない。
*2:Linuxでprocfsを操作するモジュールを書いたことがあれば,あんな感じだと言えば想像できるだろうか.
*3:「ネットワークはどう抽象化されるのか?」で見たように,一般的なファイルサーバは,そのインタフェースとして,コントロール用のctlファイルと,データをやり取りするdataファイルを提供する.
*4:/srvは通常のファイルとちょっと性格が異なるので,最初はなかなか理解できなかった./srvはサーバが通信相手を待ち受けるためにパイプのファイル記述子を登録(これをpostと呼ぶ)しておく掲示板のようなものだ.例えば,クライアントが/srv/mysrvをopenすると,戻り値としてサーバがpostしたファイル記述子が返ってくる.こうすることでサーバ,クライアント間でパイプを介した通信が可能になる.名前付きパイプの一種だが,ネットワーク越しにも通信できる.