Linux基礎

【図解】file descriptorと標準入力/出力とパイプ,リダイレクト

標準入出力とファイルディスクリプターの関係性

全てのプロセスは『ファイルディスクリプタ (fd: file descriptor)』というファイル書き込み用の通信チャネルを持っています。

ファイルディスクリプタ (fd) は番号で書き込み先ファイルを識別しており、必要なタイミングで必要な分だけ生成されます。例えば番号が 3456 なら fd:3456 と表現されます。

ですが、その中でも以下の 3 つはプロセス生成時に必ず作られます。

fd:0 = 標準入力
fd:1 = 標準出力
fd:2 = 標準エラー出力

0 だけが特殊で、プロセスへの入力を行うための標準的な入力チャネルです。

1 以降はプロセスが出力するためのチャネルですが、中でも 1 は標準的な出力チャネル、2 は標準的なエラー出力チャネルです。3 以降は汎用としてプログラムが任意に利用できます。

つまり、「標準」というのは「デフォルトの」というニュアンスになります。特段指示がなければここから入力、ここへ出力するよ、という意味です。

なお、ここで言うファイルというのは「一般ファイル」に限らず、キーボード入力やモニターといったキャラクタデバイス等の「スペシャルファイル」も含まれます。

具体的に見てみましょう。

ファイルディスクリプタは概念なのでその実体を捉えることはできませんが、以下コマンドで番号と出力先を確認することができます。

[root@localhost ~]# ls -l /proc/[プロセスのpid]/fd/

例えば他に誰もログインしていない状態で ssh 接続すると、bash プロセスが生成されると同時に、pts/0 という擬似端末が与えられます。

ssh 接続を行ったコンソールは /dev/pts/0 というキャラクタデバイスとリンクしています。

[root@localhost ~]# ps aux | grep bash
root     15223  0.0  0.2 115440  2032 pts/0    Ss   17:48   0:00 -bash
root     15242  0.0  0.0 112724   980 pts/0    R+   17:48   0:00 grep --color=auto bash
[root@localhost ~]# ls -l | /proc/15223/fd/
合計 0
lrwx------. 1 root root 64  2月 28 17:48 0 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 1 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 2 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 255 -> /dev/pts/0

fd ディレクトリの配下にファイルディスクリプタの番号のファイルがシンボリックリンクとして存在しており、リンク先が出力先のファイルになっています。

この出力結果より、この bash プロセスが標準入力,標準出力,標準エラー 3 つとも /dev/pts/0 に向いていることが分かります。

なのでコンソールから入力したコマンドは bash に渡されるし、その結果はコンソールに戻ってきます。

[root@localhost ~]# echo test
test

もう少し別のものも見てみましょう。例えば rsyslogd のプロセス。

[root@localhost ~]# ps aux | grep rsyslog
root      3195  0.0  0.4 214560  4352 ?        Ssl   2月27   0:06 /usr/sbin/rsyslogd -n
root     15245  0.0  0.0 112724   984 pts/0    R+   17:49   0:00 grep --color=auto rsyslog
[root@localhost ~]# ls -l /proc/3195/fd/
合計 0
lr-x------. 1 root root 64  2月 27 13:18 0 -> /dev/null
l-wx------. 1 root root 64  2月 27 13:18 1 -> /dev/null
l-wx------. 1 root root 64  2月 27 13:18 2 -> /dev/null
lr-x------. 1 root root 64  2月 27 13:18 3 -> anon_inode:inotify
lrwx------. 1 root root 64  2月 27 13:18 4 -> socket:[24126]
lr-x------. 1 root root 64  2月 27 13:18 5 -> /run/log/journal/ea94430614514eb692303cbd86320983/system.journal
l-wx------. 1 root root 64  2月 27 13:18 6 -> /var/log/messages
l-wx------. 1 root root 64  2月 27 13:18 7 -> /var/log/cron
l-wx------. 1 root root 64  2月 27 13:18 8 -> /var/log/secure
l-wx------. 1 root root 64  2月 27 13:18 9 -> /var/log/maillog

標準入出力やエラー出力は /dev/null となっています。つまり標準入出力やエラー出力は使えないということです。

代わりに journald からの入力を fd:5 で受けており、fd:6 から fd:9 まででログファイルに出力していることが分かります。(左端のアクセス権に注目。r は入力可、w は出力可、となります。)

ファイルディスクリプタの継承

先ほどの例で bash から echo を呼び出しましたが、実は正確に言うと、(bash プロセスの標準出力ではなく) echo コマンドのプロセスの標準出力が /dev/pts/0 になっているため、結果がコンソールに戻ってきているのです。

bash からコマンドを実行すると、(clone といった子プロセスを生成するシステムコールが呼び出され、) bash の子プロセスとして (execve というシステムコールによって) 実行されます。

clone 等により生成された子プロセスはファイルディスクリプタを継承するため、このような結果になります。

パイプの仕組み

パイプとは、あるプロセスの標準出力を、別のプロセスの標準入力と繋げることです。

例えば ファイル test.txt というファイルから secret という文字列が存在する行だけを抽出したい場合は以下のように打ちます。

[root@localhost ~]# cat test.txt | grep secret

このときの状態を図示すると以下のようになります。

リダイレクトの仕組み

リダイレクトとは、あるプロセスの標準出力を変更することです。つまり、デフォルトから変更する、ということです。

例えば

[root@localhost ~]# echo test

とした場合は /dev/pts/0 等のコンソールに出力しますが、

[root@localhost ~]# echo test > /root/hoge.txt

とした場合はコンソールには出力されず、代わりに /root/hoge.txt が生成され、中身が test となります。

[root@localhost ~]# cat /root/hoge.txt
test

これを応用してみましょう。

もう 1 つ ssh 接続を増やし、pts/1 という擬似端末を作ってみます。これも同様に標準入出力を持っています。

[root@localhost ~]# ls -l /proc/[pid]/fd/

このとき、1 つ目の SSH セッションからリダイレクトを使って、2つ目の SSH セッションのコンソールに出力することもできます。

pts/0 のコンソール(下図の左側)から

echo test > /dev/pts/1

とすると、2つ目のSSHセッションのコンソール(下図の右側)に test と表示されます。

SQUID の fd 枯渇と上限値の設定変更

SQUID のログ /var/log/squid/cache.log に以下のエラーが出た時は File Descriptors が上限値に達し、枯渇した状態です。

WARNING! Your cache is running out of filedescriptors

SQUID のファイルディスクリプタの数は cat /proc/[squid の PID]/limits で確認できます。Max open files の項目が上限値です。CentOS8 + SQUID4.4 ではデフォルトで 16384 になっています。

[root@localhost ~]# ps ax | grep squid
11820 ?        Ss     0:00 /usr/sbin/squid -f /etc/squid/squid.conf
11822 ?        S      0:00 (squid-1) --kid squid-1 -f /etc/squid/squid.conf
11823 ?        S      0:00 (logfile-daemon) /var/log/squid/access.log
11877 pts/0    R+     0:00 grep --color=auto squid
[root@localhost ~]# cat /proc/11820/limits | grep "open files"
Max open files            16384                16384                files
[root@localhost ~]# cat /proc/11822/limits | grep "open files"
Max open files            16384                16384                files
[root@localhost ~]# cat /proc/11823/limits | grep "open files"
Max open files            16384                16384                files

SQUID でファイルディスクリプタの上限値を増やすには /etc/squid/squid.conf に以下を追記します。

max_filedescriptors 32768

そしてSQUID を再起動します。

[root@localhost ~]# systemctl restart squid

コメント

タイトルとURLをコピーしました