Linuxを知っておこう! /proc/<PID>/proc/selfリンクをコピーしました

Linuxを触っていると、たまに/proc/1234/proc/selfのようなパスを見ることがある。

普通のディレクトリのようにlsできるし、普通のファイルのようにcatできる。
でも、そこにあるものはストレージ上に保存されたファイルではない。

/procは、Linuxカーネルが持っている情報をファイルの形で見せてくれる変なディレクトリである。
正確には、procfsという仮想ファイルシステムが/procにマウントされている。
(他にもLinuxには/dev/sysなど、普通のファイルとは違うものがたくさんある。)

今回は、次の2つを解説する。

  • /proc/<PID>: あるプロセスについての情報
  • /proc/self: 今このパスを読んでいるプロセス自身についての情報

/proc は普通のディレクトリではないリンクをコピーしました

まず、/procが何として扱われているかを見てみる。

.console
demo@machine:~$ stat -f -c %T /proc proc demo@machine:~$ mount | grep ' on /proc ' proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

statの結果がprocになっている。
これは、/procがext4やbtrfsのようなストレージ上のファイルシステムではなく、procfsとして扱われているという意味だ。

もう1つ、普通のファイルとの違いが分かりやすい例を見てみる。

.console
demo@machine:~$ ls -lh /proc/uptime -r--r--r-- 1 root root 0 Jun 28 06:26 /proc/uptime demo@machine:~$ cat /proc/uptime 258839.91 604089.14

lsではサイズが0に見えるのに、catすると中身が出てくる。
ディスク上に固定の内容が保存されているのではなく、読むたびにカーネルがその時点の情報を返しているからだ。

/proc直下には、プロセスごとの情報だけでなく、システム全体の情報もある。

パス 何が分かるか
/proc/cpuinfo CPUの情報
/proc/meminfo メモリの情報
/proc/uptime 起動してからの時間
/proc/mounts マウントされているファイルシステム
/proc/sys/ カーネルの設定値

この記事で扱う/proc/<PID>/proc/selfは、その中でも「プロセスごとの情報」を見るための入口だ。

まずプロセスを確認するリンクをコピーしました

プロセスは、実行中のプログラムのこと。

たとえばターミナルでbashを開いているなら、そのbashも1つのプロセス。
プロセスについての情報を確認するpsコマンドを実行すると、そのpsも一瞬だけプロセスとして動く。

.console
demo@machine:~$ ps -o pid,ppid,comm PID PPID COMMAND 1130855 1129979 bash 1131087 1130855 ps

左端のPIDがプロセス番号。
この例では、bashのPIDが1130855psのPIDが1131087

PPIDは親プロセスのPID。
psbashから起動されたので、psのPPIDは1130855になっている。

.mermaid
flowchart LR A["bash<br>PID 1130855"] --> B["ps<br>PID 1131087"]

/proc/<PID> はプロセスの情報置き場リンクをコピーしました

PIDが分かると、そのプロセスの情報を/proc/<PID>から見られる。

先ほどの例なら、bashの情報は/proc/1130855にある。

.console
demo@machine:~$ ls /proc/1130855 arch_status fdinfo net setgroups attr gid_map ns smaps autogroup io numa_maps smaps_rollup auxv ksm_merging_pages oom_adj stack cgroup ksm_stat oom_score stat clear_refs latency oom_score_adj statm cmdline limits pagemap status comm loginuid patch_state syscall coredump_filter map_files personality task cpu_resctrl_groups maps projid_map timens_offsets cwd mem root timers environ mountinfo sched timerslack_ns exe mounts schedstat uid_map fd mountstats sessionid wchan

ここに並んでいるcmdlinestatusは、普通の設定ファイルではない。
カーネルが「このプロセスはいまこういう状態です」と見せている仮想的なファイルだ。

よく使われるものだと以下のようなものがある。

パス 何が分かるか
/proc/<PID>/cmdline そのプロセスを起動したコマンドライン
/proc/<PID>/environ 起動時に渡された環境変数
/proc/<PID>/status PID、PPID、UID、状態などの要約
/proc/<PID>/cwd そのプロセスのカレントディレクトリ
/proc/<PID>/exe そのプロセスの実行ファイル
/proc/<PID>/fd/ そのプロセスが開いているファイル

まずはstatusが読みやすい。

