web-dev-qa-db-ja.com

/ dev / pts / 10をstdout / stdin / stderrに接続するための責任は何ですか?

端末の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(それぞれプログラミング定数stdinstdout、および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ディレクトリのデバイスノードを使用してマジックを実装します。

2
telcoM

_/dev/pts/10_は 疑似端末デバイスのペア のスレーブ側です。もう一方の端には、マスタークローンデバイス_/dev/ptmx_を開き、ペアとして_/dev/pts/10_を受け取ったプログラムがあります(_/dev/ptmx_を開くたびに、別のスレーブを取得します)。 _/dev/ptmx_と_/dev/pts/10_の間の接続は、基本的に双方向パイプです ねじれあり

ターミナルエミュレータアプリケーションを開くと、次のようになります。

  • _/dev/ptmx_を開き、相手側の名前を取得します。もう一方の端を構成して開き、
  • フォーク、
  • 新しいプロセスは、疑似端末デバイスのもう一方の端を開き、そのstdinstdout、およびstderrを接続します。 それに、
  • 新しいプロセスがシェルを実行します。

シェルはこれら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/*_から読み取られたものはすべて、端末に接続された入力デバイスから取得されます。
  • linuxでは、_/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" &
_
5

シェルでコマンドを実行する

シェルでコマンドを実行すると、次のようになります。

  • シェルが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のファイルは実際のファイルではありません(どこにも保存されていません)。これらは、(ディスクからではなく)カーネル内のデータ構造からデータを検索するファイルシステムによってアクセスされると(ディスクに書き込まれることはありません)、動的に作成されます。

0
ctrl-alt-delor