GoSuda

Apeluri directe de sistem Linux în Go pur, cu syscall.Syscall și unsafe.Pointer

By Yunjin Lee views ...

Rezumat

Vom învăța despre apelurile de sistem directe în Go. Deoarece Go oferă erori stricte la compilare și un colector de gunoi (GC) riguros, este mult mai bine să înlocuim apelurile de nivel jos cu Pure Go. Din fericire, majoritatea apelurilor de funcții C sunt reimplementate integral în Go, într-un mod bun și contemporan. Să le analizăm.

Apel de sistem

Un apel de sistem este o cerere directă adresată sistemului de operare. Deoarece sistemul este de obicei scris într-un stil rigid și demodat, întrucât rulează direct pe hardware, trebuie să ținem cont de faptul că apelul său trebuie să transmită o formă strictă și corectă a cererii. Astfel, chiar dacă nu avem nevoie de unele variabile, trebuie totuși să completăm dimensiunea, indiferent de utilizare. Să verificăm printr-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 informațiile curente ale sistemului. Putem compara acest cod cu o cutie cu încuietoare și o cheie.syscall.SYS_SYSINFO este o cheie care deschide o cutie aflată în interiorul nucleului. Prin urmare, utilizarea cheii corecte pentru cutie este importantă. Ce se va întâmpla dacă folosim syscall.SYS_GETPID pentru acest apel? Aceasta este o cheie pentru o cutie care conține ID-ul procesului. Aceasta va încerca să obțină un PID dintr-un spațiu destinat informațiilor de sistem. Drept urmare, niciuna dintre informații 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. În primul compartiment al cutiei se află Uptime, cu o dimensiune de 2^64. Dacă încercăm să citim acest câmp cu 2^32, secvența de biți nu este citită integral. Nu putem utiliza astfel de fragmente binare parțiale, decât dacă dorim să scriem trucuri de nivel jos.

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

Prin repetarea acestor fluxuri stricte și logice pentru a obține informații corecte din sistem, putem gestiona corect datele citite.

Cum să omitem „numele variabilelor”

Deși nu putem „omite” variabilele în sine, este important să distingem variabilele utilizate de cele neutilizate. Dacă utilizarea programului este suficient de clară, este preferabil să folosim variabile anonime ca substituenți, în locul etichetării fiecărei valori, chiar dacă acestea nu vor fi utilizate pe termen lung. Să verificăm acest lucru cu un exemplu, „Verificator de memorie liberă”

Exemplu – Verificator de memorie liberă

Atunci când verificăm memoria liberă și swap-ul, nu avem nevoie de celelalte informații care indică resurse diferite. Pentru o mai bună vizibilitate, puteți crea variabile anonime pentru a reține anumite spații.

 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  // anonim, iar cele neutilizate sunt marcate cu _
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 stocate efectiv într-o structură, nu există etichete sau marcaje lizibile în cod.

Concluzie

  • Utilizarea pachetelor syscall și unsafe din Go este încă mai sigură decât C/CGo
  • Dacă scrieți un proiect mare care poate fi extins cu ușurință:
    • Nu folosiți variabile anonime; atribuiți nume fiecărui membru.
  • Dacă scrieți un proiect cu utilizare limitată:
    • Puteți folosi variabile anonime pentru a ocupa spațiile care sunt de fapt neutilizate.
  • Pachetul syscall din Go este puternic și modern pentru gestionarea apelurilor de nivel jos

Citiți mai departe

syscall unsafe x/sys/unix