GoSuda

La syscall Go è un brillante sostituto dell'I/O di basso livello

By Yunjin Lee
views ...

Summary

Apprenderemo le chiamate di sistema dirette su Go. Poiché Go offre errori di compilazione rigorosi e un GC rigido, è 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. Osserviamolo.

System Call

La chiamata di sistema (System call) è una richiesta diretta al sistema operativo. Poiché il sistema è solitamente scritto in uno stile rigido e obsoleto, dato che viene eseguito direttamente su un 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 sulle informazioni del sistema attuale. 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, è importante utilizzare la chiave corretta per un armadietto. Cosa succederà se usiamo syscall.SYS_GETPID per questa chiamata? Questa è una chiave per un armadietto che contiene l'ID di Processo (Process ID). Questo tenterà 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 usare questo tipo di binari parziali a meno che non si vogliano scrivere trucchi di basso livello.

Dopo aver letto 64 bit di dati binari, finalmente siamo nel secondo slot. Può essere letto accuratamente solo se abbiamo letto l'integer precedente di dimensione 64 bit.

Ripetendo questi flussi rigorosi e logici per ottenere un'informazione appropriata 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. Verifichiamo questo con un esempio, "Free Memory Checker" (Verificatore di Memoria Libera)

Example - Free Memory Checker

Quando verifichiamo la Memoria Libera/Swap, non abbiamo bisogno di altre informazioni che indicano 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 contrassegnate 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/segni leggibili nel codice.

Conclusion

  • L'uso di syscall e unsafe di Go è comunque più sicuro di C/CGo
  • Se si sta scrivendo un progetto enorme che può essere espanso facilmente:
    • Non creare variabili anonime; assegna nomi a ciascun membro.
  • Se si sta scrivendo un progetto con un uso limitato:
    • È possibile utilizzare variabili anonime per contenere spazi che in realtà non vengono utilizzati.
  • Il syscall di Go è potente e moderno per gestire chiamate di basso livello

Read Further

syscall unsafe x/sys/unix