GoSuda

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

By Yunjin Lee views ...

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 syscall a unsafe v 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é.
  • syscall v Go je výkonný a moderný nástroj na prácu s nízkoúrovňovými volaniami

Ďalšie čítanie

syscall unsafe x/sys/unix