NetBSD Advent Calendar 2021 4日目の記事です。
今日は /proc における、NetBSDとLinuxでのスタンスの違い(?)をソースコードから読み解いて行こうと思います。
NetBSDでの/proc
Linux Advent Calendar 2021の3日目の記事で逆ポーランド計算機をカーネル内で実装し、 /proc/krpn でユーザランドからアクセスするという記事を書きました。
ふと、「NetBSDでも同様に /proc を動的に作成・削除できそうな気がする…」と調査してみたのですが、その過程で /proc に対するスタンスの違い(?)が見て取れたので紹介してみようと思います。
ソースコードから/procの実装を見てゆく
/procの大まかな定義方法を調べる
ソースコードを読み解くにあたり、取っ掛かりを見つけたいところです。 /proc の下を見てみると、 /proc/version や /proc/cpuinfo といったファイルが見えます。このあたりをキーワードにして探すのが良さそうです。
# 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 という定義があります。
# 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 の下にあるファイルと同じ名前の定義がありました。
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 のハンドラとなる関数を指定すれば良さそうです。
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 に渡されるようです。
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で定数化されています。
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 ノードを生やしたりすることはできなさそうです。
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)を見ると、 /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(Wikipedia)
- mount_procfs(8)