なぜ stdout の出力結果は混じらないのか

stdout は、一見単一のファイルに見える。しかし端末を複数開いている時、それぞれの端末の出力が混線することはない。なぜか。

$ ls /dev/stdout
/dev/stdout 


stdout は /dev/stdout に仮想ファイルとして存在している。
このファイルを読んでいるのだから混線しそうなものだが、実際には混線することはない。

さてこの /dev/stdout の詳細を追ってみよう。

$ ll /dev/stdout
lrwxrwxrwx 1 root root 15 Oct 2 00:24 /dev/stdout -> /proc/self/fd/1

/dev/stdout は /proc/self/fd/1 へのシンボリックリンクになっている。/proc/self は現在実行中のプロセスへのリンクを保持するディレクトリである。つまりプロセスごとに /proc/self ディレクトリのリンク先は異なっているわけだ。

本当に異なっているか、見てみよう。

$ ll /proc/self
lrwxrwxrwx 1 root root 64 Oct  2 00:23 /proc/self -> 9159/
$ ll /proc/self
lrwxrwxrwx 1 root root 64 Oct 2 00:23 /proc/self -> 9167/

確かに、コマンド毎にリンク先が異なっている。最初の ll コマンドの時のリンク先は /proc/9159, 二度目の ll コマンドの時のリンク先は /proc/9167 だ。


改めて、/proc/self/fd/1 は何を表しているのか見てみよう。
まず /proc/self は /proc/$PID だったので、/dev/stdout は /proc/$PID/fd/1 へのリンクだということになる。

では /proc/$PID/fd ディレクトリは何を表すディレクトリなのか。これは、このプロセスがオープンしているファイルディスクリプタを保持するディレクトリである。とはいえ stdout は常にファイルディスクリプタ1番で表されるので、当該プロセスが1番をオープンしているかどうかは関係なく、常に存在する。/proc/$PID/fd/$fd は当該ファイルへのシンボリックリンクになっており、リンクを辿ることでどのファイルをオープンしているのか知ることができる。

$ ll /proc/self/fd/1 
lrwx------ 1 aoking aoking 64 Oct 2 04:07 /proc/self/fd/1 -> /dev/pts/3

上記のシェルからでは /dev/pts/3 にリンクされていた。別のシェルから叩いてみよう。

$ ll /proc/self/fd/1
lrwx------ 1 aoking aoking 64 Oct 2 04:08 /proc/self/fd/1 -> /dev/pts/4

というわけで、「なぜ stdout の出力は混じらないのか」という問に対しては、「シェル毎に /proc/self/fd/1 のリンク先が異なっているから」というのが答えになります。


では /proc/self/fd/1 のリンク先の /dev/pts/3 や /dev/pts/4 はなんなのでしょう?これは擬似端末と呼ばれる仮想デバイスで、実際の画面(モニタ)の解像度や色などに関わらず端末が抽象化されたスペシャルファイル。今自分のシェルがどの仮想端末を用いているかは、tty コマンドで知ることができる。

$ tty
/dev/pts/3

別のシェルからだと別の結果に。

$ tty
/dev/pts/4

ll /proc/self/fd/1 した時のリンク先を表示してるだけですね。


さてこの /dev/pts/3 や /dev/pts/4 に対して読み書きできるので、こんな遊びもできます。

$ tty
/dev/pts/3
$ echo hello > /dev/pts/4

こうすると、このシェルでは何も表示されないが、/dev/pts/4 と結びついている端末を開くと、あら不思議。

$ hello

と、さっきのコマンドの出力結果が表示されている。


なんとも悪用への知恵が働きそうなシロモノです。

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版