NetBSDカーネル内のstruct procからプロセスの親子関係をグラフ化してみる

[NetBSD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/netbsd) 25日目の記事です。今日はカーネル内のプロセス情報からプロセスの親子関係のグラフを作成してみようと思います。

## struct procに含まれているプロセス情報

NetBSDでのプロセスは[/usr/src/sys/sys/proc.h](https://github.com/NetBSD/src/blob/trunk/sys/sys/proc.h)で定義されている `struct proc` 構造体で管理されています。この構造体を見ると、`struct proc->p_pptr` を辿ることで親プロセスの情報を参照できるようです。そして `char p_comm` には実行しているコマンド名(=プロセス名)が入っているため、この2つの情報を参照することで、プロセスの親子関係をグラフ化できそうです。

```c
230 struct proc {
...
265     struct proc     *p_pptr;    /* l: Pointer to parent process. */
...
335     char        p_comm[MAXCOMLEN+1];
```

## さっそくカーネルモジュールを作成してみる

動作としては、ユーザ空間から[read(2)](https://man.netbsd.org/read.2)した際に[Graphviz](https://graphviz.org/)のグラフとしてプロセスの親子関係を返すという形にしてみます。

サンプルコードは以下になります。

https://gist.github.com/furandon-pig/072c4c29744c665d54e99df87523aa51

ユーザ空間側の[read(2)](https://man.netbsd.org/read.2)に対応するカーネルモジュール側の関数は以下になります。

```c
 93 int
 94 process_graph_sample_read(dev_t self __unused, struct uio *uio, int flags __unused)
 95 {
 96     int e;
 97     char result[4096] = { '\0' };
 98     char tmp[128] = { '\0' };
 99     char *r = result;
100
101     r = strcat(r, "digraph G {\n");
102
103     struct proc *p;
104     PROCLIST_FOREACH(p, &allproc) {
105         if (p->p_pid <= 1 || (p->p_flag & PK_SYSTEM) != 0) {
106             continue;
107         }
108         snprintf(tmp, 128, "\"%s\" -> \"%s\"", p->p_comm, p->p_pptr->p_comm);
109         r = strcat(r, tmp);
110         r = strcat(r, "\n");
111     }
112     r = strcat(r, "}\n");
113     r = strcat(r, "\0");
114
115     // 変数hostnameの値をユーザ空間にコピーする。
116     if ((e = uiomove(result, strlen(result), uio)))
117         return e;
118
119     return 0;
120 }
```

ポイントになる箇所…といっても特に何かあるワケではなく、以下の個所で親と子のプロセス名を[dot形式](https://graphviz.org/doc/info/lang.html)のグラフ表記にしています。

```c
108         snprintf(tmp, 128, "\"%s\" -> \"%s\"", p->p_comm, p->p_pptr->p_comm);
```

## ユーザ空間側のコード

ユーザ空間からは単に[read(2)](https://man.netbsd.org/read.2)するだけです。

https://gist.github.com/furandon-pig/2430557d9a9d4cceb7e7fa9a97387488

## 実際に動かしてみる

```sh
# gcc -Wall -g -o process_graph_sample process_graph_sample.c
# ./process_graph_sample | tee output.dot
digraph G {
"sample" -> "bash"
"bash" -> "sshd"
"sshd" -> "sshd"
"getty" -> "init"
"getty" -> "init"
"getty" -> "init"
"getty" -> "init"
"cron" -> "init"
"inetd" -> "init"
"sshd" -> "init"
"powerd" -> "init"
"rpc.lockd" -> "init"
"rpc.statd" -> "init"
"rpcbind" -> "init"
"syslogd" -> "init"
"dhcpcd" -> "init"
}
```

PNG画像に変換します。

```sh
# dot -Tpng output.dot > output.png
```

![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/066f541d-1125-08d6-2ef7-52980a1045af.png)

プロセスの親子関係グラフが得られました。 `inetd` や `dhcpcd` 、`sshd` といったデーモンは `init` プロセスから生まれており、ユーザが利用するシェルは `sshd` から生まれていることが見て取れます。

## まとめ

カーネル内のプロセス情報からプロセスの親子関係をグラフ化してみました。カーネルモジュール経由でカーネルの内部情報にアクセスする方法が把握できたので、今回のようなサンプル的なプログラムから一歩踏み込んだ実用的なツールやユーティリティ作成に応用できればと思います。

## さいごに

今年も何とかNetBSD Advent Calendar 2023を完走することができました。これもひとえに皆様のおかげです。

[@ryoon](https://qiita.com/ryoon)様、[@oshimyja](https://qiita.com/oshimyja)様、[@ebijun](https://qiita.com/ebijun)様、[@yamori813](https://qiita.com/yamori813)様(投稿日順です)ありがとうございました。

今年は[AsiaBSDCon 2023](https://2023.asiabsdcon.org/)も開催され、次期NetBSD-10に搭載される機能の紹介もあったことから、NetBSD-currentで来るべき新機能を調べたりしていました。[wg(4)](https://man.netbsd.org/wg.4)(WireGuard)を試してみたりしたかったのですが、ちょっと今回は調べる時間が足りませんでした。

とはいえ、今後もNetBSD-10のリリースまでは様々な機能の追加やバグ修正が行われるかと思いますので、来年はこまめにNetBSD情報を追いかけたり試したり、記事にしたりしてみようと思います。