Pipe
目次- 1.0.0 簡単な基礎実験
- 1.1.0 bind '#|' X
- 1.2.0 window -m
- 1.3.0 プロセス間通信
- 1.4.0 4つのウィンドウでの実験
- 1.5.0 私的なパイプ
- 1.6.0 複数のプロセスからの利用
- 1.7.0 パイプの終了
- 1.8.0 複数のパイプの作成
- 1.9.0 プログラムとの通信
- 2.0.0 チャット
- 2.1.0 目標
- 2.2.0 通信の基礎知識
- 2.3.0 空きポートの調べ方
- 2.4.0 listen1
- 2.5.0 telnet を使った接続実験
- 2.6.0 チャット(1)
- 2.6.1 トラブルシューティング
- 2.7.0 チャット(2)
- 3.0.0 /srv
- 3.1.0 実験(1)
- 3.2.0 実験(2)
- 3.3.0 /srv の仕組み
- 3.4.0 実験(2)の解釈
- 3.5.0 実験(1)の解釈
- 4.0.0 con /srv/foo
- 5.0.0 srv コマンド
- 5.1.0 実験(1)
- 5.2.0 con が使用しているファイル記述子
- 5.3.0 実験(2)
- 5.4.0 /srv 利用上の注意
- 6.0.0 mount
- 6.1.0 u9fs
- 7.0.0 リモートホストとPlan 9 端末との間のパイプ
- 7.1.0 cpu コマンド
- 7.2.0 実験
- 7.2.1 準備
- 7.2.2 リモートホストにおける con の実行
- 7.2.3 端末における con の実行
- 7.2.4 通信
- 7.2.5 通信の終了
- 8.0.0 リモートホストで実行されるプログラムと Plan 9 端末との会話
- 8.1.0 rx コマンド
- 8.2.0 con コマンド
2005/05/20 追加 (7.0.0 8.0.0)
2005/05/07 更新
2005/05/05
ここでは Plan 9 のパイプの機能を紹介する。
プログラムからの使い方は通常の Unix の方法がそのまま使えるが、BSD系のような片方向のパイプではなく、SYSTEM-V 系の双方向パイプである。その仕組みから言えば Plan 9 のパイプは Unix のパイプとかなり異なる。パイプもまたファイル指向である。そのためにシェルからも容易にパイプを生成し利用できる。以下にシェルを使って簡単に実験できるパイプの使い方を紹介する。
簡単な基礎実験
bind '#|' X
term% cd /tmp term% mkdir X term% bind '#|' X term% ls X X/data X/data1 term%この中に現れる '#|' はカーネルが提供するパイプである。これはプロセス毎に提供されている。これを適当なディレクトリ X に bind すると、X を通じてパイプを利用できる。X/data と X/data1 は完全に等価である。X/data への書き込みは X/data1 から読み取れる。逆に X/data1 への書き込みは X/data から読み取れる。
この例では X をわざわざ作成しているが、既存のものを用いても構わない。既存のものとしては /n/temp が適当であろう。ここに bind しても、名前空間の異なる他のプロセスには影響を与えない。
window -m
bind したウィンドウでterm% window -mを実行する。するとウィンドウが生成される。サイズや生成位置を指定したい場合にはマニュアルを見るが良い。生成されたウィンドウで ls X を実行してみよう。
term% ls X X/data X/data1 term%となるはずである。window コマンドの -m オプションによって、このコマンドを打ち込んだウィンドウと生成されたウィンドウの環境が保たれようとする。特にワーキングディレクトリと名前空間が保たれている。読者はこのオプションが無い場合と比較するが良い。
プロセス間通信
読者は一方のウィンドウでcat > X/dataを実行し、他方のウィンドウで
cat X/data1を実行してみるがよい。すると例えば次のようになる。
データを入力する側:
term% cat > X/data alice bob carol term%carol まで入力し ctl-D を打つ。
すると他方のウィンドウでこの結果が見られる。
term% cat X/data1 alice bob carol term%先に述べたように data と data1 は等価である。2つのウィンドウの関係もそうである。読者は自ら確認してみるがよい。
4つのウィンドウでの実験
先の実験は2つのウィンドを使った。今度は4つのウインドウを使って実験してみよう。bind を行ったウィンドウであと2回
window -mを実行すれば相互通信可能な合計4つのウィンドウができあがる。これらを A0, A1, B0, B1 としよう。各々のウィンドウで
ウィンドウ A0:
cat > X/dataウィンドウ A1:
cat X/dataウィンドウ B0:
cat > X/data1ウィンドウ B1:
cat X/data1を実行しておく。
読者は A0 で打ち込んだデータが B1 で表示され、B0 で打ち込んだデータが A1 で表示されるのが確認できるであろう。
私的なパイプ
読者は Plan 9 のパイプが私的な性格を持っている事を次のように確認できる。
もう1つウィンドウを生成する。普通に行うようにマウスで適当に生成してもよいし、-m オプションなしの window コマンドを使ってもよい。生成されたウィンドウ(これを C とする)で /tmp に移動し
ls Xを実行してみる。X/data や X/data1 は存在しないのが分かるはずである。
そこで、さらに
bind '#|' Xを実行する。
ウィンドウ B1 において cat X/data1 の実行を止め、C で
cat X/data1を実行し、A0 でデータを打ち込んでみる。もはや C は A0 からのデータを受け取れないことが確認できるであろう。つまりプロセス間通信の秘密は保たれ、また邪魔もされない。UNIX の名前付きパイプとの根本的な違いである。
複数のプロセスからの利用
1つのバイプを複数のプロセスから利用できる。先の4つのウィンドウを使った実験は4つのプロセスから利用する特殊な場合である。一般的に言えば X/data を n 個のプロセスがオープンし、X/data1 を m 個のプロセスがオープンする。1つのパイプの端を複数のプロセスが読み取るやりかたは旨く行かないであろう。この場合にはデータの取り合いが発生する。しかし複数のプロセスが書き込むケースは応用の余地がある。Plan 9 のパイプは、殆ど同時に複数のプロセスが同じパイプの端に書き込んだ場合にも、書き込みデータが混じり合わないようにしている*。
パイプの終了
一旦パイプの両端がオープンされてしまえば、バイブの片方が完全に閉じられる*までパイプは有効である。従って例えば名前空間を共有する、ウィンドウAで
cat X/dataウィンドウBで
cat >X/data1を実行すれば、他の(名前空間を共有する)ウィンドウで
echo 'blah blah' >X/data1としてもパイプは閉じない。このメカニズムは何かに応用できるかもしれない。
複数のパイプの作成
他のディレクトリ、例えば Y に bind を実行すれば X とは独立なパイプが生成される。つまり bind によって新しいキャネルを作成してくれているのである。読者は実際にそのことを確認してみるが良い。
Plan 9 のドキュメントや bind の使用例を見ていると、bind は名前空間の部分木の張り付けを行っているかのような印象を受けるが、カーネルディバイス '#|' の bind に関する限り
bind '#|' Xは単に '#|' の別名 X を作成しているのではない事がこの実験から分かる。実際、直接
cat '#|/data'と
cat >'#|/data1'を実行しても通信はできない。
プログラムとの通信
この実験では '#|' を X に bind し、その下で名前空間を共有するウィンドウ A,B,C,D を作成する。ウィンドウA
tr a-z A-Z <X/data >X/dataウィンドウB
cat >X/data1ウィンドウC
cat X/data1ウィンドウD
con -C $home/tmp/X/data1
チャット
目標
前節で述べた4つのウィンドウを使った実験は、それ自体としてはつまらないように見える。1つのコンピュータ、1つのディスプレー、1つのキーボードで実験している限り、どのように役に立つかが見えてこないのだ。
もしもウィンドウ A0, A1 が動くコンピュータと、ウィンドウ B0, B1 が動くコンピュータが異なったらどうか? 異なるコピュータを結ぶチャットが実現するのである。チャットの相手はアメリカの友人かもしれない。素晴らしいではないか?
ここでは Plan 9 に標準的に含まれているツールだけを使ってチャット環境をどこまで構築できるか考えてみる。
通信の基礎知識
コンピュータは通信ポートを通じて他のコンピュータと通信する。2つのコンピュータのうちの1つは他方のコンピュータのために受信ポートを準備し、アクセスされるのを待つ。
以下ではアクセスされるのを待っているコンピュータをサーバー、他方のコンピュータをクライアントと呼ぶ。
空きポートの調べ方
サーバの空きポートはサーバ上でnetstat -nを実行する事によって知る事ができる。
term% netstat -n ... tcp 27 arisawa Listen 445 0 :: tcp 28 arisawa Established 24467 564 204.178.31.8 ... term%第5フィールド(この例では 445 や 24467)が現在使用されているポートである。第6フィールドは接続相手方のポートであり、0のものは接続を待っている状態である。接続中であれば相手の IP アドレスが表示されている。
listen1
listen1 はユーザレベルのリスナーである。ちょっとした実験をやるのに都合が良い。listen1 の仕様はaux/listen1 [-tv] addr cmd args ...である。addr は
'tcp!*!9000'のように指定する。この例では TCP の 9000 ポートからのメッセージを聞いている。この中のアステリスク(*) は listen1 を起動したコンピュータが持っている全ての IP アドレスについて聞く事を意味している。
cmd args ...は指定ポートからの接続要求に対して実行されるコマンドと引数である。コマンドは名前ではなくパスで与える。即ち / または ./ で始まる必要がある。
listen1 は v フラグの下で冗長なメーセージを出すが、この実験ではこのフラグは使わない。t フラグのの下では listen1 が実行された名前空間のままコマンドがが起動される。この事は listen1 を実行したユーザと同じ権限でコマンドが実行される事をも意味している。我々の実験では t フラグが要求されるので、安全な環境で実験して欲しい。
telnet を使った接続実験
クライアントは telnet を使ってサーバに接続するものとする。但しポートは telnet ポート(TCP 23)ではなく適当な空きポートを利用する。以下ではサーバの名称を venus としポート 9000 を聞くとする。実験の最初の目標は listen1 で指定されたコマンドが実行されている事をクライアントが確認する事である。コマンドとして rc を選ぶ。分かりやすくするためにクライアントが見るプロンプトを '* ' に変更しておく。そこで次のように listen1 を実行してみる。
prompt='* ' aux/listen1 -t 'tcp!*!9000' /bin/rc -iすると、UNIX クライアントからは
telnet venus 9000Plan 9 クライアントからは
telnet -r tcp!venus!9000で rc が起動されている事が確認できるが、動作がおかしい。例えば
-bash$ telnet venus 9000 * ls : bad character in file name: 'ls 'のようになる。これは telnet の仕様として、改行の前に CR コードを付加するからであり、そのために rc はクライアントが投入するコマンドを正しく解釈できない。そこで rc にリクエストを渡す前に CR コードを削除する事とし、次のように変更する。即ち、ファイル chat1 を作成し、それをコマンドとして実行する。
#!/bin/rc tr -d 0x0d | {prompt='*' /bin/rc -i}
chat1 の内容。許可ビットを 755 に設定しておく。
その下でサーバ側の実行をaux/listen1 -t 'tcp!*!9000' ./chat1とする。
特に
ls Xで
X/data X/data1が見える事を確認しよう。
まるで telnet でログインしたかのように、クライアント側からサーバに対する任意のコマンドが実行できるのを確認できる。通常の telnet ログインと異なるのは認証が伴わない点だけである。
なおクライアントが接続を終了するには、クライアントが
- UNIX であれば ctl-] に続けて ctl-D
- Plan 9であれば ctl-D
チャット(1)
サーバがbind '#|' X aux/listen1 -t 'tcp!*!9000' ./chat1を実行し、さらにサーバ側の2つのウィンドウで
cat X/dataと
cat >X/dataを実行しているとせよ。クライアントは2つの仮想ターミナルで
cat X/data1と
cat >X/dat1を実行する。すると2つのコンピュータ間のチャットが実現する。
トラブルシューティング
* cat X/data1 cat: error reading X/data1: i/o on hungup channel *
チャット(2)
チャット(1) の方法はクライアントに不必要な強い権限を与えた。rc を直接クライアントに使わせる事はとても危険なのである。しかも使いやすいとは言えない。ここではその点に付いて改善してみる。
今回は2つのポートを使う事にする。クライアントには 9000 とポート 9001 に telnet でアクセスしてもらう事にし、クライアントはポート 9000 を受け取ったメーセージの表示に使い、ポート 9001 に書き込む。この場合のサーバ側での listen1 は次のようになる。
aux/listen 'tcp!:!9000' /bin/cat X/data1 & aux/listen 'tcp!:!9001' /bin/rc -c 'tr -d \x0d>X/data1' &サーバ側は、一方のウィンドウで
cat >X/data他方のウィンドウで
cat X/dataを実行する。
/srv
プログラムの中で使用する pipe() 関数によるパイプは親子のプロセスの間でしか使えない。他方カーネルファイル '#|' を使ったパイプは名前空間を共有するプロセス間でしか使えない。そしてユーザが異なれば名前空間は共有しない。それではユーザが異なるプロセス間のパイプはどのように実現しているのか? この疑問に答えるのがこの節の目的である。
バイプのニーズはユーザが異なる2つのプロセス間だけではなく、分散配置されている2つの異なるコンピュータでも発生する。Plan 9 の /srv ディレクトリはそうした問題を解決するために存在する。後に見るように /srv は正確にはパイプではなく、単にファイル記述子を渡す仕組みである。機能的にはパッファの無いパイプとも考えられる。
/srv は C プログラムで扱うのが普通である。C を使わないで /srv に関する面白い実験を見つけるのは難しい。以下の /srv に関する記事は Mike Haertel の 9fans への投稿をヒントにしている。この投稿の内容は http://tinyurl.com/arnnd にアクセスすればわかる。
実験(1)
foo を適当な名前とする。但し /srv/foo は存在しないとせよ。その下で1つのウィンドウ A でcat | echo 0 >/srv/foo他のウインドウ B で
cat /srv/fooを実行する。
ウィンドウ A で入力したデータがウィンドウ B で表示されるのを確認する。
実験(2)
foo を適当な名前とする。但し /srv/foo は存在しないとせよ*。その下で1つのウィンドウ A でecho 0 >/srv/foo他のウインドウ B で
cat /srv/fooを実行する。
ウィンドウ A で入力したデータがウィンドウ B で表示されることもあれば、シェルコマンドとして認識される事もあるのを確認する。
rm /srv/fooで削除できる。
/srv の仕組み
実験(1)を理解するために、/srv の仕組みを解説する。
/srv/foo をオープンしたプロセスはファイル記述子をカーネルから渡される。このファイル記述子が指しているファイルの実体は /srv/foo の中に書き込まれているファイル記述子が指している実体と同じものである。(この事は後の実験で示される。)
実験(2)の解釈
実験(2)が簡単なのでこれをまず議論する。echo 0 >/srv/fooで、このウィンドウのファイル記述子0のファイル(標準入力)が、この後に /srv/foo を開くプロセスにパイプされる。それはウィンドウBで実行した
cat /srv/fooである。
ウィンドウAでは標準入力は閉ざされていないので、2つのプロセスによって読み取られようとする。1つはカーネルのパイプを処理するプロセスから*、他はシェルである。その結果実験(2)の現象が発生する。
実験(1)の解釈
実験(1)のcat | echo 0 >/srv/fooは標準入力が2重に読み取られる問題を巧みに避けている。
cat |を添える事によって/srv/foo が生成された時の標準入力を cat の出力に限定しているのである。これを考えついたやつは凄い。
con /srv/foo
実験(1)と実験(2)では cat を使って /srv/foo は読み取りオープンした。Plan 9 の標準ツール con は /srv/foo に対して読み書きできる。すなわちキーボードから読み取ったデータを /srv/foo に書き込み、/srv/foo から読み取ったデータを表示してくれる*。
Plan 9 のパイプは双方向パイプなので con を生かした実験をしてみよう。1つのウインドウで
prompt='* ' rc -i <[0=1] >[2=1]| echo 0 >/srv/fooあるいは
prompt='* ' rc -i >[0=1] >[2=1] | echo 0 >/srv/fooを実行する。この中に現れる <[0=1] も >[0=1] もファイル記述子1の内容をファイル記述子0の所にコピーする*。これによって I/O が切り替わる。>[2=1] も同様に考えれば良い。
他のウィンドウで
con -C /srv/fooを実行すると
*のプロンプトが表示される。ここで ls を打ち込んでみよう。ファイル一覧が表示されるはずである。con の -C はローカルエコーを行うフラグである。これがないと打ち込んだコマンドが表示されない。
srv コマンド
srv コマンドはファイル記述子を /srv の中の与えられた名前のファイルに書き込む。ファイル記述子は通常はネットワークの I/O の口から得られるものであるが、コマンドの I/O の口から得る事も可能である。
実験(1)
srv コマンドの典型的な使い方の例は次のようなものである。srv tcp!venus!9000 fooここに venus はアクセスするサーバの名称であり、9000 はそのポートである。これによって /srv/foo が作成される。
srv コマンドを実行すると
post...のメッセージが出てくる。これは /srv/foo にファイル記述子が書き込まれた事を意味している。
サーバ venus ではリスナーが 9000 ポートを監視していなくてはならない。でないと
srv: dial tcp!venus!9000: connection refusedと言われるであろう。そこで実験のために venus で listen1 を使って
term% prompt='* ' aux/listen1 -t 'tcp!*!9000' /bin/rc -iを実行して srv コマンドの実験をする事にしよう。
クライアントは
term% srv tcp!venus!9000 foo term% con -C /srv/fooを実行する。すると
*のプロンプトが出て、任意の rc コマンドが con の下に実行できる事が分かる。
我々は良く似た事を telnet を使って行った。telnet の実験と比較するとサーバ側で CR コードの削除の必要性がなくなった。srv と con の組み合わせは、改行コードの前に CR コードを付加する事無しに、率直にサーバとデータを交換する。
con が使用しているファイル記述子
con が使用しているファイル記述子の正体を調べよう。netstat -nを実行すると、例えば(筆者の場合には)
term% netstat -n ... tcp 32 arisawa Listen 9000 0 :: tcp 33 network Closed 14067 9000 192.168.1.2 tcp 34 arisawa Established 14068 9000 192.168.1.2 tcp 35 network Established 9000 14068 192.168.1.2 ... term%この実験ではサーバとクライアントが同一のマシンである。第4フィールドの Closed の行は古い実験の残り滓である。14068 がクライアントのポートであり、クライアントは /net/tcp/34 を、サーバは /net/tcp/35 を使っている事が分かる。
また
term% ps ... arisawa 1995 0:00 0:00 60K Open listen1 arisawa 1999 0:00 0:00 248K Await rc arisawa 2000 0:00 0:00 52K Pread con arisawa 2001 0:00 0:00 52K Pread con ...から con の実行に2つのプロセスが使われており、各々の pid は 2000 と 2001 である事が分かる。各々、キーボードからのデータ待ちとサーバからのデータ待ちのはずである。各プロセスが使用しているファイル記述子とその実体は次のように判明する。
term% cat /proc/2000/fd /usr/arisawa 0 r M 66 (0000000000000001 0 00) 8192 1777 /dev/cons 1 w M 66 (0000000000000001 0 00) 8192 112344 /dev/cons 2 w M 66 (0000000000000001 0 00) 8192 112344 /dev/cons 3 rw I 0 (000000000002044d 0 00) 0 21760 /net/tcp/34/data term% cat /proc/2001/fd /usr/arisawa 0 r M 66 (0000000000000001 0 00) 8192 1795 /dev/cons 1 w M 66 (0000000000000001 0 00) 8192 112618 /dev/cons 2 w M 66 (0000000000000001 0 00) 8192 112618 /dev/cons 3 rw I 0 (000000000002044d 0 00) 0 22052 /net/tcp/34/data term%con は /srv/foo をオープンした時にファイル記述子 3 を受け取ったが、その実体は /net/tcp/34/data である事がここから分かる。
実験(2)
今度は srv の -e オプシヨンを使おう。このオプションはsrv -e command srvnameのように使う。command には任意の rc コマンドを与える事ができ、srvname は /srv の中に作成するファイル名である。これによって他のプログラムは /srv/srvname にアクセスする事によって command と会話する事が可能になる。
例を挙げよう。
srv -e 'prompt=''* '' >[2=1] rc -i' fooこれは
prompt='* ' rc -i >[0=1] >[2=1] | echo 0 >/srv/fooと内容的に等価である。
これを実行し、他のウィンドウで
con -C /srv/fooを実行してみよう。すると
*のプロンプトが表示され、rc と会話できる事が分かる。
srv コマンドの -e オプシヨンが何時から付加されたか筆者は知らない。Bell-labs の過去の Plan 9 のソースは sources.plan9.bell-labs.com で見る事ができる*。2002/12/12 のソースが最も古い。Mike Haertel の 9fans の投稿は 2002/05/01 なので、この投稿に触発された可能性が高い。
/srv 利用上の注意
以上ではコマンドとして rc を使ったが tr の方が簡明であったと思う。そこでここでは英字の小文字を大文字に変化するプログラムを走らせてそれを /srv/foo に渡す。srv -e 'tr a-z A-Z' fooそして他のウインドウで
con /srv/fooさらに他のウィンドウでも
con /srv/fooを動かす。con のウィンドウで alice を打ち込んでみる。その結果の ALICE は必ずしも同じウィンドウの con で受け取られるわけではない事が分かる。
この結果は /srv/foo を使うプログラムが複数になる場合には何らかの統制が必要である事を意味している*。つまり1つのプロセスだけが /srv/foo を使うようにユーザ側で気をつけるか、読み書きに対して排他制御を行うかである。
mount
Plan 9 は様々なサービスをファイルとしてのインターフェースでユーザに提供する。サービスプログラムはカーネルに組み込まれるのではなくユーザによって自由にシステムに組み込む事ができる。ユーザはそれらをファイルとしての名前空間に組み込むために mount を実行する。UNIX の mount はカーネルのプログラムを管理者権限でユーザに提供するが、Plan 9 の mount は完全にユーザの手中にある。そして Plan 9 の mount で中心的な役割を果たしているのが /srv の中の名前である。
既に見たように /srv の中の名前、例えば /srv/foo は名前の付いたパイプのように振る舞う。その片方の端は /srv/foo を生成したプロセス P に繋がっている。mount は
mount /srv/foo /n/fooのように適当なディレクトリ /n/foo にプロセス P のサービスをファイルとして見せる口を作る。このように旨く働くためには、もちろん、P のプログラムは mount を許すための必要な形式を踏まえなくてはならない。このような形式を備えたプログラムをシェルスクリプトのように複数のコマンドを組み合わせて作成する事は難しく、C 言語や Python などに頼らざるを得ないと思う。
u9fs
Plan 9 の配布ソフトウェアの中には /srv と結びついた多数のサービスプログラムが含まれている。u9fs もその中の1つである。このプログラムは UNIX のファイルシステムを Plan 9 にマウントする。
u9fs は UNIX のリスナー(inetd や xinetd) の下でサービスを実行するものとしてデザインされていた。しかしリスナーの下で実行すると UNIX システムの管理者権限が必要となる。筆者はユーザレベルのリスナーを作成し、この問題をクリアしてきたが、いずれにせよリスナーが必要であると誰もが思い込んでいた。Mike Haertel の投稿までは...
彼の投稿は
% ssh myname@remotehost u9fs -a none -u myname <[0=1] | echo 0 > /srv/remotehost % mount /srv/remotehost /n/kremvaxであるが、現在の srv の仕様では
% srv -e 'ssh myname@remotehost u9fs -a none -u myname' remotehost % mount /srv/remotehost /n/kremvaxの方が率直である事を読者は理解するであろう*。
srvssh myname@remotehost mount /srv/remotehost /n/kremvax
リモートホストとPlan 9 端末との間のパイプ
cpu コマンド
cpu コマンドは Plan 9 端末が Plan 9 ホストをコマンドベースで使う時の、UNIX の telnet のようなコマンドである。但し telnet と以下の点が異なる。- cpu コマンドでアクセスすると /mnt/term に端末側のファイルシステムが見える。従って端末との間のファイルのコピーが cp コマンドなどの Plan 9 のツールを使って行える。
- cpu コマンドでアクセスすると acme のようなグラフィカルなユーザインターフェースを備えたエディタが使える。一般的に言えば、ローカルなウィンドウと同じように作業できる。
- factotum によるユーザ認証が行われるために、ユーザからパスワードを入れなくても良い。
- 暗号化された通信チャネルが使用されている。
実験
準備
まず端末の1つのウィンドウでパイプが使えるようにしておく。そして名前空間を共有するもう一つのウィンドウを生成しておく。以下では、この2つのウィンドウを A と B とする。term% cd tmp term% bind '#|' X term% window -m
リモートホストにおける con の実行
ウィンドウ A でterm% cpu ar% ls /mnt/term/usr/arisawa/tmp/X /mnt/term/usr/arisawa/tmp/X/data /mnt/term/usr/arisawa/tmp/X/data1 ar% con /mnt/term/usr/arisawa/tmp/X/datacpu が引数無しに起動されると環境変数 cpu で指定されたサーバにアクセスする。ここでは ar がサーバである。
con は、その引数が '/' で始まる時には、通信の相手がファイルであると解釈する。従って X/data に対しては絶対パスを指定する必要がある。
端末における con の実行
ウィンドウ B でterm% con /usr/arisawa/tmp/X/data1
通信
さていよいよ A と B の間で通信を行う。A で alice と打ち込めば、それが B で表示されるのが確認されるはずである。B で bob と打ち込めば、それが A で表示されるであろう。
通信の終了
con による通信を終了するにはctl-\つまりコントロールキーを押しながら '\' キーを打つ。すると
>>>が表示され、ここで 'q' を打つ。
ar% con /mnt/term/usr/arisawa/tmp/X/data bob >>> q ar%
リモートホストで実行されるプログラムと Plan 9 端末との会話
9p プロトコルを使ってのリモート実行には rx を使う方法と cpu を使う方法がある。基本的な使い方はrx host command arg ... cpu -h host -c commans arg ...である。
cpu はいかにも Plan 9 らしい高度な機能をユーザに提供してくれる。すなわち
- 端末の名前空間をリモート側の /mnt/term にマウントし、
- 端末の /dev/cons にリモートの標準入出力を割り振って
ここではリモートで実行されるプログラムを端末側からプログラムによって制御することを想定し*、それを実現するのに役立ちそうな実験をしてみる事にする。
以下の実験では端末側に 2 つのウィンドウが登場する。
rx コマンド
ウィンドウ Aerm% srv -e 'rx ar tr a-z A-Z' foo post... term%ウィンドウ B
term% con /srv/foo ALICE小文字の alice を打ち込んで、リモートホスト ar で実行される tr によって ALICE が表示されているのである。
con コマンド
さて上の rx を cpu で置き換えるとerm% srv -e 'cpu -h ar -c tr a-z A-Z' foo post... term%でも良さそうに思われるが実際には旨く行かない。原因は cpu コマンドは標準入出力を端末の /dev/cons に割り振るからである。この親切はこのケースにおいてはアダになる。
cpu コマンドがよけいな事をしないように改造する考えもあろうが、次のようにすれば問題を回避できる。
ウィンドウ A で
term% bind '#|' /n/temp; bind /n/temp/data /dev/cons; term% window -m term% cpu -c tr a-z A-Z & term%ウィンドウ B で
term% con /n/temp/data1 ALICE小文字の alice を打ち込んで、リモートホスト ar で実行される tr によって ALICE が表示されているのである。