Raspberry Pi4 screenセッションの確認方法

# 序文
screenコマンド。とても便利で重宝している。
ところが調子に乗って複数の ssh接続で複数の screenセッションを作成し、他の作業と行ったり来たりしていると混乱してくる。
`screen -ls`でセッションの一覧は確認できる。
```shell
$ screen -ls
There are screens on:
        25987.pts-2.raspberrypi4        (2022年04月03日 10時54分18秒)   (Attached)
        25946.pts-0.raspberrypi4        (2022年04月03日 10時54分10秒)   (Attached)
2 Sockets in /run/screen/S-pi.
```
しかし、このコマンドを実行している自分は screenセッション上なのか、もしそうであれば、一覧のどれなのか。ぱっと見、判断できない。
ターミナルのタイトルにセッション情報を表示する、というアプローチもあるけど、今回はコマンド（ワンライナー）で確認する方法を考えてみる。

# 環境
サーバの`Linux`環境は Raspberry Pi OS bullseye、クライアントは Windows10 Home、ターミナルは TeraTerm。
いわゆるラズパイ環境だけど、Linux汎用と思われ。
以下のエイリアスがデフォルトで定義されているので、明示的に強調表示色の指定は省略。自環境に合わせて読み替えること。

```shell
$ alias grep
alias grep='grep --color=auto'
$ alias egrep
alias egrep='egrep --color=auto'
```

# 基本的なコマンド
- pstree
実行中のプロセスをツリー形式で表示する。

| スイッチ | 意味 |
| ---- | ---- |
| -p | PID を表示する。PID は10進数でプロセス名の後に括弧付で表示される。-p を付けると、ツリーのまとめ表示は行わない |
| -s | 選択したプロセスの親を表示する |
| -h | カレントプロセスとその先祖のプロセスを強調表示する。ターミナルが強調表示をサポートしていなかったり、カレントプロセスとその上位グループのいずれもがツリーに属していなければ、この指定は単に無効となる |
| -U | UTF-8 (Unicode) の罫線文字 (line drawing character) を使う |

- ps
現在実行されているプロセスのスナップショットを表示する。

| スイッチ | 意味 |
| ---- | ---- |
| u | ユーザー指向のフォーマット |
| l | BSD の長いフォーマット。親PID（PPID）を表示する |
| p | プロセス ID で選択する。-p, --pid と等しい |
| ax | 全てのプロセス表示 |

`ps`コマンドのスイッチはハイフン（`-`）の有り無しで意味が変わる場合があるので注意。（というかそれが大半か）

`head`コマンドで先頭一行切り出しは`head -1`で良い。`head -n 1`より少しシンプルになる。
しかし、この用法、ドキュメントを見ても載っていないんだよなぁ。（自分が見落としている？）

組込変数。
| 変数 | 意味 |
| ---- | ---- |
| $$ | シェルのプロセスID |
| $PPID | 親のプロセスID |

# 基本的な仕組みの確認
まず自身のシェルプロセスID（`$$`）、親プロセスID（`$PPID`）、セッションの一覧（`screen -ls`）、ツリー構造（`pstree -pshU $$ | egrep "|$$"`）を確認。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/9be38f3e-3cb8-061b-6b0f-11e14a5db04c.png" width=80%>

`egrep`コマンドがプロセスツリー上では`grep`として表示されている。これは、`egrep`の実態が`grep -E`だから。
```shell
$ cat /usr/bin/egrep
#!/bin/sh
exec grep -E "$@"
```
しかし`sshd` `screen`プロセスが二重（親子？）になっているのがよく分からないなぁ。それはさておき。
コマンドを実行している自プロセス（28129）は`bash`であり、`screen`（28128）プロセスの子プロセスであることが分かる。
簡単な図に起こすと以下。
![screen_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/ffade750-57dc-5ffe-c4f9-056e3d0ce2fa.png)
`screen -ls`で表示されるセッション一覧の形式は`pid.tty.host`。
コマンド実行環境（bash）から見て、親（`screen`プロセス）の`PID`がこの一覧に表示されていることが分かる。
つまり、`screen -ls`で親PIDが含まれる行を強調表示すれば良いことになる。コマンド例は以下。
```shell
$ screen -ls | egrep "|$PPID[.].*"
```
実行結果は以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/b4a1e8a0-e602-7f66-ec60-a3082aa23cf6.png" width=70%>
おぉ、分かりやすい。
また、`screen -ls`から全ての`PID`を拾いプロセスツリーを表示するなら以下。
```shell
$ screen -ls | grep $'\t' | sed -e "s/\t\([0-9]*\)[.].*/\1/" | xargs -i pstree -phU {} | egrep "|$$"
```
実行結果は以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/4a4fee85-2495-bfb0-48e7-892d88cf3cf8.png" width=80%>

