Der Go `syscall` ist ein brillanter Ersatz für Low-Level I/O
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
syscallundunsafeist 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
syscallist leistungsstark und modern für die Handhabung von Low-Level-Aufrufen