端末のtty
でコマンドを実行すると、/dev/pts/10
が返されます。
それ以外に、ファイル/dev/stdout
/dev/stdin
と/dev/stderr
があります。それらを直接操作すると、ターミナルに結果が表示されます。
user@laptop:build$ tty
/dev/pts/10
user@laptop:build$ echo "Test" > /dev/stdout
Test
user@laptop:build$ echo "Test" > /dev/stdin
Test
user@laptop:build$ echo "Test" > /dev/stderr
Test
また、シェルから開始されたすべてのcliアプリでは、stdout
/stderr
/stdin
のファイル記述子が開かれます。つまり、何かを印刷するスクリプトを実行する場合、印刷はstdout
に書き込むことと同じです。
これまでのところ、stdout
/stderr
/stdin
は、シェルが動作する唯一のインターフェースでした。これはアプリにも当てはまります。
一部のOSコンポーネントは、最終的にstdout
に書き込まれたデータを端末に移動します。そうしないと、何も出力されません。
stdout/stdin/stderr
と端末の間の接続がいつどこで発生し、std*
との対話が実際に端末上の何かにつながるのですか?
私が挑戦したいと思っている私の大まかな仮定は次のとおりです。
/dev/stdout
、/dev/stdin
、および/dev/stderr
は、実行中のシェルによって作成されます。これらは、シェルなしでは存在しません。シェルは、端末を表す実際のデバイスファイル(
/dev/pts/10
)を使用して通信チャネルを設定し、/dev/stdout
、/dev/stdin
、および/dev/stderr
を介して端末の機能を公開します。このようにして、シェルは、すべてのアプリが単純な印刷のためにデバイスファイルを操作する方法を心配するのではなく、単純なファイルインターフェイスをアプリに提供します。
更新
/dev/pts/10
は疑似端末ですが、疑似端末の概念を導入せずに答えを出すことができる答えをもっと大切にします。私はそれが質問への答えから気をそらすだけであるという観点から来ています:
stdout/stdin/stderr
と端末の間の接続がいつどこで発生し、/dev/std*
との対話が実際に端末上の何かにつながるのですか?
POSIX準拠のプログラムは、ファイル記述子#0、#1、および#2(それぞれプログラミング定数stdin
、stdout
、およびstderr
とも呼ばれます)をそのプログラムから継承することを期待できます。親プロセス、すでに開いている、すぐに使用できる状態。
テキストコンソールにログインしているセッションのコマンドラインプログラムの最も単純なケースでは、リダイレクトは適用されません。この継承のチェーンは、TTYデバイスを初期化したgetty
プロセスに戻ります。ログインセッション。
GUIを使用してログインする場合、ディスプレイマネージャープロセス(gdm/kdm/sddm/lightdm/xdm/<whatever>dm
)は通常、標準入力と出力を/dev/null
に設定し、標準エラーを$HOME/.xsession-errors
に設定するか、これらのファイル記述子は、同様に、デスクトップ環境の一部として、またはデスクトップメニューまたはアイコンの使用を開始して、セッションで開始されたすべてのGUIプログラムに継承されます。
たとえばSSHセッション、つまりセッションの初期化に分岐したsshd
プロセスは、疑似TTYデバイスペアを割り当て、stdin/out/err
ファイル記述子をその半分にポイントしてから、exec()
ユーザーのシェルを編集しました。そのフォークの反対側は、疑似TTYデバイスペアの残りの半分を保持し、セッションが終了するまで、ネットワークと疑似TTYデバイス間の送信/受信トラフィックの暗号化/復号化を処理します。
ターミナルエミュレータがGUIセッション内で開始されると、新しいセッションを初期化するときのsshd
プロセスと基本的に同じように動作します。つまり、疑似TTY、fork()
自体、および1つのコピーを割り当てます。ファイル記述子#0、#1、および#2を疑似TTYにポイントし、最後にexec()
をユーザーのシェルにポイントするなど、セッションをセットアップします。フォークの反対側は、実際に保守するタスクを引き続き処理します。ターミナルウィンドウの視覚的表現。
つまり、簡単に言うと、(疑似?)TTYデバイスは、端末セッションを初期化したものによってstdin/stdout/stderrに接続され、それとアプリケーションの間にあるすべてのプロセスは、単にそれらを何もしないによるファイル記述子への継承。子プロセスにそのまま渡します。
シェルのコマンドラインでリダイレクトオペレーターを使用する場合、シェルfork()
は、一時的なコピーのexec()
の直後に、実際にコマンドをfork()
する準備として、自分自身の一時的なコピーとしてそれぞれのファイル記述子を閉じ、その場所でリダイレクト演算子によって指定されたものを開き、exec()
コマンドを開いて、変更されたstdin/out/errファイル記述子を継承します。
一部のUnixスタイルのシステムでは、/dev/std*
デバイスがシェルによって処理される場合があります。しかし、Linuxはそれらをもう少し「本物」にします。
Linuxでは、/dev/stdin
、/dev/stdout
、および/dev/stderr
は、/proc
ファイルシステムを指す単なる古いシンボリックリンクです。
$ ls -l /dev/std*
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Feb 4 08:22 /dev/stdout -> /proc/self/fd/1
これらのリンクは、システムの起動時にudev
RAMベースの/dev
ファイルシステムが初期化されるときに作成されます。それらは単なる通常のプレーンシンボリックリンクであり、魔法のようなものは何もありません。
しかし/proc
は、システム内のプロセスの状態をリアルタイムで反映する完全仮想ファイルシステムであるため、いくつかの「魔法の」プロパティがあります。
/proc/self
は、/proc/<PID>
ディレクトリを指すシンボリックリンクですそれを調べるプロセスの:$ ls -l /proc/self # the PID of this ls command will be 10839
lrwxrwxrwx 1 root root 0 Feb 4 08:22 /proc/self -> 10839/
$ ls -l /proc/self # the PID of this ls command will be 10843
lrwxrwxrwx 1 root root 0 Feb 4 08:22 /proc/self -> 10843/
/proc/<PID>/fd
は、<PID>
でプロセスによって開かれたファイル記述子に対応する名前のシンボリックリンクを含むディレクトリであり、そのファイル記述子が関連付けられているものを指すです。したがって、/dev/pts/10
のプロセスが/dev/stdin
にアクセスしようとすると、シンボリックリンクが/proc/self/fd/0
をポイントします... /proc/self/fd/0
にアクセスすると、/proc
ファイルシステムドライバは、カーネルのプロセステーブルを調べ、それを使用して、カーネルにアクセスしているプロセスのファイル記述子テーブルを見つけ、/proc/self/fd/0
へのシンボリックリンクとして/dev/pts/10
を提示します。正確にそのプロセスは現在、ファイル記述子#0に関連付けられた/dev/pts/10
を持っています。
Solaris 11では、/dev/std*
デバイスは/dev/fd/
ディレクトリへのシンボリックリンクであり、同様に「魔法」です。
$ uname -sr
SunOS 5.11
$ ls -l /dev/std*
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stderr -> ./fd/2
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stdin -> ./fd/0
lrwxrwxrwx 1 root root 0 Jun 17 2019 /dev/stdout -> ./fd/1
ここで、Solaris /dev
ファイルシステムドライバは、歴史的な理由でLinuxが行うように/dev/fd
ファイルシステムにリダイレクトする代わりに、/proc
ディレクトリのデバイスノードを使用してマジックを実装します。
_/dev/pts/10
_は 疑似端末デバイスのペア のスレーブ側です。もう一方の端には、マスタークローンデバイス_/dev/ptmx
_を開き、ペアとして_/dev/pts/10
_を受け取ったプログラムがあります(_/dev/ptmx
_を開くたびに、別のスレーブを取得します)。 _/dev/ptmx
_と_/dev/pts/10
_の間の接続は、基本的に双方向パイプです ねじれあり 。
ターミナルエミュレータアプリケーションを開くと、次のようになります。
/dev/ptmx
_を開き、相手側の名前を取得します。もう一方の端を構成して開き、シェルはこれら3つのファイル記述子をセットアップするために何もせず、親プロセスから継承します。その子がシェルからファイル記述子を継承するのと同じ方法です。
備考:Linuxシステムでは、_/dev/stdin
_、_/dev/stdout
_、および_/dev/stderr
_は実ファイルであり、一連のシンボリックリンクによって_/proc/<pid>/0
_、_/proc/<pid>/1
_および_/proc/<pid>/2
_は、実際の入出力デバイスを指します。この場合は_/dev/pts/10
_です。
これらの3つの存在 標準ストリーム はCライブラリによって保証されています。
編集:更新された質問に対処するために、回答のいくつかのポイントを明確にしましょう:
/proc/pts/*
_に書き込まれたものはすべて、それを作成して表示した端末によって読み取られ、表示されます。_/proc/pts/*
_から読み取られたものはすべて、端末に接続された入力デバイスから取得されます。/dev/stdout
_は_/proc/self/fd/1
_へのシンボリックリンクであるのが一般的ですが、_/dev/stdin
_は_/proc/self/fd/0
_へのシンボリックリンクです。仮想_/proc
_ファイルシステムは、すべてのアプリケーション_/proc/self
_を_/proc/<pid>
_へのシンボリックリンクとして表示するように注意します。ここで、_<pid>
_はアプリケーションプロセスIDです。/proc/<pid>/fd
_のシンボリックリンクは、アプリケーションによって開かれた、または親から継承されたファイル、パイプ、その他のものを指します。すべてのアプリケーションは、3つのファイル記述子を開くことが保証されています。入力を読み取るための_0
_、出力を書き込むための_1
_、エラーを書き込むための_2
_です。あなたの場合は_/dev/pts/10
_です。出力を別のファイルにリダイレクトしない場合、シェルによって実行されるすべてのコマンドが端末に書き込みます。このルールの例外は、コマンドのプロセスグループが端末のフォアグラウンドプロセスグループと異なる場合、書き込みの場合とは異なります。失敗すると、SIGTTOU
がコマンドに送信されます。この動作は_stty tostop
_および_stty -tostop
_で制御できます。
_stty tostop
echo "/dev/stdout points to the terminal, but I won't print anything" &
stty -tostop
echo "You can see me" &
_
シェルでコマンドを実行すると、次のようになります。
fork
を呼び出す:両方のプロセスで実行中のシェル。std in/out/err
をセットアップしますが、リダイレクトがない場合は何もしません。新しいプロセスは元のシェルからこれらを継承し、シェルはすでに正しい値を持っています。exec
を呼び出して新しいプログラムを実行します。この新しいプログラムはstd in/out/err
の値を継承し、新しいシェルを置き換えます。この新しいシェルは非常に一時的なものです(実装の詳細であるため、ドキュメントに記載されています)。サブシェルと同じではありません。
/dev/stdin
を開きます新しいプログラムが/dev/stdin
を開くと、カーネル内のファイルシステムコードは、これが/proc/self/fd/0
へのシンボリックリンクであることを確認し、/dev/self
が/proc/nnnn
へのシンボリックリンクであることを確認します。ここで、nnnnはプロセスのpidであるため、これは/proc/nnnn/fd/0
を指します。ファイルを指す例: /dev/pts/10
。 /dev/stdin
を開くと、新しいファイル記述子が作成されます。ファイル記述子0はすでにファイルを指しているため、通常はdev/stdin
を開く必要はありません。
(プログラムがstdinを読み取るように記述されていない場合にのみ実行する必要がありますが、ファイルから読み取ることができます。)(これはすべてstdoutおよびstderrにも当てはまります。)
/proc
のファイルは実際のファイルではありません(どこにも保存されていません)。これらは、(ディスクからではなく)カーネル内のデータ構造からデータを検索するファイルシステムによってアクセスされると(ディスクに書き込まれることはありません)、動的に作成されます。