screenセッション全体と自分の場所が確認でき、これもいい感じ。
また、セッション情報は`/var/run/screen/S-<login>`に格納されている。
```shell
$ ls /var/run/screen/S-`whoami`/
28066.pts-0.raspberrypi4  28128.pts-2.raspberrypi4
```
ここからセッションを拾って上記のように一覧ツリー化する方法も考えられる。コマンド例は以下。
```shell
$ ls /var/run/screen/S-`whoami`/ | sed s/[.].*//g | xargs -i pstree -phU {} | egrep "|$$"
```

実行結果は以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/cedc6f54-06f6-72dd-85fa-88c2be7720d3.png" width=80%>
こちらの方が多少コマンドが短くて済む。また、結果として`screen`セッションの`PID`でソートされている。

# 複数ウィンドウの場合
一つの`screen`セッション内に二つのウィンドウを作成している場合の実行結果は以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/36be95a6-091a-fa9d-e9d2-fefe8f909737.png" width=80%>

つまり、複数のウィンドウを生成しているということは、複数の`bash`プロセスを生成している、という仕組みであることが分かる。簡単な図に起こすと以下。
![screen_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/b828a1b6-a306-57ae-8a8f-53ff98815f59.png)

# sshdを起点に確認
`ssh`接続先の`ssh`サーバである`sshd`サービスの`PID`をまず確認。
```shell
$ systemctl status sshd | grep Loaded:
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
$ grep ExecStart= /lib/systemd/system/ssh.service
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
```
`/usr/sbin/sshd -D`として実行されているプロセスらしいので、それを検索。
```shell
$ ( ps alx | head -1; ps alx | grep "/usr/sbin/sshd -D" | grep -v grep )
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0     577       1  20   0  13644  6924 -      Ss   ?          0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
```
親PIDが`1`（`systemd`）なので、これでビンゴらしい。または`/var/run/sshd.pid`でも確認できる。
```shell
$ cat /var/run/sshd.pid
577
```
但し、後者は複数の`sshd`サービスが起動した場合は最後のものが格納される仕様らしいので注意。
取り合えず、後者を採用した`sshd`サービス配下のツリー表示コマンドは以下。
```shell
$ pstree -phU `cat /var/run/sshd.pid` | egrep "|$$"
```
新規でssh接続しての実行結果が以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/8b1eb3d3-4be0-fa6d-8fec-77604d4f434d.png" width=70%>
この場合、`screen`セッションでの接続を含め、`sshd`サービス配下が全て表示されることが分かる。
ところが、`screen`セッションをデタッチしてから再アタッチし、`screen`セッション上から確認すると以下。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/0b8273a3-16f5-e8fa-f40c-c4d67bc08059.png" width=80%>
デタッチした時点で`screen`セッションの直接の親の`sshd`プロセスは破棄され、`systemd`に差し変わる仕様らしい。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/67648764-0e90-e09e-d4b7-28f89c10007e.png" width=80%>
うーむ。`sshd`プロセスを介していないのにどうやって`ssh`接続しているんだろう？
と思ったけど、この時点の自分は`sshd`を親に持つ`bash`プロセスであり、`screen`セッションを作成する前の接続。
一方、`screen`セッションは`ssh`接続とは独立してサーバーサイドに保持される仕様。
再度`screen`セッションにアタッチする場合は`screen`セッションの子の`bash`に遷移するイメージ。ということになる。
`sshd─bash` `<───>` `screen─bash`
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/451f24fd-5ca2-1629-8022-ff2ff2c2e548.png" width=80%>

