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)