GoSuda

Goのsyscallは、低レベルI/Oの優れた代替手段であります。

By Yunjin Lee
views ...

Summary

我々はGoにおける直接的なシステムコールについて学習します。 Goは厳格なコンパイラエラーと強固なGCを提供しているため、低レベルの呼び出しをPure Goで置き換える方が遥かに優れています。幸いなことに、Cの関数呼び出しのほとんどは、Goにおいて優れて現代的な方法で完全に再実装されています。それを見てみましょう。

System Call

システムコールはオペレーティングシステムへの直接的な要求です。システムは通常、ハードウェア上で直接実行されているため、厳格で古風なスタイルで記述されているのが常であり、その呼び出しは厳密かつ正しい形式の要求を伝えなければならないことを考慮する必要があります。したがって、たとえいくつかの変数が必要でなくても、使用の有無にかかわらずサイズを埋める必要があります。完全に機能する例で確認してみましょう。

Full Example

 1package main
 2import (
 3	"fmt"
 4	"syscall"
 5	"unsafe"
 6)
 7
 8type sysinfo_t struct {
 9	Uptime    int64
10	Loads     [3]uint64
11	Totalram  uint64
12	Freeram   uint64
13	Sharedram uint64
14	Bufferram uint64
15	Totalswap uint64
16	Freeswap  uint64
17	Procs     uint16
18	Pad       uint16
19	_         [4]byte
20	Totalhigh uint64
21	Freehigh  uint64
22	MemUnit   uint32
23	_         [4]byte
24}
25
26func main() {
27	var info sysinfo_t
28	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
29	if errno != 0 {
30		fmt.Println("sysinfo syscall failed:", errno)
31		return
32	}
33
34	scale := float64(1 << 16)
35	fmt.Printf("Uptime: %d seconds\n", info.Uptime)
36	fmt.Printf("Load Average: %.2f %.2f %.2f\n",
37		float64(info.Loads[0])/scale,
38		float64(info.Loads[1])/scale,
39		float64(info.Loads[2])/scale)
40	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
41		info.Totalram*uint64(info.MemUnit)/1024/1024,
42		info.Freeram*uint64(info.MemUnit)/1024/1024,
43		info.Bufferram*uint64(info.MemUnit)/1024/1024)
44	fmt.Printf("Swap: total=%d MB free=%d MB\n",
45		info.Totalswap*uint64(info.MemUnit)/1024/1024,
46		info.Freeswap*uint64(info.MemUnit)/1024/1024)
47	fmt.Printf("Processes: %d\n", info.Procs)
48}

この例には全ての変数が含まれており、現在のシステム情報の広範な情報が出力されます。我々はこのコードを、ロッカーと鍵として比較することができます。syscall.SYS_SYSINFOは、カーネル内部にあるロッカーの鍵です。したがって、ロッカーに正しい鍵を使用することが重要です。この呼び出しに対してsyscall.SYS_GETPIDを使用するとどうなるでしょうか? これはProcess IDを含むロッカーの鍵です。これはシステム情報のための空間からPIDを取得しようと試みます。結果として、どの情報も正しく読み取ることができず、この呼び出しは失敗状態として返されなければなりません。

さて、どの項目が含まれており、項目がどのように順序付けられているかを知る必要があります。ロッカーの最初のスロットには、Uptimeがあり、サイズは2^64です。これを2^32で読み取ろうとすると、ビットシーケンスは完全に読み取られません。低レベルのトリックを記述するのでない限り、これらの種類の部分的なバイナリを使用することはできません。

64ビットのバイナリデータを読み取った後、ようやく2番目のスロットに到達します。これは、以前の64ビットサイズの整数を読み取った場合にのみ正確に読み取ることができます。

システムから適切な情報を取得するために、これらの厳密で論理的なフローを繰り返すことで、読み取られたデータを適切に処理できます。

How to skip 'variable names'

変数そのものを「スキップ」することはできませんが、使用される変数と破棄される変数を区別することが重要です。プログラムの使用目的が十分に明確な場合、たとえそれらが永久に使用されないとしても、各値にラベルを付けるよりも、名無しの変数をプレースホルダーとして使用する方が優れています。「Free Memory Checker」という例でこれを確認してみましょう。

Example - Free Memory Checker

Free Memory/Swapsを確認する場合、異なるリソースを示す他の情報は必要ありません。より良い可視性を実現するために、特定のスペースを保持するために匿名変数を作成することができます。

 1package main
 2
 3import (
 4	"fmt"
 5	"syscall"
 6	"unsafe"
 7)
 8
 9type sysinfo_t struct {
10	_          int64
11	_         [3]uint64
12	Totalram  uint64
13	Freeram   uint64
14	Sharedram uint64
15	Bufferram uint64
16	Totalswap uint64
17	Freeswap  uint64
18	_         uint16  // anonymous, and unused ones are marked as _
19	_         uint16  
20	_         [4]byte 
21	_         uint64  
22	_         uint64  
23	MemUnit   uint32
24	_         [4]byte
25}
26
27func main() {
28	var info sysinfo_t
29	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
30	if errno != 0 {
31		fmt.Println("sysinfo syscall failed:", errno)
32		return
33	}
34
35	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
36		info.Totalram*uint64(info.MemUnit)/1024/1024,
37		info.Freeram*uint64(info.MemUnit)/1024/1024,
38		info.Bufferram*uint64(info.MemUnit)/1024/1024)
39	fmt.Printf("Swap: total=%d MB free=%d MB\n",
40		info.Totalswap*uint64(info.MemUnit)/1024/1024,
41		info.Freeswap*uint64(info.MemUnit)/1024/1024)
42}

結果として、変数はラベルなしで読み取られます。匿名値は実際に構造体に格納されますが、コード上にはラベル/読み取り可能なマークはありません。

Conclusion

  • Goのsyscallunsafeを使用することは、C/CGoよりも依然として安全です。
  • 容易に拡張できる巨大なプロジェクトを記述している場合:
    • 匿名変数を作成せず、メンバーごとに名前を付けます。
  • 使用が限定されたプロジェクトを記述している場合:
    • 実際に使用されないスペースを保持するために、匿名変数を使用することができます。
  • Goのsyscallは、低レベルの呼び出しを処理するための強力かつモダンな手段です。

Read Further

syscall unsafe x/sys/unix