Logo address

Pipe

目次

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 のパイプは、殆ど同時に複数のプロセスが同じパイプの端に書き込んだ場合にも、書き込みデータが混じり合わないようにしている*。
* マニュウルによると32KB までのデータについてはアトミックである。なおファイルへのアトミックな書き込みに関して言えば Plan 9 のファイルは書き込みロックを許可ビットで指定できる。また syslog は書き込み時に自らロックを掛けている。

パイプの終了

一旦パイプの両端がオープンされてしまえば、バイブの片方が完全に閉じられる*までパイプは有効である。従って例えば名前空間を共有する、
ウィンドウ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 9000
Plan 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 ログインと異なるのは認証が伴わない点だけである。

なおクライアントが接続を終了するには、クライアントが

とすればよい。

チャット(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 で表示されることもあれば、シェルコマンドとして認識される事もあるのを確認する。

注釈: * /srv/foo は
	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)の現象が発生する。

注 * 実は後に明らかになるように、これはカーネルのプロセスではなく、cat である。

実験(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 から読み取ったデータを表示してくれる*。
* con は本来は通信ツールである。/srv のファイルに対しても通信できるようにしたのは後からの追加機能だと思う。

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 はローカルエコーを行うフラグである。これがないと打ち込んだコマンドが表示されない。

注釈: * C プログラムコードで言えば dup(1,0) を実行する。ファイル記述子と言うのはメモリのポインターの配列のインデックスだと考えれば良い。ポインターの指している場所にはファイルの実体の情報が存在する。dup(1,0) によって 1 に書かれているポインターの値が 0 にコピーされる。この問題では 1 はパイプになっているので、rc の標準入力がパイプに切り替わるのである。

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 を使うようにユーザ側で気をつけるか、読み書きに対して排他制御を行うかである。

* 同様な事はバイプについても言える。なお /srv/foo や X/data などについては chmod でロックの指定はできない。

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 が準備されている
	srvssh myname@remotehost
	mount /srv/remotehost /n/kremvax

リモートホストとPlan 9 端末との間のパイプ

cpu コマンド

cpu コマンドは Plan 9 端末が Plan 9 ホストをコマンドベースで使う時の、UNIX の telnet のようなコマンドである。但し telnet と以下の点が異なる。
以下では cpu コマンドを使ってリモートホストにアクセスした場合の端末との間のパイプについて解説する。

実験

準備

まず端末の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/data
cpu が引数無しに起動されると環境変数 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 らしい高度な機能をユーザに提供してくれる。すなわち

  1. 端末の名前空間をリモート側の /mnt/term にマウントし、
  2. 端末の /dev/cons にリモートの標準入出力を割り振って
くれる。他方 rx は cpu に比べてシンプルで、このような事はしない。

ここではリモートで実行されるプログラムを端末側からプログラムによって制御することを想定し*、それを実現するのに役立ちそうな実験をしてみる事にする。

* このような技術はグリッドコンピュータの実現に不可欠である
但し実験であるから、端末側に関してはパイプの端を con を使って手入力でアクセスする事にする。この部分は原理的にはプログラムによる置き換えが可能である。

以下の実験では端末側に 2 つのウィンドウが登場する。

rx コマンド

ウィンドウ A
erm% 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 が表示されているのである。