Priame systémové volania Linuxu v čistom Go pomocou syscall.Syscall a unsafe.Pointer

Zhrnutie
Naučíme sa o priamom volaní systému v jazyku Go. Keďže Go ponúka prísne kontroly kompilátora a neústupný GC, je oveľa lepšie nahradiť nízkoúrovňové volania čistým Go. Našťastie väčšina volaní funkcií jazyka C je plne znovuimplementovaná v Go, dobrým a moderným spôsobom. Pozrime sa na to.
Volanie systému
Volanie systému je priamy požiadavok na operačný systém. Keďže systém je zvyčajne napísaný v neohybnom, staromódnom štýle, pretože beží priamo na hardvéri, musíme zohľadniť, že jeho volanie musí odovzdať prísny a správny tvar požiadavky. Preto, aj keď niektoré premenné nepotrebujeme, stále musíme vyplniť ich veľkosť bez ohľadu na použitie. Overme si to na plne funkčnom príklade.
Úplný prí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 príklad obsahuje všetky premenné a vypisuje podrobné informácie o aktuálnych systémových informáciách.
Tento kód môžeme prirovnať k zámku a kľúču.syscall.SYS_SYSINFO je kľúč, ktorý odomyká zámok nachádzajúci sa vnútri jadra.
Preto je dôležité použiť správny kľúč pre zámok.
Čo sa stane, keď pre toto volanie použijeme syscall.SYS_GETPID?
Toto je kľúč k zámku, ktorý obsahuje identifikátor procesu (PID).
Týmto sa pokúsime získať PID z priestoru pre systémové informácie.
Výsledkom je, že žiadnu z informácií nie je možné správne prečítať; volanie musí skončiť so stavom zlyhania.
Teraz musíme vedieť, aké položky sú obsiahnuté a ako sú usporiadané. V prvom slote zámku sa nachádza Uptime s veľkosťou 2^64. Ak sa pokúsime čítať to s 2^32, bitová postupnosť nebude prečítaná úplne. Nemôžeme používať takéto čiastočné binárne údaje, pokiaľ nechceme písať nízkoúrovňové triky.
Po prečítaní 64 bitov binárnych údajov sa konečne dostaneme k druhému slotu. Môže byť prečítaný presne len vtedy, keď sme prečítali predchádzajúce 64-bitové celé číslo.
Opakovaním týchto prísnych a logických postupov získame správne informácie zo systému a môžeme s prečítanými údajmi správne pracovať.
Ako preskočiť 'názvy premenných'
Hoci nemôžeme premenné samotné 'preskočiť', je dôležité rozlišovať použité a zahodené premenné. Ak je účel programu dostatočne jasný, je lepšie použiť bezmenné premenné ako zástupné miesta, než označovať každú hodnotu, aj keď sa navždy nepoužijú. Overme si to na príklade „Kontrola voľnej pamäte“.
Príklad – Kontrola voľnej pamäte
Pri kontrole voľnej pamäte/swapu nepotrebujeme ostatné informácie, ktoré označujú iné zdroje. Pre lepšiu prehľadnosť môžete vytvoriť anonymné premenné, ktoré zachytia konkrétne miesta.
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ýsledkom je, že premenné sa čítajú bez označení. Hoci sú anonymné hodnoty v skutočnosti uložené v štruktúre, v kóde nie sú žiadne názvy ani čitateľné označenia.
Záver
- Používanie
syscallaunsafev Go je stále bezpečnejšie ako C/CGo - Ak píšete rozsiahly projekt, ktorý sa dá ľahko rozširovať:
- Nepoužívajte anonymné premenné; vytvárajte názvy pre každý člen.
- Ak píšete projekt s obmedzeným použitím:
- Môžete použiť anonymné premenné na zachytenie miest, ktoré sú skutočne nepoužité.
syscallv Go je výkonný a moderný nástroj na prácu s nízkoúrovňovými volaniami