GoSuda

Go syscall představuje vynikající náhradu nízkoúrovňového I/O.

By Yunjin Lee
views ...

Souhrn

Seznámíme se s přímým voláním systémových služeb (system call) v Go. Vzhledem k tomu, že Go nabízí přísné chyby kompilátoru a striktní GC, je mnohem lepší nahradit nízkoúrovňová volání v čistém Go. Naštěstí většina volání C funkcí je plně reimplementována v Go, a to dobrým a současným způsobem. Pojďme se na to podívat.

System Call

System call je přímá žádost na operační systém. Jelikož je systém obvykle napsán v pevném, staromódním stylu, neboť běží přímo na hardwaru, musíme vzít v úvahu, že jeho volání musí poskytovat přísnou a správnou formu požadavku. Takže i když nepotřebujeme některé proměnné, stále musíme vyplnit velikost bez ohledu na použití. Ověříme si to na plně funkčním příkladu.

Ú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	// Volání system call SYS_SYSINFO
29	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
30	// Kontrola chyb
31	if errno != 0 {
32		fmt.Println("sysinfo syscall failed:", errno)
33		return
34	}
35
36	scale := float64(1 << 16)
37	fmt.Printf("Uptime: %d seconds\n", info.Uptime)
38	fmt.Printf("Load Average: %.2f %.2f %.2f\n",
39		float64(info.Loads[0])/scale,
40		float64(info.Loads[1])/scale,
41		float64(info.Loads[2])/scale)
42	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
43		info.Totalram*uint64(info.MemUnit)/1024/1024,
44		info.Freeram*uint64(info.MemUnit)/1024/1024,
45		info.Bufferram*uint64(info.MemUnit)/1024/1024)
46	fmt.Printf("Swap: total=%d MB free=%d MB\n",
47		info.Totalswap*uint64(info.MemUnit)/1024/1024,
48		info.Freeswap*uint64(info.MemUnit)/1024/1024)
49	fmt.Printf("Processes: %d\n", info.Procs)
50}

Tento příklad zahrnuje všechny proměnné a tiskne rozsáhlé informace o aktuálních systémových informacích. Tento kód můžeme přirovnat ke skříňce a klíči.syscall.SYS_SYSINFO je klíč, který odemyká skříňku, jež se nachází uvnitř jádra. Proto je důležité použít správný klíč ke skříňce. Co se stane, když pro toto volání použijeme syscall.SYS_GETPID? Toto je klíč ke skříňce, která obsahuje ID procesu. Pokusí se získat PID z prostoru pro systémové informace. V důsledku toho nelze žádnou informaci správně přečíst; volání musí být vráceno jako neúspěšný stav.

Nyní potřebujeme vědět, které položky jsou obsaženy a jak jsou položky uspořádány. V prvním slotu skříňky máme Uptime s velikostí 2^64. Pokud se to pokusíme přečíst s 2^32, bitová sekvence není plně přečtena. Tyto druhy částečných binárních dat nemůžeme použít, pokud se nechystáme psát nízkoúrovňové triky.

Po přečtení 64 bitů binárních dat jsme konečně ve druhém slotu. Lze jej přesně přečíst pouze tehdy, když jsme přečetli předchozí 64bitové celé číslo.

Opakováním těchto přísných a logických toků pro získání správných informací ze systému můžeme správně zpracovat přečtená data.

Jak přeskočit 'názvy proměnných'

I když nemůžeme 'přeskočit' samotné proměnné, je důležité rozlišovat použité proměnné a ty, které byly zahozeny. Pokud je použití programu dostatečně jasné, je lepší použít bezejmenné proměnné jako zástupné symboly, než označovat každou hodnotu, i když se nikdy nepoužije. Pojďme si to ověřit na příkladu "Kontrola volné paměti".

Příklad – Kontrola volné paměti

Při kontrole volné paměti/swapů nepotřebujeme další informace, které by naznačovaly jiné zdroje. Pro dosažení lepší viditelnosti můžete použít anonymní proměnné k uchování specifických prostorů.

 1package main
 2
 3import (
 4	"fmt"
 5	"syscall"
 6	"unsafe"
 7)
 8
 9type sysinfo_t struct {
10	_          int64 // anonymní a nepoužívané jsou označeny jako _
11	_         [3]uint64
12	Totalram  uint64
13	Freeram   uint64
14	Sharedram uint64
15	Bufferram uint64
16	Totalswap uint64
17	Freeswap  uint64
18	_         uint16  
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 důsledku toho jsou proměnné čteny bez štítků. Ačkoli anonymní hodnoty jsou skutečně uloženy do struktury, v kódu nejsou žádné štítky/čitelná označení.

Závěr

  • Používání Go syscall a unsafe je stále bezpečnější než C/CGo.
  • Pokud píšete obrovský projekt, který lze snadno rozšířit:
    • Nevytvářejte anonymní proměnné; pojmenujte každého člena.
  • Pokud píšete projekt s omezeným použitím:
    • Můžete použít anonymní proměnné k uchování prostorů, které jsou ve skutečnosti nepoužité.
  • Go syscall je výkonný a moderní pro zpracování nízkoúrovňových volání.

Další četba

syscall unsafe x/sys/unix