La syscall Go è un brillante sostituto dell'I/O di basso livello
Summary
Apprenderemo le chiamate di sistema dirette in Go. Dato che Go offre rigorosi errori del compilatore e un rigido GC, è molto meglio sostituire le chiamate di basso livello in Pure Go. Fortunatamente, la maggior parte delle chiamate di funzione C sono completamente re-implementate in Go, in modo valido e contemporaneo. Analizziamole.
System Call
Una chiamata di sistema (System call) è una richiesta diretta al sistema operativo. Poiché il sistema è solitamente scritto in uno stile rigido e obsoleto, dato che è in esecuzione direttamente sull'hardware, dobbiamo considerare che la sua chiamata deve fornire una forma di richiesta rigorosa e corretta. Quindi, anche se non abbiamo bisogno di alcune variabili, dobbiamo comunque specificare la dimensione indipendentemente dall'uso. Verifichiamo con un esempio completamente funzionante.
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}
Questo esempio include tutte le variabili e stampa informazioni estese sullo stato attuale del sistema.
Possiamo paragonare questo codice a un armadietto e una chiave.syscall.SYS_SYSINFO è una chiave che sblocca un armadietto che si trova all'interno di un kernel.
Pertanto, utilizzare la chiave corretta per un armadietto è importante.
Cosa accadrebbe se usassimo syscall.SYS_GETPID per questa chiamata?
Questa è una chiave per un armadietto che contiene l'ID del processo (Process ID).
Questo tenterebbe di ottenere un PID da uno spazio destinato alle informazioni di sistema.
Di conseguenza, nessuna delle informazioni può essere letta correttamente; la chiamata deve essere restituita come stato fallito.
Ora, dobbiamo sapere quali elementi sono contenuti e come sono ordinati. Nel primo slot di un armadietto, abbiamo Uptime, con una dimensione di 2^64. Se tentiamo di leggerlo con 2^32, la sequenza di bit non viene letta completamente. Non possiamo utilizzare questi tipi di binari parziali a meno che non si vogliano scrivere trucchi di basso livello.
Dopo aver letto 64 bit di dati binari, siamo finalmente sul secondo slot. Può essere letto accuratamente solo dopo aver letto il precedente intero di dimensione a 64 bit.
Ripetendo questi flussi logici e rigorosi per ottenere informazioni appropriate da un sistema, possiamo gestire correttamente i dati letti.
How to skip 'variable names'
Anche se non possiamo "saltare" le variabili stesse, è importante distinguere le variabili usate da quelle scartate. Se l'uso del programma è sufficientemente chiaro, è meglio usare variabili senza nome come segnaposto piuttosto che etichettare ogni valore anche se non verrà mai utilizzato. Verifichiamolo con un esempio, il "Free Memory Checker".
Example - Free Memory Checker
Quando si controlla la Memoria Libera/Swap, non abbiamo bisogno di altre informazioni che indichino risorse diverse. Per ottenere una migliore visibilità, è possibile creare variabili anonime per contenere spazi specifici.
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 _ (quelle anonime e non usate sono marcate come _)
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, non ci sono etichette/marcatori leggibili nel codice.
Conclusion
- L'utilizzo di
syscalleunsafedi Go è comunque più sicuro di C/CGo - Se si sta scrivendo un progetto vasto che può essere espanso facilmente:
- Non creare variabili anonime; assegna nomi a ciascun membro.
- Se si sta scrivendo un progetto con un utilizzo limitato:
- È possibile utilizzare variabili anonime per contenere spazi che in realtà non vengono utilizzati.
- Il
syscalldi Go è potente e moderno per gestire chiamate di basso livello