Chiamate di sistema Linux dirette in Go puro con syscall.Syscall e unsafe.Pointer

Riepilogo
Impareremo a conoscere le chiamate di sistema dirette in Go. Poiché Go offre errori di compilazione rigorosi e un garbage collector rigido, è molto preferibile sostituire le chiamate di basso livello con Pure Go. Fortunatamente, la maggior parte delle chiamate di funzione del C è stata completamente reimplementata in Go, in modo valido e contemporaneo. Diamo un'occhiata.
Chiamata di sistema
Una chiamata di sistema è una richiesta diretta al sistema operativo. Poiché il sistema è solitamente scritto in uno stile rigido e antiquato, dato che viene eseguito direttamente sull'hardware, dobbiamo considerare che la sua chiamata debba fornire una forma della richiesta rigorosa e corretta. Quindi, anche se non ci servono alcune variabili, dobbiamo comunque riempire la dimensione indipendentemente dall'uso. Vediamo un esempio completamente funzionante.
Esempio completo
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}
Questo esempio include tutte le variabili e stampa informazioni dettagliate sulle informazioni di sistema correnti.
Possiamo paragonare questo codice a una serratura e a una chiave.syscall.SYS_SYSINFO è una chiave che apre una serratura situata all'interno del kernel.
Pertanto, è importante usare la chiave corretta per la serratura.
Cosa succede se usiamo syscall.SYS_GETPID per questa chiamata?
Questa è la chiave per una serratura che contiene l'ID del processo.
Ciò tenterà di ottenere un PID da uno spazio dedicato alle informazioni di sistema.
Di conseguenza, nessuna delle informazioni può essere letta correttamente; la chiamata deve restituire uno stato di errore.
Ora, dobbiamo sapere quali elementi sono contenuti e come sono ordinati. Nella prima posizione della serratura troviamo Uptime, di dimensione 2^64. Se proviamo a leggerlo con 2^32, la sequenza di bit non viene letta completamente. Non possiamo utilizzare questi tipi di sequenze parziali di bit, a meno che non si voglia ricorrere a trucchi di basso livello.
Dopo aver letto 64 bit di dati binari, finalmente ci troviamo nella seconda posizione. Essa può essere letta correttamente solo dopo aver letto il precedente intero a 64 bit.
Ripetendo questi flussi rigorosi e logici, è possibile ottenere le informazioni corrette dal sistema e gestire correttamente i dati letti.
Come saltare i "nomi delle variabili"
Anche se non possiamo "saltare" le variabili stesse, è importante distinguere le variabili utilizzate da quelle scartate. Se l'uso del programma è sufficientemente chiaro, è preferibile usare variabili senza nome come segnaposto piuttosto che etichettare ogni valore, anche se non verranno utilizzati per sempre. Vediamo questo aspetto con un esempio, "Free Memory Checker".
Esempio - Free Memory Checker
Quando si controllano la memoria libera e lo swap, non abbiamo bisogno delle altre informazioni che indicano risorse diverse. Per ottenere una migliore leggibilità, è possibile creare variabili anonime per contenere specifici spazi.
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 // anonimi, e quelli non utilizzati sono contrassegnati con _
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}
Di conseguenza, le variabili vengono lette senza etichette. Sebbene i valori anonimi siano effettivamente memorizzati in una struttura, nel codice non ci sono etichette o segni leggibili.
Conclusione
- Usare
syscalleunsafedi Go è ancora più sicuro rispetto a C/CGo - Se si sta scrivendo un progetto ampio che può essere facilmente esteso:
- Non utilizzare variabili anonime; assegnare un nome a ciascun membro.
- Se si sta scrivendo un progetto con un utilizzo limitato:
- È possibile usare variabili anonime per riservare gli spazi effettivamente inutilizzati.
syscalldi Go è potente e moderno per gestire le chiamate di basso livello