カーネルのパラメータを引いたり設定したりする時に便利なのが sysctl
コマンドです。
$ sysctl kern.ostype kern.ostype: Darwin
このコマンドのシステムコールをGo言語から叩いて、OSの種類を引いてみましょう。
func main() { ret, _ := syscall.Sysctl("kern.ostype") fmt.Printf("%s\n", ret) }
Darwin
問題ないですね。 数字を返すものを叩いてみましょう。
$ sysctl machdep.cpu.feature_bits
machdep.cpu.feature_bits: 9221959987971750911
func main() { ret, _ := syscall.Sysctl("machdep.cpu.feature_bits") val := *(*uint64)(unsafe.Pointer(&[]byte(ret)[0])) fmt.Printf("%d\n", val) }
出力結果
9221959987971750911
unsafeはパッケージ使いたくないだって? CPUアーキテクチャのエンディアン固定になっちゃうけどなぁ。
val := binary.LittleEndian.Uint64([]byte(ret))
9221959987971750911
次は extfeature_bits
を見たい?
$ sysctl machdep.cpu.extfeature_bits machdep.cpu.extfeature_bits: 1241984796928
func main() { ret, _ := syscall.Sysctl("machdep.cpu.extfeature_bits") val := binary.LittleEndian.Uint64([]byte(ret)) fmt.Printf("%d\n", val) }
実行してみます
panic: runtime error: index out of range goroutine 1 [running]: encoding/binary.binary.littleEndian.Uint64(...) /usr/local/Cellar/go/1.9.2/libexec/src/encoding/binary/binary.go:76 main.main() /private/tmp/main.go:16 +0x1d7 exit status 2
!!!> index out of range <!!!
なにが起きたのか。
syscall.Sysctl
の返り値 (string
) の長さを見てみると、すぐにわかります。
ret, _ := syscall.Sysctl("machdep.cpu.feature_bits") fmt.Printf("%d\n", len(ret)) ret, _ = syscall.Sysctl("machdep.cpu.extfeature_bits") fmt.Printf("%d\n", len(ret))
8 7
// Throw away terminating NUL. if n > 0 && buf[n-1] == '\x00' { n-- } return string(buf[0:n]), nil
な、なんということを…
つまりstructでも…?
ret, _ := syscall.Sysctl("kern.boottime") fmt.Printf("%d\n", len(ret))
15
お、おう… わかった… わかったよ…
sysctl.Sysctl
は最後のNULを落とす。- 文字列を返すような場合はよい挙動だが、数字や構造体の場合は注意が必要。64bit整数を返す場合、その値に依存して8byteだったり7byteだったりする。
unsafe.Pointer
で数字や構造体にキャストする分には問題ないように思われる (ほんまやろか?)。+ "\x00"
してから処理したほうがいいかもしれない。
困っている人もいる (例) が、指摘されているように sysctl
パッケージは変更を受けつけていないので、この挙動が変わることや別の関数が追加されることはなさそう。
そもそも string
で返すのなんかおかしくない? []byte
で欲しいよね…
それあります!
golang.org/x/sys
パッケージの unix.SysctlRaw
を使いましょう。
https://github.com/golang/sys/blob/53aa286056ef226755cd898109dbcdaba8ac0b81/unix/syscall_bsd.go#L524
func main() { ret, _ := unix.SysctlRaw("vm.loadavg") fmt.Printf("%d\n", len(ret)) ret, _ = unix.SysctlRaw("kern.boottime") fmt.Printf("%d\n", len(ret)) ret, _ = unix.SysctlRaw("machdep.cpu.extfeature_bits") fmt.Printf("%d\n", len(ret)) }
24 16 8
これで安心して眠れそうですね。
sysctl.Sysctl
は最後のNULを落とします。文字列として欲しい時はこれで良いが、バイト列として欲しい時は unix.SysctlRaw
を使いましょう。