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

Ö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ésunsafecsomagjainak 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
syscallcsomagja erőteljes és modern eszköz az alacsony szintű hívások kezelésére