FreeBSD でプログラムからシステムやプロセスの情報を取得する

# Linux の場合

Linux の場合 procfs があるので、/proc 以下のファイルを開いて読み込んで解釈すればよい。例えば

```C
#include <stdio.h>
#include <string.h>

size_t read_process_size(){
    const size_t SIZE = 2048;
    size_t virtual_size = 0;
    size_t nbytes = 0;
    int read_size = 0;
    char buf[SIZE];

    FILE* fp = fopen("/proc/self/status", "rb");
    nbytes = fread(buf, 1, SIZE-1, fp);
    buf[SIZE] = '\0';
    char *memp = strstr(buf, "VmSize:");
    read_size = sscanf(memp, "%*s %u", &virtual_size);
    fclose(fp);
    return(virtual_size);
}
```

という感じに、読み取った文字列を処理することになる。
なんでもファイルシステムにしてしまおうという思想のもとに、プログラムとしては結構簡単なものになっている。サンプルなのでエラー処理が一切ないのは赦してほしい。

# FreeBSD では

## 同じことはできる

FreeBSD にも [procfs](https://www.freebsd.org/cgi/man.cgi?query=procfs&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) はあるので同様のことはできる。
いや、出力フォーマットも Linux と一緒にしてくれないと、同じコードが使い回せないから困るんだけどという場合は、[linprocfs](https://www.freebsd.org/cgi/man.cgi?query=linprocfs&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) というカーネルモジュールがあるのでそちらを使えば良い。実際、Linuxエミュレーションで Linux のプログラムを動かす時にこちらが必要になることがある。

## procfs がいつも使えるとは限らない

しかし、FreeBSD の場合 procfs はオプショナルなもので、どのシステムでも /proc をマウントしてくれているとは限らない。FreeBSD なら動くというプログラムでは、/proc の存在に頼らないようにしなければならない。
ではどうすればよいかというと、答えは /proc がマウントされていなくてもシステムやプロセスの情報を取得してくれる、ベースシステムの ps コマンドや top コマンドのソースを見ればよいのだが、それをちょっと解説してみよう。

## システム情報 kvm および sysctl

FreeBSDで ps コマンドや top コマンドがシステムやプロセスの情報を取得するのに使っている、公式のインターフェースは [kvm: kernel memory interface](https://www.freebsd.org/cgi/man.cgi?query=kvm&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) である（Linux の [KVM](https://www.redhat.com/ja/topics/virtualization/what-is-KVM) とは違うので注意）。これを使う場合は、libkvm をリンクする必要がある。
場合によっては、それと sysctl コマンドで得られる情報を組み合わせる必要があり、こちらは [sysctl関数](https://www.freebsd.org/cgi/man.cgi?query=sysctl&apropos=0&sektion=3&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html)で取得できる。こちらは libc に含まれる。

例として、システム情報のうち swap のサイズ、プロセス情報のうち仮想サイズを取得するプログラムは以下のようになる。

### sysctl

後述する kvm で取得できるメモリの量はメモリページの数で示されるので、kB に変換するためには、1ページが何kBか知る必要があり、それは sysctl で取得する。

```C
#include <sys/sysctl.h>

int mib[2];
int pagesize = 0;
size_t len;

mib[0] = CTL_HW;
mib[1] = HW_PAGESIZE;
len = sizeof(pagesize);
sysctl(mib, 2, &pagesize, &len, NULL, 0);
```

これはコマンドでは ```sysctl hw.pagesize``` に相当する。
mib に指定可能な定数（つまりどのような値を取得可能か）と値を取得する変数の型について、代表的なものは [sys/sysctl.h](https://cgit.freebsd.org/src/tree/sys/sys/sysctl.h) に定義されている。また、sysctl 関数の[manページ](https://www.freebsd.org/cgi/man.cgi?query=sysctl&apropos=0&sektion=3&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html)にも載っている。
今回の場合は、pagesize を取得するために指定する2番目の定数は
```C
/*
 * CTL_HW identifiers
 */
#define HW_PAGESIZE      7              /* int: software page size */
```
となっているので、1番目の定数は ```CTL_HW``` で、値を取得する変数の型は int であることが分かる。
なお、5番目と6番目の引数は、値を変更する際に指定するものなので、値を取得する場合はそれぞれ ```NULL``` と ```0``` を指定しておく。

残念ながら sysctl 変数を網羅したドキュメントはないため、ここに載っていない sysctl 変数を取得したい場合、kernel のソースコードから sysctl 変数を公開している箇所を探す必要がある。hw.pagesize は [sys/kern/kern_mib.c](https://cgit.freebsd.org/src/tree/sys/kern/kern_mib.c) で次のように定義されており、型が int であることが分かる。
```C
SYSCTL_INT(_hw, HW_PAGESIZE, pagesize, CTLFLAG_RD|CTLFLAG_CAPRD,
    SYSCTL_NULL_INT_PTR, PAGE_SIZE, "System memory page size");
```

sys/sysctl.h には sysctl コマンドと同じ形式で取得できる sysctlbyname 関数も定義されており、次の形でも取得できるが、変数の型はいずれにせよ調べる必要がある。
```C
sysctlbyname("hw.pagesize", &pagesize, &len, NULL, 0);
```

### kvm

kvm でのデータ取得は、おおよそ ```kvm_openfiles``` 関数で開き、 ```kvm_getswapinfo``` や ```kvm_getprocs``` などのデータを読み込む関数で読み込み、```kvm_close``` で閉じるという、ファイルの読み込みと似た形式になっている。

```C
#include <stdio.h>
#include <kvm.h>
#include <fcntl.h>
#include <paths.h>
#include <limits.h>

kvm_t *kd;
char errbuf[_POSIX2_LINE_MAX];
struct kvm_swap ks;

kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf);
kvm_getswapinfo(kd, &ks, 1, 0);
printf("swap used: %lu pages.", ks.ksw_used);
kvm_close(kd);
```

```kvm_getswapinfo``` でデータを取得する ```kvm_swap``` 構造体は [kvm.h](https://cgit.freebsd.org/src/tree/lib/libkvm/kvm.h) で定義されているが、 ```kvm_getprocs``` でデータを取得する ```kinfo_proc``` は [sys/user.h](https://cgit.freebsd.org/src/tree/sys/sys/user.h) で定義されている。

```C
#include <kvm.h>
#include <unistd.h>
#include <sys/user.h>

struct kinfo_proc *kp;
int nentries = -1;

kp = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &nentries);
printf("process virtual size: %lu bytes", kp->ki_size);
```

### サンプルプログラム

以上をまとめてエラー処理もきちんとつけるとこんな感じになる。ビルド時に ```-lkvm``` をお忘れなく。

```C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <kvm.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <unistd.h>

int main()
{
    int mib[2];
    int pagesize = 0;
    size_t len;

    kvm_t *kd;
    const char *nlistf, *memf;
    char errbuf[_POSIX2_LINE_MAX];
    struct kvm_swap ks;
    struct kinfo_proc *kp;
    int nentries = -1;

    mib[0] = CTL_HW;
    mib[1] = HW_PAGESIZE;
    len = sizeof(pagesize);
    if (sysctl(mib, 2, &pagesize, &len, NULL, 0) == -1) {
        fprintf(stderr, "sysctl failed.\n");
        return(1);
    }
    const size_t pagekb = pagesize / 1024;

    nlistf = NULL;
    memf = _PATH_DEVNULL;
    kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
    if (kd == NULL) {
        fprintf(stderr, "%s\n", errbuf);
        return(1);
    }
    if (kvm_getswapinfo(kd, &ks, 1, 0) == -1) {
        fprintf(stderr, "%s\n", kvm_geterr(kd));
    }
    printf("swap used: %lu kb.\n", pagekb * ks.ksw_used);
    printf("swap total: %lu kb.\n", pagekb * ks.ksw_total);

    kp = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &nentries);
    if (kp == NULL || nentries < 0) {
        fprintf(stderr, "%s\n", kvm_geterr(kd));
    }
    if (nentries > 0) {
        printf("process virtual size: %lu kb.\n", kp->ki_size / 1024);
    }

    if (0 != kvm_close(kd)) {
        fprintf(stderr, "%s\n", strerror(errno));
    }

    exit(0);
}
```

# 詳しい情報

使い方について上記で足りない場合は、ひとまず前掲の /bin/ps の[ソースコード](https://cgit.freebsd.org/src/tree/bin/ps/ps.c)を見るのがよいだろう。
その上で、sysctl については [sysctl(3)](https://www.freebsd.org/cgi/man.cgi?query=sysctl&apropos=0&sektion=3&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) で足りると思うが、kvm については [kvm(3)](https://www.freebsd.org/cgi/man.cgi?query=kvm&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html) を起点に manpage をいろいろ読む必要があるだろう。

# ほかの言語では

C/C++ では libkvm を使って上記のようにプログラムすることになろうが、ほかの言語ではクロスプラットフォームのライブラリがあることが多いので（例えば python では psutil）それを探して利用するのがよいだろう。