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

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șiunsafedin 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
syscalldin Go este puternic și modern pentru gestionarea apelurilor de nivel jos