GoSuda

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

By Yunjin Lee views ...

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 syscall e unsafe di 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.
  • syscall di Go è potente e moderno per gestire le chiamate di basso livello

Approfondimenti

syscall unsafe x/sys/unix