Go syscall ist ein brillanter Ersatz für Low-Level I/O
Zusammenfassung
Wir werden uns mit direkten Systemaufrufen in Go befassen. Da Go strikte Compilerfehler und eine rigide GC bietet, ist es wesentlich vorteilhafter, Low-Level-Aufrufe in reinem Go zu ersetzen. Glücklicherweise sind die meisten C-Funktionsaufrufe in Go vollständig und auf zeitgemäße Weise reimplementiert. Betrachten wir dies genauer.
Systemaufruf
Ein Systemaufruf 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 bestimmte Variablen nicht benötigen, müssen wir die Größe unabhängig von der Verwendung angeben. Überprüfen wir dies anhand eines vollständig funktionierenden Beispiels.
Vollständiges Beispiel
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 enthält alle Variablen und gibt umfangreiche Informationen über den aktuellen Systemzustand aus.
Wir können diesen Code als Schließfach und Schlüssel vergleichen.syscall.SYS_SYSINFO ist ein Schlüssel, der ein Schließfach im Kernel öffnet.
Daher ist die Verwendung des richtigen Schlüssels für ein Schließfach wichtig.
Was passiert, 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 Bereich für Systeminformationen abzurufen.
Infolgedessen können 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 sie geordnet sind. Im ersten Bereich eines Schließfachs haben wir die Uptime mit einer Größe von 2^64. Wenn wir dies mit 2^32 lesen wollen, 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 befinden wir uns schließlich im zweiten Bereich. Dieser kann nur dann genau gelesen werden, wenn wir die vorherige 64-Bit-Ganzzahl gelesen haben.
Durch die Wiederholung dieser strikten und logischen Abläufe zur Gewinnung korrekter Informationen aus einem System können wir die gelesenen Daten ordnungsgemäß verarbeiten.
Wie man 'Variablennamen' überspringt
Obwohl wir Variablen selbst nicht 'überspringen' können, ist es wichtig, verwendete Variablen und verworfene Variablen zu unterscheiden. Wenn die Verwendung des Programms klar genug ist, ist es besser, namenlose Variablen als Platzhalter zu verwenden, anstatt jeden Wert zu kennzeichnen, selbst wenn er niemals verwendet wird. Überprüfen wir dies anhand eines Beispiels, dem "Free Memory Checker".
Beispiel – Freier Speicher-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 Bereiche zu belegen.
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 // anonyme und ungenutzte werden als _ gekennzeichnet
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 im Code keine Bezeichnungen/lesbaren Markierungen.
Fazit
- Die Verwendung von Go's
syscallundunsafeist immer noch sicherer als C/CGo. - Wenn Sie ein umfangreiches Projekt schreiben, das leicht erweitert werden kann:
- Erstellen Sie keine anonymen Variablen; benennen Sie jedes Element explizit.
- Wenn Sie ein Projekt mit begrenzter Nutzung schreiben:
- Sie können anonyme Variablen verwenden, um ungenutzte Bereiche zu belegen.
- Go's
syscallist leistungsfähig und modern für die Handhabung von Low-Level-Aufrufen.