# psコマンドで先祖を辿る
`ps`コマンドで`l`スイッチを指定すると親PID（`PPID`列）も表示される。これを利用してカレントのプロセス（`screen`セッション上）から先祖を辿るコマンド例が以下。
```shell
$ ps l | head -1; pid=$$; while [ $pid -ge 1 ]; do ps hlp $pid; pid=`ps hlp $pid | awk '{print $4}'`; done
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000   32419   32418  20   0   6172  4672 do_wai Ss   pts/6      0:00 /bin/bash
1  1000   32418   32417  20   0   5420  2736 do_sel Ss   ?          0:00 SCREEN
0  1000   32417   32398  20   0   5128  2876 do_sys S+   pts/5      0:00 screen
0  1000   32398   32397  20   0   6144  4584 do_wai Ss   pts/5      0:00 -bash
5  1000   32397   32388  20   0  16064  4768 -      S    ?          0:00 sshd: pi@pts/5
4     0   32388     577  20   0  16064  7960 -      Ss   ?          0:00 sshd: pi [priv]
4     0     577       1  20   0  13644  6924 -      Ss   ?          0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
4     0       1       0  20   0 166904 10504 -      Ss   ?          0:06 /sbin/init
```
`/sbin/init`は`systemd`のシンボルリンク。起点である`systemd`に辿りついていることが分かる。
```shell
$ ls -la /sbin/init
lrwxrwxrwx 1 root root 20  3月 21 04:55 /sbin/init -> /lib/systemd/systemd
```
一旦デタッチしてから再度アタッチ。上記コマンドを再実行した結果が以下。やはり`screen`プロセスの親が`systemd`に差し変わっていることが分かる。
```shell
$ ps l | head -1; pid=$$; while [ $pid -ge 1 ]; do ps hlp $pid; pid=`ps hlp $pid | awk '{print $4}'`; done
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000   32419   32418  20   0   6172  4672 do_wai Ss   pts/6      0:00 /bin/bash
1  1000   32418       1  20   0   5420  2748 do_sel Ss   ?          0:00 SCREEN
4     0       1       0  20   0 166904 10504 -      Ss   ?          0:06 /sbin/init
```
`pstree`コマンドでも先祖を辿ることができるけど、こちらの方法は詳細が表示されるので、これはこれでありかも。

それはそうと、気になったのは最後の`systemd`の行。`PPID`列の値が`0`。
`systemd`とは、最初に起動するプロセスであり、`PID`は`1`固定。以下、次々とシステムに必要なサービスが子プロセスとして起動されていく。従ってプロセスの依存関係は`systemd`を起点とした樹形図構造であり、全てのプロセスの親を辿っていくと必ず`systemd`に辿り着く。というのが基本的な仕様。つまり、`systemd`の親は存在しない。
<details><summary>クリックで開閉</summary><div>

```shell
$ pstree
systemd─┬─apache2───5*[apache2]
        ├─applet.py
        ├─avahi-daemon───avahi-daemon
        ├─bluetoothd
        ├─colord───2*[{colord}]
        ├─cron
        ├─cups-browsed───2*[{cups-browsed}]
        ├─cupsd───dbus
        ├─2*[dbus-daemon]
        ├─dhcpcd
        ├─fcitx───mozc_server───2*[{mozc_server}]
        ├─fcitx-dbus-watc
        ├─hciattach
        ├─lightdm─┬─Xorg───{Xorg}
        │         ├─lightdm─┬─lxsession─┬─lxpanel───4*[{lxpanel}]
        │         │         │           ├─lxpolkit───2*[{lxpolkit}]
        │         │         │           ├─openbox
        │         │         │           ├─pcmanfm───2*[{pcmanfm}]
        │         │         │           ├─ssh-agent
        │         │         │           └─2*[{lxsession}]
        │         │         └─2*[{lightdm}]
        │         └─2*[{lightdm}]
        ├─login───bash
        ├─menu-cached───2*[{menu-cached}]
        ├─packagekitd───2*[{packagekitd}]
        ├─polkitd───2*[{polkitd}]
        ├─rngd───3*[{rngd}]
        ├─rpc.statd
        ├─rpcbind
        ├─rsyslogd───3*[{rsyslogd}]
        ├─rtkit-daemon───2*[{rtkit-daemon}]
        ├─screen───2*[bash]
        ├─2*[screen───bash]
        ├─ssh-agent
        ├─sshd─┬─sshd───sshd───bash
        │      ├─2*[sshd───sshd───bash───screen]
        │      └─sshd───sshd───bash───pstree
        ├─systemd─┬─(sd-pam)
        │         ├─dbus-daemon
        │         ├─gvfs-afc-volume───3*[{gvfs-afc-volume}]
        │         ├─gvfs-goa-volume───2*[{gvfs-goa-volume}]
        │         ├─gvfs-gphoto2-vo───2*[{gvfs-gphoto2-vo}]
        │         ├─gvfs-mtp-volume───2*[{gvfs-mtp-volume}]
        │         ├─gvfs-udisks2-vo───3*[{gvfs-udisks2-vo}]
        │         ├─gvfsd─┬─gvfsd-trash───2*[{gvfsd-trash}]
        │         │       └─2*[{gvfsd}]
        │         ├─gvfsd-fuse───5*[{gvfsd-fuse}]
        │         ├─gvfsd-metadata───2*[{gvfsd-metadata}]
        │         ├─pipewire─┬─pipewire-media-───{pipewire-media-}
        │         │          └─{pipewire}
        │         └─pulseaudio───2*[{pulseaudio}]
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-timesyn───{systemd-timesyn}
        ├─systemd-udevd
        ├─thd
        ├─udisksd───4*[{udisksd}]
        ├─vncagent
        ├─vncserver-x11-s───vncserver-x11-c
        ├─vncserverui───vncserverui
        ├─watchdog
        ├─2*[wpa_supplicant]
        └─xcompmgr
```
</div></details>

