ロードされているカーネルモジュールの依存関係をグラフ化してみる

[NetBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/netbsd) 10日目の記事です。今日はロードされているカーネルモジュールの依存関係を可視化する話をしようと思います。

## 背景

NetBSDに限らず、現代のOSでは「カーネルモジュール」という形でOSを稼働させたまま特定の機能を追加・削除できます。例えばNetBSDでは、[modstat(8)](https://man.netbsd.org/NetBSD-9.2/i386/modstat.8)コマンドでロードされているカーネルモジュールを確認できます。

```sh
$ modstat
NAME                       CLASS    SOURCE   FLAG  REFS    SIZE REQUIRES
aac                        driver   builtin  -        1       - pci
accf_dataready             misc     builtin  -        0       - -
accf_httpready             misc     builtin  -        0       - -
acpiacad                   driver   builtin  -        0       - sysmon_envsys,sysmon_power
acpibat                    driver   builtin  -        0       - sysmon_envsys
acpibut                    driver   builtin  -        0       - sysmon_power
...
```

NetBSDのデフォルトインストール状態だと、約260個ほどのカーネルモジュールがロードされているようです。中にはサウンド系のデバイスなど、私の環境では使用しない(※)モジュールもあるようです。
(※VirtualBox上でサウンドデバイスを無効化しているので…)

```sh
$ modstat | wc -l
     265
```

というワケで、不要なカーネルモジュールを除去したいのですが、モジュールには依存関係があるようで、ちゃんと順番を考えないとエラーになってしまいます。

```sh
$ sudo modunload compat_linux
modunload: compat_linux: Device busy
```

`modstat` コマンドの `REQUIRES` フィールドには、該当のカーネルモジュールを必要としている(＝依存関係のある)モジュールが記載されているようです。この情報をパースすれば、カーネルモジュールの依存図が作成できそうです。

```sh
$ modstat | egrep 'NAME|compat_linux'
NAME                       CLASS    SOURCE   FLAG  REFS    SIZE REQUIRES
compat_linux               exec     builtin  -        1       - compat_ossaudio,sysv_ipc,compat_util,compat_50,compat_43,exec_elf64,exec_aout
compat_linux32             exec     builtin  -        0       - compat_linux,sysv_ipc,compat_sysv_50,compat_netbsd32_50,compat_netbsd32_43,exec_elf32,compat_netbsd32,compat_netbsd32_sysvipc
```

## カーネルモジュールの依存関係を可視化する

`modstat` をパースする簡単なスクリプトを作成してみます。

```ruby
#!/usr/bin/env ruby

kmod_list = {}
IO.popen('modstat', 'r').readlines.each_with_index do |record, index|
  next if index == 0
  name, _, _, _, _, _, requires = record.chomp.gsub(/ +/, ' ').split(' ')
  requires.split(',').each do |kmod|
    kmod_list[name] = kmod
  end
end

puts 'digraph "mygraph" {'
kmod_list.each_pair do |kmod, req|
  next if req == '-'
  puts "  \"#{kmod}\" -> \"#{req}\";"
end
puts '}'
```

`modstat` コマンドにおける、NAMEとREQUIRESの部分をグラフ化するだけです。

```sh
$ ./sample.rb > tmp.dot
digraph "mygraph" {
  "aac" -> "pci";
  "acpiacad" -> "sysmon_power";
  "acpibat" -> "sysmon_envsys";
...
  "vmbus" -> "hyperv";
  "vmt" -> "sysmon_taskq";
  "vnd" -> "zlib";
  "wmidell" -> "sysmon_power";
  "wmieeepc" -> "sysmon_power";
  "wmihp" -> "sysmon_envsys";
  "wmimsi" -> "acpiwmi";
  "xc3028" -> "i2cexec";
  "xc5k" -> "i2cexec";
}
```

[Graphviz](https://graphviz.org/)でグラフ化します。

```sh
$ dot -Tsvg tmp.dot > output.svg
```

こんな感じで可視化できました。こうやって見ると、いくつかのモジュール群になっているのがわかりますね。

![nbsd1210_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/c1a518ec-ce5f-8302-0203-050ea76b8025.png)

画像だと表示がつぶれてしまうので、SVG形式にしたものをGistに用意しました。

 * [netbsd_kernel_module_dependency.svg](https://gist.github.com/furandon-pig/8877783c150bdb139e3b0af9d8816dcf)

## まとめ

`modstat` の出力から、カーネルモジュールの依存関係を可視化してみました。モジュール名から明らかに自分では使用していないものが判別できたりするので、不要なモジュールを除去することで、よりスリムな(？)カーネルにできそうです。