.console
demo@machine:~$ sed -n '1,10p' /proc/1130855/status Name: bash Umask: 0002 State: S (sleeping) Tgid: 1130855 Ngid: 0 Pid: 1130855 PPid: 1129979 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000

psで見たPIDやPPIDと同じ情報がファイルとしても読める。
psのようなプロセス確認ツールも、Linuxでは主に/proc配下の情報を参照して表示を作っている。

なお、/proc/<PID>は生きているプロセスに対応している。
プロセスが終了すると、そのPIDのディレクトリも基本的には消える。

.console
demo@machine:~$ sleep 60 & [1] 1171339 demo@machine:~$ pid=$! demo@machine:~$ ls /proc/$pid/status /proc/1171339/status demo@machine:~$ kill $pid demo@machine:~$ wait $pid 2>/dev/null demo@machine:~$ ls /proc/$pid/status ls: cannot access '/proc/1171339/status': No such file or directory

/proc/<PID>は過去のログ置き場ではなく、今いるプロセスを覗く場所だと考えると分かりやすい。

/proc/self は読んでいるプロセス自身リンクをコピーしました

次に/proc/self

名前だけ見ると「自分自身」なので、今開いているシェルのことに見える。
しかし正確には、/proc/selfは「そのパスへアクセスしたプロセス自身」を指す。

確認してみる。

.console
demo@machine:~$ echo $$ 1130855 demo@machine:~$ readlink /proc/self 1135077

echo $$で出ている1130855は、今いるシェルのPID。

一方、readlink /proc/selfで出ている1135077は、readlinkコマンド自身のPID。
/proc/selfを読んだのはシェルではなく、シェルから起動されたreadlinkだからだ。

.mermaid
sequenceDiagram participant Shell as bash (PID:1130855) participant Readlink as readlink (PID:1135077) participant Proc as /proc/self Shell->>Readlink: readlink /proc/self を起動 Readlink->>Proc: /proc/self を読む Proc-->>Readlink: /proc/1135077 として解決

/proc/selfは固定のディレクトリではなく、アクセスするプロセスによって、指す先が変わる。

この性質は、catで見るとさらに分かりやすい。

.console
demo@machine:~$ cat /proc/self/status | grep '^Name:' Name: cat

人間としてはシェルからコマンドを打っているが、/proc/self/statusを開いて読んだのはcat
そのため、表示されるNamebashではなくcatになる。

逆に、1つのプログラムの中で自分のPIDと/proc/selfを比べると、同じものを指していることが分かる。

.console
demo@machine:~$ python3 -c 'import os; print(os.getpid()); print(os.readlink("/proc/self"))' 1173992 1173992

python3プロセス自身が/proc/selfを読んでいるので、os.getpid()の結果と/proc/selfの解決先が一致する。

シェル自身を見たいなら $$ を使うリンクをコピーしました

ターミナルで作業していて「今のシェル自身の/procを見たい」なら、/proc/selfではなく/proc/$$を使う。

$$は、現在のシェルのPIDに展開される。

.console
demo@machine:~$ echo $$ 1130855 demo@machine:~$ readlink /proc/$$/exe /usr/bin/bash demo@machine:~$ sed -n '1,6p' /proc/$$/status Name: bash Umask: 0002 State: S (sleeping) Tgid: 1130855 Ngid: 0 Pid: 1130855

一方で、実行中のコマンド自身に自分の情報を参照させたいなら/proc/selfが便利。

.console
demo@machine:~$ readlink /proc/self/exe /usr/lib/cargo/bin/coreutils/readlink

この例では/proc/self/exeを読んだのがreadlinkなので、readlink自身の実行ファイルが表示される。

cmdline は起動コマンドリンクをコピーしました

cmdlineには、そのプロセスがどのようなコマンドラインで起動されたかが入っている。

ただし、文字列はスペース区切りではなくNUL文字、つまり\0で区切られている。
そのままだと読みづらいので、trを用いて改行に変換すると見やすい。

.console
demo@machine:~$ tr '\0' '\n' < /proc/$$/cmdline bash

別のコマンドで見ると、/proc/selfが「そのコマンド自身」を指すことも分かる。

.console
demo@machine:~$ tr '\0' '\n' < /proc/self/cmdline tr \0 \n

この/proc/self/cmdlineを読んでいるのはtrなので、tr自身の引数が表示されている。

environ は起動時の環境変数リンクをコピーしました

