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が何として扱われているかを見てみる。
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つ、普通のファイルとの違いが分かりやすい例を見てみる。
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も一瞬だけプロセスとして動く。
demo@machine:~$ ps -o pid,ppid,comm
PID PPID COMMAND
1130855 1129979 bash
1131087 1130855 ps
左端のPIDがプロセス番号。
この例では、bashのPIDが1130855、psのPIDが1131087。
PPIDは親プロセスのPID。psはbashから起動されたので、psのPPIDは1130855になっている。
flowchart LR
A["bash<br>PID 1130855"] --> B["ps<br>PID 1131087"]/proc/<PID> はプロセスの情報置き場リンクをコピーしました
PIDが分かると、そのプロセスの情報を/proc/<PID>から見られる。
先ほどの例なら、bashの情報は/proc/1130855にある。
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
ここに並んでいるcmdlineやstatusは、普通の設定ファイルではない。
カーネルが「このプロセスはいまこういう状態です」と見せている仮想的なファイルだ。
よく使われるものだと以下のようなものがある。
| パス | 何が分かるか |
|---|---|
/proc/<PID>/cmdline |
そのプロセスを起動したコマンドライン |
/proc/<PID>/environ |
起動時に渡された環境変数 |
/proc/<PID>/status |
PID、PPID、UID、状態などの要約 |
/proc/<PID>/cwd |
そのプロセスのカレントディレクトリ |
/proc/<PID>/exe |
そのプロセスの実行ファイル |
/proc/<PID>/fd/ |
そのプロセスが開いているファイル |
まずはstatusが読みやすい。
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のディレクトリも基本的には消える。
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は「そのパスへアクセスしたプロセス自身」を指す。
確認してみる。
demo@machine:~$ echo $$
1130855
demo@machine:~$ readlink /proc/self
1135077
echo $$で出ている1130855は、今いるシェルのPID。
一方、readlink /proc/selfで出ている1135077は、readlinkコマンド自身のPID。/proc/selfを読んだのはシェルではなく、シェルから起動されたreadlinkだからだ。
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で見るとさらに分かりやすい。
demo@machine:~$ cat /proc/self/status | grep '^Name:'
Name: cat
人間としてはシェルからコマンドを打っているが、/proc/self/statusを開いて読んだのはcat。
そのため、表示されるNameもbashではなくcatになる。
逆に、1つのプログラムの中で自分のPIDと/proc/selfを比べると、同じものを指していることが分かる。
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に展開される。
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が便利。
demo@machine:~$ readlink /proc/self/exe
/usr/lib/cargo/bin/coreutils/readlink
この例では/proc/self/exeを読んだのがreadlinkなので、readlink自身の実行ファイルが表示される。
cmdline は起動コマンドリンクをコピーしました
cmdlineには、そのプロセスがどのようなコマンドラインで起動されたかが入っている。
ただし、文字列はスペース区切りではなくNUL文字、つまり\0で区切られている。
そのままだと読みづらいので、trを用いて改行に変換すると見やすい。
demo@machine:~$ tr '\0' '\n' < /proc/$$/cmdline
bash
別のコマンドで見ると、/proc/selfが「そのコマンド自身」を指すことも分かる。
demo@machine:~$ tr '\0' '\n' < /proc/self/cmdline
tr
\0
\n
この/proc/self/cmdlineを読んでいるのはtrなので、tr自身の引数が表示されている。
environ は起動時の環境変数リンクをコピーしました
environには、そのプロセスに渡された環境変数が入っている。
これもNUL区切りなので、trで改行に変換する。
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で見えるのは、基本的にはプロセスが起動した時点の環境変数。
プログラムが起動後に環境変数を書き換えた場合、その変更がここに期待通り反映されるとは限らない。
「現在の環境変数をいつでも正確に見られるファイル」というより、「そのプロセスが起動時に持っていた環境を確認できる場所」と考える方が安全。
cwd、exe、root はリンクリンクをコピーしました
cwd、exe、rootはファイルではなくシンボリックリンク。readlinkで見ると分かりやすい。
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ディレクトリには、そのプロセスが開いているファイルが並ぶ。
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
0、1、2は特別によく使われる。
| 番号 | 名前 | 意味 |
|---|---|---|
0 |
標準入力 | キーボード入力やパイプからの入力 |
1 |
標準出力 | 通常の出力先 |
2 |
標準エラー | エラーメッセージの出力先 |
「プロセスが今どのファイルや端末を開いているか」を見る入口がfd。
通常のファイルだけでなく、端末、pipe、socketなどもここに現れる。
たとえば、パイプでつないだコマンドの標準入力はpipeになることがある。
demo@machine:~$ echo hello | cat /proc/self/fd/0
hello
この場合、catから見ると/proc/self/fd/0は自分の標準入力を指す。
ファイル名を受け取るプログラムに「標準入力を読んでほしい」と伝えたいとき、/proc/self/fd/0のようなパスが役に立つことがある。
読めないこともあるリンクをコピーしました
/procは何でも見放題というわけではない。
自分のプロセスは読めても、別ユーザーのプロセスは権限で読めないことがある。
システムの設定によっては、他人の/proc/<PID>自体が見えにくくされていることもある。
demo@machine:~$ tr '\0' '\n' < /proc/1/environ
bash: /proc/1/environ: Permission denied
これは正常な挙動。/procは便利な観察用インターフェースだが、プロセスの情報には環境変数や開いているファイルなど、見えてほしくないものも含まれるため、権限チェックが入る。
特にenvironやfdは、そのプロセスの実行環境や開いているファイルが見えるため、読める範囲が制限されやすい。
また、システムによっては/procがhidepidオプション付きでマウントされ、他ユーザーのプロセス情報が見えにくくなっていることもある。
まとめリンクをコピーしました
/procは、カーネルが持つ情報をファイルの形で見せる仮想ファイルシステム。
普通のファイルのように見えても、読むたびにカーネルがその時点の情報を返している。
/proc/<PID>を見ると、そのPIDを持つプロセスの情報が分かる。statusで概要、cmdlineで起動コマンド、environで起動時の環境変数、cwdやexeでパス、fdで開いているファイルを確認できる。
/proc/selfは「現在のシェル」ではなく、「そのパスを読んでいるプロセス自身」を指す。
シェル自身を見たいなら、まずは/proc/$$を使うと理解しやすい。
この違いが分かると、/proc/<PID>と/proc/selfはかなり読みやすくなる。