🤖

🤖

:gijutsu_burogu:

システムコールとGo

この記事の内容

システムコールとは

システムコールとは、特権モードで OS の機能を呼ぶことです。 アプリケーションでは通常、ユーザーモードであり CPU の利用が制限されています。 そのため、システムコールによって、特権モードでのみ許可されている機能をユーザーモードのアプリケーションから利用できるようにしています。 メモリの割当やファイル入出力、通信などは特権モードでしか行えません。

システムコールがないとどうなる

計算を行う命令はユーザーモードでも可能です。

しかし

  • 特権モードでないとプロセス間通信ができないので、計算結果を画面に出力できない
  • 特権モードでないとファイル入出力ができないので、計算結果をファイル出力することができない
  • 特権モードでないと共有メモリを作成できないので、計算結果を他のプログラムから参照することができない
  • 特権モードでないと外部通信できないので、計算結果を外部ウェブサービスなどに送信できない

システムコールがなければなにもできません。

また、システムコールには抽象化の側面もあります。 OS が提供するシステムコールによって、プログラマはハードウェアを直接制御するプログラムを記述する必要がありません。 さらに、高水準言語によってハードウェアの種類の違いを意識する必要もなくなります。

また、POSIX(Portable Operating System Interface)は IEEE 規格であり、OS 間でシステムコールを呼び出すための共通のインターフェースを決めています。

Go とシステムコール

試しに、ハローワールドの実装を Goland で追いました。 VS Code のデバッガモードでステップインをすることで追えます。 以下より、標準出力するだけでも確かにシステムコールが呼ばれていることが分かります。

// go version go1.14.4 darwin/amd64
fmt.Println("Hello World!")

// ↓call

n, err = w.Write(p.buf) // print.go

// ↓call

n, e := f.write(b) // file.go

// ↓call

n, err = f.pfd.Write(b) // file_unix.go

// ↓call

n, err := syscall.Write(fd.Sysfd, p[nn:max]) // fd_unix.go

// ↓call

n, err = write(fd, p) // syscall_unix.go

// ↓call

r0, _, e1 := syscall(funcPC(libc_write_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(p))) // zsyscall_darwin_amd64.go

ここで libc を使われています。 C 言語のライブラリで printf とか fopen とかが入っています。 libc を介してシステムコールが呼ばれています。

$ otool -L main
main:
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)

コンパイルしたバイナリを調べると、/usr/lib/libSystem.B.dylibが使われています。(Mac) これがMacでのlibcのようです。

stackoverflow.com

DockerでLinux環境を作り同様に検証してみました。

$ ldd main
/lib/ld-musl-x86_64.so.1: main: Not a valid dynamic program

静的リンクなバイナリになりました。 MacLinuxで異なった結果になりました。 今回は、ハローワールドな簡単なバイナリで調べてみましたが複雑なコードだとまた違った結果がでるかもしれません。