procfsの実装からNetBSDとLinuxでのスタンスの違い(？)を読み解いてみる

[NetBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/netbsd) 4日目の記事です。
今日は `/proc` における、NetBSDとLinuxでのスタンスの違い(？)をソースコードから読み解いて行こうと思います。

## NetBSDでの/proc

[Linux Advent Calendar 2021](https://qiita.com/advent-calendar/2021/linux)の[3日目の記事](https://qiita.com/furandon_pig/items/ee393ee89c39232a4cdb)で逆ポーランド計算機をカーネル内で実装し、 `/proc/krpn` でユーザランドからアクセスするという記事を書きました。

 * [逆ポーランド計算機カーネルモジュール(仮)を作ってみる](https://qiita.com/furandon_pig/items/ee393ee89c39232a4cdb)

ふと、「NetBSDでも同様に `/proc` を動的に作成・削除できそうな気がする…」と調査してみたのですが、その過程で `/proc` に対するスタンスの違い(？)が見て取れたので紹介してみようと思います。

## ソースコードから/procの実装を見てゆく

### /procの大まかな定義方法を調べる

ソースコードを読み解くにあたり、取っ掛かりを見つけたいところです。 `/proc` の下を見てみると、 `/proc/version` や `/proc/cpuinfo` といったファイルが見えます。このあたりをキーワードにして探すのが良さそうです。

```sh
# ls -l /proc/ | tail
-r--r--r--  1 root     wheel      0 Dec  6 23:40 cpuinfo
lr-xr-xr-x  1 root     wheel      4 Dec  6 23:40 curproc@ -> 5886
-r--r--r--  1 root     wheel      0 Dec  6 23:40 devices
-r--r--r--  1 root     wheel      0 Dec  6 23:40 loadavg
-r--r--r--  1 root     wheel      0 Dec  6 23:40 meminfo
-r--r--r--  1 root     wheel      0 Dec  6 23:40 mounts
lr-xr-xr-x  1 root     wheel      4 Dec  6 23:40 self@ -> curproc
-r--r--r--  1 root     wheel      0 Dec  6 23:40 stat
-r--r--r--  1 root     wheel      0 Dec  6 23:40 uptime
-r--r--r--  1 root     wheel      0 Dec  6 23:40 version
```

とりあえず"version"というキーワードで検索してみると、 `sys/miscfs/procfs` というディレクトリがあり、その中の `procfs_vnops.c` に `PFSversion` という定義があります。

```sh
# cd /usr/src/sys
# find ./miscfs/procfs/ | xargs grep version | grep procfs
...
./miscfs/procfs/procfs_vfsops.c:      case PFSversion:        /* /proc/version = -r--r--r-- */
./miscfs/procfs/procfs_vnops.c:       { DT_REG, N("version"),     PFSversion,        procfs_validfile_linux },
./miscfs/procfs/procfs_vnops.c:       case PFSversion:
./miscfs/procfs/procfs_vnops.c:       case PFSversion:
```

`procfs_vnops.c` を見ると、`N("version")` や `N("cpuinfo")` といった、 `/proc` の下にあるファイルと同じ名前の定義がありました。

```sh
sys/miscfs/procfs/procfs_vnops.c:
 189 /*
 190  * List of files in the root directory. Note: the validate function will
 191  * be called with p == NULL for these ones.
 192  */
 193 static const struct proc_target proc_root_targets[] = {
 194 #define N(s) sizeof(s)-1, s
 195     /*    name          type        validp */
 196     { DT_REG, N("meminfo"),     PFSmeminfo,        procfs_validfile_linux },
 197     { DT_REG, N("cpuinfo"),     PFScpuinfo,        procfs_validfile_linux },
 198     { DT_REG, N("uptime"),      PFSuptime,         procfs_validfile_linux },
 199     { DT_REG, N("mounts"),      PFSmounts,         procfs_validfile_linux },
 200     { DT_REG, N("devices"),     PFSdevices,        procfs_validfile_linux },
 201     { DT_REG, N("stat"),        PFScpustat,        procfs_validfile_linux },
 202     { DT_REG, N("loadavg"),     PFSloadavg,        procfs_validfile_linux },
 203     { DT_REG, N("version"),     PFSversion,        procfs_validfile_linux },
 204 #undef N
 205 };
```

`struct proc_target` の定義を見ると、 `*pt_name` に `/proc` のノード名、 `*pt_valid` に `/proc` のハンドラとなる関数を指定すれば良さそうです。

```c
miscfs/procfs/procfs_vnops.c:
 150 static const struct proc_target {
 151     u_char  pt_type;
 152     u_char  pt_namlen;
 153     const char  *pt_name;
 154     pfstype pt_pfstype;
 155     int (*pt_valid)(struct lwp *, struct mount *);
 156 } proc_targets[] = {
...
```

### /procのノード名は決め打ちにされている？

あらためて `/proc/version` に相当する定義を見てみます。マクロ `N("version")` が `sizeof(s)-1` と `s` の2つの定義に展開されるため、 `struct proc_target` と照らし合わせると `N("version")` が `/proc` のノード名になり、 `PFSversion` が `struct proc_target.pt_pfstype` に渡されるようです。

```sh
sys/miscfs/procfs/procfs_vnops.c:
 193 static const struct proc_target proc_root_targets[] = {
 194 #define N(s) sizeof(s)-1, s
 195     /*    name          type        validp */
...
 203     { DT_REG, N("version"),     PFSversion,        procfs_validfile_linux },
 204 #undef N
 205 };
```

`PFSversion` は `/proc` のノード毎に処理を分岐させるために利用される気がします。実際にソースコードを見てみると以下のenumで定数化されています。

```c
miscfs/procfs/procfs.h:
 78 /*
 79  * The different types of node in a procfs filesystem
 80  */
 81 typedef enum {
...
114     PFSversion, /* kernel version (if -o linux) */
...
119 } pfstype;
```

これで `/proc` を動的に生やすための大まかな流れが把握できたような…と思ったのですが、 `PFSversion` に相当する値はenumで定義されているため、 `typedef enum { ... } pfstype` の範囲を超える値を指定してもダメなような…。加えて、範囲内の値でも別の `/proc` ノードと重複してしまうのでうまく動作しないはずです…。

### /procノードを増やすにはカーネルの再ビルドが必要

もう少し調べてみます。 `PFSversion` を参照している個所を見ると、 `procfs_rw()` 関数の中で `/proc` のノードによって処理を分岐しています。 `miscfs/procfs/procfs_subr.c` はファイル名の通り、 `procfs` 自身の処理ルーチンとなっています。

この実装内容から考えるに、Linuxでの実装とは異なり、カーネルモジュールのような形で `/proc` ノードを生やしたりすることはできなさそうです。

```c
miscfs/procfs/procfs_subr.c:
140 int
141 procfs_rw(void *v)
142 {
...
191     switch (pfs->pfs_type) {
...
277     case PFSversion:
278         error = procfs_doversion(curl, p, pfs, uio);
279         break;
...
291     default:
292         error = EOPNOTSUPP;
293         break;
294     }
```

### この/procのスタンスの違いはなぜ？

こうやって調べてみると、NetBSDとLinuxでは `/proc` の利用シーンに対するスタンスの違いが見えてみます。

カーネルモジュールのサンプルは `/usr/src/sys/modules/examples` に置かれているのですが、探しても `/proc` を操作するサンプルが見当たらなかったのは、そもそもそういった使い方を想定していないからなのかもしれません…。

[mount_procfs(8)](https://man.netbsd.org/NetBSD-8.0/mount_procfs.8)を見ると、 `/proc` で提供されるファイル( `/proc` ノード)の一覧が記載されています。

```
FILES

     /proc/#
     /proc/#/cmdline
     /proc/#/cwd
     /proc/#/exe
     /proc/#/file
     /proc/#/fpregs
     /proc/#/map
     /proc/#/maps
     /proc/#/mem
     /proc/#/note
     /proc/#/notepg
     /proc/#/regs
     /proc/#/root
     /proc/#/status
     /proc/curproc
     /proc/self
```

考えてみれば、 `/proc` は「プロセスに関する情報を提供するファイルシステム」であるため、プロセスに関連しないものや動的に生えてくる任意のプロセス情報といったものは提供しない(させない)ようにしている、とも言えます。

じつは `/proc/echo` みたいなサンプルを用意しようと考えていたのですが、こうやって一通り調べた後で考え直してみると、 `/proc` の用途にマッチしていませんね…。

## まとめ

NetBSDでの `/proc` の実装を調べてみました。併せてLinuxと照らし合わせてみると、 `/proc` の実装スタンスの違いも見えてきました。
プロセスの情報を返す、という `/proc` の元々の用途を考えると、NetBSDの実装は意図も踏まえた内容にも思えてきます。

## 参考URL

 * [procfs](https://ja.wikipedia.org/wiki/Procfs)(Wikipedia)
 * [mount_procfs(8)](https://man.netbsd.org/NetBSD-8.0/mount_procfs.8)