ところが前述の`ps`コマンドの結果では、`systemd`の`PPID`列の値は`0`。プロセスIDの最小値は`1`という仕様であり、`0`は存在しない筈。`ps`コマンドでもプロセスID に`0`を指定するとエラーになる。

```shell:プロセスID に 1 を指定
$ ps lp 1
F   UID     PID    PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0       1       0  20   0 166904 10504 -      Ss   ?          0:06 /sbin/init
```

```shell:プロセスID に 0 を指定
$ ps lp 0
error: process ID out of range

Usage:
 ps [options]

 Try 'ps --help <simple|list|output|threads|misc|all>'
  or 'ps --help <s|l|o|t|m|a>'
 for additional help text.

For more details see ps(1).
```
`systemd`の`PPID`列の値に`0`が表示されるのは、存在しないことを便宜的に表しているのかな？ と思っていたら、以下が参考になった。
[LinuxおよびSolarisでのPIDの範囲はどのくらいですか？](https://ja.arip-photo.org/130357-what-is-the-range-of-UPBPTX "LinuxおよびSolarisでのPIDの範囲はどのくらいですか？")
> 最小PIDは実際には0です。つまり、実際にはプロセスではないカーネルです...
カーネルはPIDを取得しますか？
必要はありませんが、pid 0が与えられます。LinuxではPPIDとしてのみ表示されると思いますが、Solarisでは間違いなく/ proc / 0エントリがあり、psはプロセス0を「スケジュール済み」として報告します。

へーへーへー。

# まとめ（コピペ用）
| コマンド | 意味 |
| ---- | ---- |
| echo $$ $PPID | 自プロセス、親プロセスID 表示 |
| ps lp $$ | 自プロセス表示（`ps`コマンド`） |
| pstree -pshU `$$` \| egrep "\|$$" | 自プロセスを含むツリー表示 |
| ps l \| head -1; pid=$$; while [ $pid -ge 1 ]; do ps hlp $pid; pid=\`ps hlp $pid \| awk '{print $4}'`; done | 自プロセスを起点に先祖表示（`ps`コマンド版）|
| screen -ls | `screen`セッション一覧表示 |
| screen -ls \| egrep "\|$PPID[.].*" | `screen`セッション一覧表示（自プロセス強調表示版） |
| screen -ls \| grep `$`'\t' \| sed -e "s/\t\\([0-9]\*\\)[.].*/\1/" \| xargs -i pstree -phU {} \| egrep "\|$$" | `screen`セッション一覧ツリー表示（`screen -ls`版） |
| ls /var/run/screen/S-\`whoami`/ \| sed s/[.].*//g \| xargs -i pstree -phU {} \| egrep "\|$$" | `screen`セッション一覧ツリー表示（プロセスファイル版） |
| pstree -phU \`cat /var/run/sshd.pid` \| egrep "\|$$" | `sshd`サービス配下のツリー表示 |

# 参考URL
[pstree(1) — manpages-ja — Debian bullseye — Debian Manpages](https://manpages.debian.org/bullseye/manpages-ja/pstree.1.ja.html "pstree(1) — manpages-ja — Debian bullseye — Debian Manpages")
[ps(1) — manpages-ja — Debian bullseye — Debian Manpages](https://manpages.debian.org/bullseye/manpages-ja/ps.1.ja.html "ps(1) — manpages-ja — Debian bullseye — Debian Manpages")
[screen(1) — manpages-ja — Debian bullseye — Debian Manpages](https://manpages.debian.org/bullseye/manpages-ja/screen.1.ja.html "screen(1) — manpages-ja — Debian bullseye — Debian Manpages")
[#きょうのsystemd_ (翻訳)PID 1 を考え直す - ぽぽの備忘録](https://popopopoon.hatenadiary.jp/entry/2017/11/05/215540 "#きょうのsystemd_ (翻訳)PID 1 を考え直す - ぽぽの備忘録")