GoSuda

Közvetlen Linux rendszerhívások tiszta Go-ban a syscall.Syscall és az unsafe.Pointer segítségével

By Yunjin Lee views ...

Összefoglalás

Megismerkedünk a közvetlen rendszerhívásokkal Go nyelven. Mivel a Go szigorú fordítóprogram-hibaüzeneteket és merev szemétgyűjtést (GC) biztosít, előnyösebb az alacsony szintű hívásokat tiszta Go kóddal helyettesíteni. Szerencsére a C függvényhívások nagy része jó és korszerű módon teljesen újra van implementálva Go nyelven. Nézzük meg ezt.

Rendszerhívás

A rendszerhívás közvetlen kérés az operációs rendszer felé. Mivel a rendszer általában merev, régimódi stílusban íródik, hiszen közvetlenül a hardveren fut, figyelembe kell vennünk, hogy hívásának szigorú és helyes formájú kérést kell közvetítenie. Ezért még akkor is, ha nincs szükségünk bizonyos változókra, a méretüket akkor is ki kell tölteni, függetlenül attól, hogy használjuk-e őket. Nézzünk meg egy teljesen működőképes példát.

Teljes példa

 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}

Ez a példa minden változót tartalmaz, és kiírja a jelenlegi rendszerinformációk részletes adatait. Ezt a kódot úgy tekinthetjük, mint egy zárat és egy kulcsot. A syscall.SYS_SYSINFO egy kulcs, amely kinyitja a kernelen belül található zárat. Ezért fontos, hogy a zárhoz megfelelő kulcsot használjunk. Mi történik, ha ehhez a híváshoz a syscall.SYS_GETPID értéket használjuk? Ez egy olyan kulcs, amely a folyamatazonosítót tartalmazó zárat nyitja. Ez arra tesz kísérletet, hogy a PID-et a rendszerinformációk területéről szerezze meg. Ennek eredményeként az információk egyike sem olvasható helyesen; a hívásnak sikertelen állapotot kell visszaadnia.

Most meg kell tudnunk, milyen elemek vannak benne, és hogyan vannak sorrendbe rendezve. A zár első rekeszében található az Uptime, 2^64 méretben. Ha ezt 2^32 mérettel próbáljuk beolvasni, a bitsorozat nem olvasódik be teljesen. Ilyen részleges bináris adatokat nem használhatunk, hacsak nem alacsony szintű trükköket akarunk írni.

Az 64 bit bináris adat beolvasása után végre a második rekeszben vagyunk. Csak akkor olvasható pontosan, ha az előző 64 bites egész számot már beolvastuk.

E szigorú és logikus folyamatok ismétlésével a rendszerből megfelelő információt nyerhetünk, és a beolvasott adatokat megfelelően kezelhetjük.

Hogyan hagyjuk ki a „változónevek” használatát

Bár magukat a változókat nem tudjuk „kihagyni”, fontos megkülönböztetni a használt és az eldobott változókat. Ha a program használata elég egyértelmű, jobb névtelen változókat használni helyőrzőként, mint minden értéket megcímkézni, még akkor is, ha azokat véglegesen nem használjuk. Nézzünk erre egy példát, a „Szabad memória ellenőrző” programot.

Példa – Szabad memória ellenőrző

Szabad memória/swap ellenőrzésekor nincs szükségünk más, különböző erőforrásokat jelölő információkra. A jobb áttekinthetőség érdekében névtelen változókat hozhatunk létre a konkrét területek tárolására.

 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  // névtelen, a nem használt mezőket _ jelöli
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}

Következésképpen a változók címkék nélkül kerülnek beolvasásra. Bár a névtelen értékek valójában egy struktúrában tárolódnak, a kódon nincsenek címkék vagy olvasható jelölések.

Következtetés

  • A Go syscall és unsafe csomagjainak használata továbbra is biztonságosabb, mint a C/CGo használata
  • Ha egy nagy, könnyen bővíthető projektet írunk:
    • Ne használjunk névtelen változókat; minden tagnak adjunk nevet.
  • Ha egy korlátozott felhasználású projektet írunk:
    • Használhatunk névtelen változókat azokon a területeken, amelyek valójában nincsenek használatban.
  • A Go syscall csomagja erőteljes és modern eszköz az alacsony szintű hívások kezelésére

További olvasnivaló

syscall unsafe x/sys/unix