environには、そのプロセスに渡された環境変数が入っている。
これもNUL区切りなので、trで改行に変換する。

.console
demo@machine:~$ tr '\0' '\n' < /proc/$$/environ | head SHELL=/bin/bash USER=demo HOME=/home/demo PWD=/home/demo PATH=/usr/local/bin:/usr/bin:/bin

/proc/<PID>/environで見えるのは、基本的にはプロセスが起動した時点の環境変数。
プログラムが起動後に環境変数を書き換えた場合、その変更がここに期待通り反映されるとは限らない。

「現在の環境変数をいつでも正確に見られるファイル」というより、「そのプロセスが起動時に持っていた環境を確認できる場所」と考える方が安全。

cwdexeroot はリンクリンクをコピーしました

cwdexerootはファイルではなくシンボリックリンク。
readlinkで見ると分かりやすい。

.console
demo@machine:~$ readlink /proc/$$/cwd /home/demo demo@machine:~$ readlink /proc/$$/exe /usr/bin/bash demo@machine:~$ readlink /proc/$$/root /

それぞれの意味はこう。

パス 意味
/proc/<PID>/cwd そのプロセスのカレントディレクトリ
/proc/<PID>/exe そのプロセスの実行ファイル
/proc/<PID>/root そのプロセスから見たルートディレクトリ

たとえば、あるプロセスがどのディレクトリで動いているのかを知りたいならcwdを見る。
どの実行ファイルから動いているかを知りたいならexeを見る。

fd は開いているファイルリンクをコピーしました

fdディレクトリには、そのプロセスが開いているファイルが並ぶ。

.console
demo@machine:~$ ls -l /proc/$$/fd total 0 lrwx------ 1 demo demo 64 Jun 30 20:55 0 -> /dev/pts/4 lrwx------ 1 demo demo 64 Jun 30 20:55 1 -> /dev/pts/4 lrwx------ 1 demo demo 64 Jun 30 20:55 2 -> /dev/pts/4 lrwx------ 1 demo demo 64 Jun 30 20:57 255 -> /dev/pts/4

012は特別によく使われる。

番号 名前 意味
0 標準入力 キーボード入力やパイプからの入力
1 標準出力 通常の出力先
2 標準エラー エラーメッセージの出力先

「プロセスが今どのファイルや端末を開いているか」を見る入口がfd
通常のファイルだけでなく、端末、pipe、socketなどもここに現れる。

たとえば、パイプでつないだコマンドの標準入力はpipeになることがある。

.console
demo@machine:~$ echo hello | cat /proc/self/fd/0 hello

この場合、catから見ると/proc/self/fd/0は自分の標準入力を指す。
ファイル名を受け取るプログラムに「標準入力を読んでほしい」と伝えたいとき、/proc/self/fd/0のようなパスが役に立つことがある。

読めないこともあるリンクをコピーしました

/procは何でも見放題というわけではない。

自分のプロセスは読めても、別ユーザーのプロセスは権限で読めないことがある。
システムの設定によっては、他人の/proc/<PID>自体が見えにくくされていることもある。

.console
demo@machine:~$ tr '\0' '\n' < /proc/1/environ bash: /proc/1/environ: Permission denied

これは正常な挙動。
/procは便利な観察用インターフェースだが、プロセスの情報には環境変数や開いているファイルなど、見えてほしくないものも含まれるため、権限チェックが入る。

特にenvironfdは、そのプロセスの実行環境や開いているファイルが見えるため、読める範囲が制限されやすい。
また、システムによっては/prochidepidオプション付きでマウントされ、他ユーザーのプロセス情報が見えにくくなっていることもある。

まとめリンクをコピーしました

/procは、カーネルが持つ情報をファイルの形で見せる仮想ファイルシステム。
普通のファイルのように見えても、読むたびにカーネルがその時点の情報を返している。

/proc/<PID>を見ると、そのPIDを持つプロセスの情報が分かる。
statusで概要、cmdlineで起動コマンド、environで起動時の環境変数、cwdexeでパス、fdで開いているファイルを確認できる。

/proc/selfは「現在のシェル」ではなく、「そのパスを読んでいるプロセス自身」を指す。
シェル自身を見たいなら、まずは/proc/$$を使うと理解しやすい。

この違いが分かると、/proc/<PID>/proc/selfはかなり読みやすくなる。

参考リンクをコピーしました