Přímá volání systému Linux v čistém Go pomocí syscall.Syscall a unsafe.Pointer

Shrnutí
Naučíme se o přímých systémových voláních v Go. Jelikož Go nabízí striktní kontrolu při kompilaci a přísný garbage collector (GC), je mnohem lepší nahradit nízkoúrovňová volání čistým Go. Naštěstí je většina volání funkcí z C plně reimplementována v Go, a to dobrým a moderním způsobem. Podívejme se na to.
Systémové volání
Systémové volání je přímý požadavek na operační systém. Protože systém je obvykle napsán v přísném, staromódním stylu, neboť běží přímo na hardwaru, musíme počítat s tím, že jeho volání musí být předáno v přesné a správné podobě požadavku. Proto, i když některé proměnné nepotřebujeme, musíme přesto vyplnit jejich velikost bez ohledu na to, zda je použijeme. Podívejme se na plně funkční příklad.
Úplný příklad
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}
Tento příklad obsahuje všechny proměnné a vypisuje podrobné informace o aktuálním stavu systému.
Tento kód můžeme přirovnat k zámku a klíči.syscall.SYS_SYSINFO je klíč, který odemyká zámek uvnitř jádra.
Proto je důležité použít správný klíč pro daný zámek.
Co se stane, když pro toto volání použijeme syscall.SYS_GETPID?
Jde o klíč k zámku, který obsahuje ID procesu.
Tím se pokusíme získat PID z prostoru určeného pro systémové informace.
Výsledkem je, že žádnou z informací nelze přečíst správně; volání musí skončit ve stavu selhání.
Nyní potřebujeme vědět, jaké položky jsou obsaženy a v jakém pořadí jsou uspořádány. V prvním slotu zámku je Uptime o velikosti 2^64. Pokud se pokusíme přečíst tuto hodnotu pomocí 2^32, bitová sekvence nebude přečtena celá. Nemůžeme používat takovéto částečné binární hodnoty, pokud nechceme psát nízkoúrovňové triky.
Po přečtení 64 bitů binárních dat se konečně dostaneme ke druhému slotu. Lze ho přečíst správně až tehdy, když jsme přečetli předchozí 64bitové celé číslo.
Opakováním těchto přísných a logických kroků získáme správné informace o systému a můžeme s přečtenými daty správně pracovat.
Jak přeskočit „názvy proměnných“
I když proměnné samotné nelze „přeskočit“, je důležité rozlišovat mezi použitými a zahozenými proměnnými. Pokud je účel programu dostatečně jasný, je lepší použít anonymní proměnné jako zástupné symboly, než označovat každou hodnotu, i když nebudou nikdy použity. Podívejme se na to na příkladu „Kontrola volné paměti“.
Příklad: Kontrola volné paměti
Při kontrole volné paměti a swapu nepotřebujeme ostatní informace, které označují jiné zdroje. Pro lepší přehlednost můžete vytvořit anonymní proměnné, které zastupují konkrétní prostory.
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 _
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}
Výsledkem je, že hodnoty se čtou bez popisků. Ačkoli se anonymní hodnoty ve skutečnosti ukládají do struktury, v kódu nejsou žádné popisky ani čitelné označení.
Závěr
- Použití
syscallaunsafev Go je stále bezpečnější než C/CGo - Pokud píšete rozsáhlý projekt, který lze snadno rozšiřovat:
- Nepoužívejte anonymní proměnné; pojmenujte každý člen.
- Pokud píšete projekt s omezeným použitím:
- Můžete použít anonymní proměnné k zastoupení prostorů, které jsou skutečně nepoužité.
syscallv Go je výkonný a moderní nástroj pro práci s nízkoúrovňovými voláními