GoSuda

Der Go `syscall` ist ein brillanter Ersatz für Low-Level I/O

By Yunjin Lee
views ...

Summary

Wir werden etwas über direkte Systemaufrufe in Go lernen. Da Go strenge Compiler-Fehler und einen rigiden GC bietet, ist es wesentlich besser, Low-Level-Aufrufe durch reines Go zu ersetzen. Glücklicherweise sind die meisten C-Funktionsaufrufe in Go vollständig, gut und zeitgemäß reimplementiert. Betrachten wir dies nun genauer.

System Call

Ein System Call ist eine direkte Anforderung an das Betriebssystem. Da das System üblicherweise in einem rigiden, altmodischen Stil geschrieben ist, da es direkt auf der Hardware läuft, müssen wir berücksichtigen, dass sein Aufruf eine strikte und korrekte Form einer Anforderung liefern muss. Selbst wenn wir also einige Variablen nicht benötigen, müssen wir die Größe ungeachtet der Verwendung ausfüllen. Prüfen wir dies anhand eines vollständig funktionierenden Beispiels.

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}

Dieses Beispiel beinhaltet alle Variablen und gibt umfangreiche Informationen zur aktuellen Systeminformation aus. Wir können diesen Code mit einem Schließfach und einem Schlüssel vergleichen.syscall.SYS_SYSINFO ist ein Schlüssel, der ein Schließfach im Kernel öffnet. Daher ist die Verwendung des korrekten Schlüssels für ein Schließfach wichtig. Was geschieht, wenn wir syscall.SYS_GETPID für diesen Aufruf verwenden? Dies ist ein Schlüssel für ein Schließfach, das die Prozess-ID enthält. Dies würde versuchen, eine PID aus einem Speicherbereich für Systeminformationen zu erhalten. Infolgedessen kann keine der Informationen korrekt gelesen werden; der Aufruf muss als fehlgeschlagen zurückgegeben werden.

Nun müssen wir wissen, welche Elemente enthalten sind und wie die Elemente geordnet sind. Im ersten Fach eines Schließfachs haben wir Uptime mit einer Größe von 2^64. Wenn wir versuchen, dies mit 2^32 zu lesen, wird die Bitsequenz nicht vollständig gelesen. Wir können diese Art von partiellen Binärdaten nicht verwenden, es sei denn, wir beabsichtigen, Low-Level-Tricks zu schreiben.

Nach dem Lesen von 64 Bit Binärdaten sind wir schließlich im zweiten Fach. Es kann nur dann genau gelesen werden, wenn wir die vorherige 64-Bit-Ganzzahl gelesen haben.

Indem wir diese strikten und logischen Abläufe wiederholen, um eine korrekte Information von einem System zu erhalten, können wir die gelesenen Daten ordnungsgemäß verarbeiten.

How to skip 'variable names'

Obwohl wir Variablen selbst nicht 'überspringen' können, ist es wichtig, verwendete Variablen und verworfene zu unterscheiden. Wenn die Verwendung des Programms klar genug ist, ist es besser, namenlose Variablen als Platzhalter zu verwenden, als jeden Wert zu benennen, selbst wenn er nie verwendet wird. Prüfen wir dies anhand eines Beispiels, dem "Free Memory Checker"

Example - Free Memory Checker

Beim Überprüfen von freiem Speicher/Swaps benötigen wir keine anderen Informationen, die unterschiedliche Ressourcen anzeigen. Um eine bessere Übersichtlichkeit zu erzielen, können Sie anonyme Variablen erstellen, um bestimmte Speicherbereiche zu halten.

 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}

Folglich werden Variablen ohne Bezeichnungen gelesen. Obwohl anonyme Werte tatsächlich in einer Struktur gespeichert werden, gibt es keine Bezeichnungen/lesbaren Markierungen im Code.

Conclusion

  • Die Verwendung von Go's syscall und unsafe ist immer noch sicherer als C/CGo
  • Wenn Sie ein großes Projekt schreiben, das leicht erweitert werden kann:
    • Verwenden Sie keine anonymen Variablen; geben Sie jedem Member einen Namen.
  • Wenn Sie ein Projekt mit begrenzter Nutzung schreiben:
    • Sie können anonyme Variablen verwenden, um Speicherbereiche zu halten, die tatsächlich ungenutzt sind.
  • Go's syscall ist leistungsstark und modern für die Handhabung von Low-Level-Aufrufen

Read Further

syscall unsafe x/sys/unix