GoSuda

Apelul de sistem Go (Go syscall) este o înlocuire strălucită a operațiunilor de I/O de nivel scăzut (low-level I/O)

By Yunjin Lee
views ...

Rezumat

Vom învăța despre apelul de sistem direct în Go. Deoarece Go oferă erori de compilare stricte și un GC (Garbage Collector) rigid, este mult mai bine să înlocuim apelurile de nivel jos în Pure Go. Din fericire, majoritatea apelurilor de funcții C sunt reimplementate integral în Go, într-o manieră bună și contemporană. Să aruncăm o privire asupra acestui aspect.

Apelul de Sistem

Apelul de sistem este o solicitare directă către sistemul de operare. Deoarece sistemul este de obicei scris într-un stil rigid, demodat, întrucât rulează direct pe hardware, trebuie să considerăm că apelul său trebuie să livreze o formă strictă și corectă a unei solicitări. Așadar, chiar dacă nu avem nevoie de anumite variabile, tot trebuie să completăm dimensiunea, indiferent de utilizare. Să verificăm cu un exemplu complet funcțional.

Exemplu Complet

 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}

Acest exemplu include toate variabilele și afișează informații extinse despre starea curentă a sistemului. Putem compara acest cod cu un lacăt și o cheie.syscall.SYS_SYSINFO este o cheie care deblochează un lacăt ce se află în interiorul unui kernel. Prin urmare, utilizarea cheii corecte pentru un lacăt este importantă. Ce se va întâmpla dacă folosim syscall.SYS_GETPID pentru acest apel? Aceasta este o cheie pentru un lacăt care conține ID-ul de Proces. Aceasta va încerca să obțină un PID din spațiul destinat informațiilor de sistem. Ca rezultat, nicio informație nu poate fi citită corect; apelul trebuie să returneze o stare de eșec.

Acum, trebuie să știm ce elemente sunt conținute și cum sunt ordonate elementele. În primul slot al unui lacăt, avem Uptime, cu o dimensiune de 2^64. Dacă încercăm să citim acest lucru cu 2^32, secvența de biți nu este citită complet. Nu putem utiliza astfel de binare parțiale decât dacă vom scrie trucuri de nivel jos.

După citirea a 64 de biți de date binare, ajungem în sfârșit la al doilea slot. Acesta poate fi citit cu precizie numai după ce am citit întregul anterior de 64 de biți.

Prin repetarea acestor fluxuri stricte și logice pentru a obține informații adecvate de la un sistem, putem gestiona corect datele citite.

Cum să omitem 'numele variabilelor'

Deși nu putem 'omite' variabilele în sine, este important să facem distincția între variabilele utilizate și cele eliminate. Dacă utilizarea programului este suficient de clară, este mai bine să folosim variabile fără nume ca substituenți (placeholders) decât să etichetăm fiecare valoare, chiar dacă acestea nu sunt folosite niciodată. Să verificăm acest lucru cu un exemplu, "Verificator de Memorie Liberă"

Exemplu - Verificator de Memorie Liberă

Când verificăm Memoria Liberă/Swap-urile, nu avem nevoie de alte informații care indică resurse diferite. Pentru a obține o vizibilitate mai bună, puteți crea variabile anonime pentru a reține spații specifice.

 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 _ (cele anonime și neutilizate sunt marcate ca _)
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}

În consecință, variabilele sunt citite fără etichete. Deși valorile anonime sunt de fapt stocate într-o structură, nu există etichete/marcaje lizibile în cod.

Concluzie

  • Utilizarea syscall și unsafe din Go este în continuare mai sigură decât C/CGo
  • Dacă scrieți un proiect vast care poate fi extins cu ușurință:
    • Nu creați variabile anonime; denumiți fiecare membru.
  • Dacă scrieți un proiect cu utilizare limitată:
    • Puteți utiliza variabile anonime pentru a reține spațiile care sunt de fapt neutilizate.
  • syscall-ul din Go este puternic și modern pentru a gestiona apelurile de nivel jos

Citiți Mai Departe

syscall unsafe x/sys/unix