ネットワークのファイルへの抽象化

Plan9というオペレーティングシステムでは,Plan9はソケットインタフェースを採用せず,ネットワークに対しても,通常のファイルに対するインタフェース,つまりopen-read-write-closeを使って操作すると言った.

UNIXには/devディレクトリ以下にデバイスファイルが存在するように,Plan9では/net以下がネットワークに対する名前空間(ファイルシステムのツリーを想像してもらえればよいが,Plan9の場合,プロセスごとに名前空間を持つことができる)になる.UNIXのソケットは,プログラムからファイル記述子を介して操作できるが,専用のAPIを使う必要があり,ファイルシステム上のファイルとして見えるわけではない.つまり,ローカルホスト上の資源とリモートホスト上の資源を扱う場合に,異なる作法に乗っ取ったプログラミングが必要になる.Plan9では,ネットワーク透過に資源を扱うために,ネットワークに対するファイルへの抽象化を拡張している.

では,Plan9からLinux上で動作するエコーサーバにアクセスする例によって,具体的に見ていこう.なお,Plan9側ではCでプログラムを書かずに,シェルスクリプトだけで通信してみる.

Linux側のエコーサーバ

今時のLinuxだと,エコーサーバなんか動いていないだろうから,Perlで次のようなプログラムを書いてみた.LinuxマシンのIPアドレスは12.34.56.78とする.

#!/usr/bin/env perl
use IO::Socket;

$s = new IO::Socket::INET(Listen => 5, LocalAddr => '12.34.56.78',
                          LocalPort => 9000, Proto => 'tcp', Reuse => 1);
die "IO::Socket : $!" unless $s;

while (1) {
    $sock = $s->accept();
print "accept!\n";
    while (<$sock>) {
        print $sock $_;
    }
    close($sock)
}

close($s);
exit;

rcでエコークライアント

Plan9のシェルはrcである.

まず,/net/tcp/cloneというファイルをcatして,その出力を覚えておこう.

cpu% cat /net/tcp/clone
5

ここからが本番で,/net/tcp/cloneに"connect Linuxマシンのアドレス!ポート番号"を書き込む.このcatプロセスが死ぬとコネクションが切れてしまうので,注意.

cpu% cat > /net/tcp/clone
connect 12.34.56.78!9000

そして,別のターミナルから次のように実行してみよう."5"は最初に実行した"cat /net/tcp/clone"の出力結果と読み替えてほしい.

cpu% echo hoge > /net/tcp/5/data
cpu% cat /net/tcp/5/data
hoge

echoとcatだけでネットワークプログラミング(?)ができてしまった.
これはUNIXには真似できない芸当だよね.

後始末(シグナルではなくてノート)

このままだとcatは入力を待ち続けて,終了しない.ctl-Dでも終了しない.なぜだろう?

ということでcatをkillする.killもUNIXとはちょっと違っている.killの引数はコマンド名で(pkillに近いね),しかも,killコマンドが本当にプロセスを殺すのではなく,"echo..."という出力を返す.

cpu% kill cat
echo kill>/proc/68725/note

この出力をパイプでrcに食わせることでプロセスを殺すことができる.

cpu% kill cat | rc

つまり,/proc//noteというファイルに"kill"という文字列を書き込むことでプロセスを殺すことができる.UNIXのシグナルに近いが整数値ではなく,文字列であることに注意しよう.文字列の方がシグナル番号の枯渇に悩む必要がないし,分散環境にもなんとなく適していて,スケールできそうである.

ちょっと裏側を解説

閑話休題.ネットワークの話に戻ろう.基本的には,/net/tcp/*/ctlがコントロール用のファイル,/net/tcp/*/dataがデータ通信用のファイルになる./net/tcp/*以下を見ると,ctlとdata以外にも次のようなファイルが存在することがわかる.

%cpu ls /net/tcp/5
/net/tcp/5/ctl
/net/tcp/5/data
/net/tcp/5/err
/net/tcp/5/listen
/net/tcp/5/local
/net/tcp/5/remote
/net/tcp/5/status

そして,新たにコネクションを張るために,cloneファイルと呼ばれるファイルが存在する*1TCPの場合は,/net/tcp/cloneがそれである./net/tcp/cloneをopenすると,新たなインタフェースが割り当てられ,そのctlファイルのファイル記述子が返される.つまり,先の"cat > /net/tcp/clone"は,/net/tcp/*/ctlへリダイレクトされる.そして,connectメッセージにより,コネクションが確立し,/net/tcp/*/dataを介して,データのやり取りが可能になる.

また,今回の例はIPアドレスを直に指定したが,名前引きは/dev/dnsへのread/writeで行なう.

*1:UNIXの疑似端末のクローンデバイスに考えは近いのかな.