Direkte Linux-systemkall i ren Go med syscall.Syscall og unsafe.Pointer

Sammendrag
Vi skal lære om direkte systemkall i Go. Ettersom Go tilbyr strenge kompileringsfeil og en stiv GC, er det langt bedre å erstatte lavnivåkall med ren Go. Heldigvis er de fleste C-funksjonskallene fullt ut reimplementert i Go, på en god og moderne måte. La oss se nærmere på dette.
Systemkall
Et systemkall er en direkte forespørsel til operativsystemet. Ettersom systemet vanligvis er skrevet i en stiv, gammeldags stil, da det kjører direkte på maskinvaren, må vi ta hensyn til at kallet må utgjøres i en streng og korrekt form. Derfor må vi, selv om vi ikke trenger enkelte variabler, likevel angi størrelsen, uavhengig av bruk. La oss se på et fullt fungerende eksempel.
Fullstendig eksempel
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}
Dette eksemplet inkluderer alle variabler og skriver ut omfattende informasjon om gjeldende systeminformasjon.
Vi kan sammenligne denne koden med et lås og en nøkkel.syscall.SYS_SYSINFO er en nøkkel som låser opp et skap som befinner seg inne i kjernen.
Derfor er det viktig å bruke riktig nøkkel til skapet.
Hva skjer når vi bruker syscall.SYS_GETPID for dette kallet?
Dette er en nøkkel til et skap som inneholder prosess-ID-en.
Dette vil forsøke å hente en PID fra et område for systeminformasjon.
Som et resultat kan ingen av informasjonene leses korrekt; kallet må returneres med tilstanden mislykket.
Nå må vi vite hvilke elementer som finnes, og hvordan elementene er ordnet. I den første plassen i skapet finner vi Uptime, med en størrelse på 2^64. Hvis vi forsøker å lese dette med 2^32, leses ikke bitsekvensen fullt ut. Vi kan ikke bruke slike delvise binærdata, med mindre vi skal skrive lavnivåtriks.
Etter å ha lest 64 bits binærdata, er vi endelig ved en andre plass. Den kan bare leses nøyaktig når vi har lest det forrige heltallet på 64 bits.
Ved å gjenta disse strenge og logiske trinnene for å få riktig informasjon fra systemet, kan vi håndtere de leste dataene på en korrekt måte.
Hvordan hoppe over «variabelnavn»
Selv om vi ikke kan «hoppe over» variabler selv, er det viktig å skille mellom brukte og forkastede variabler. Hvis programmets formål er tilstrekkelig klart, er det bedre å bruke navnløse variabler som plassholdere enn å merke hver verdi, selv om de ikke brukes permanent. La oss se på dette med et eksempel, «Free Memory Checker».
Eksempel - Free Memory Checker
Når vi kontrollerer ledig minne og swap, trenger vi ikke annen informasjon som angir andre ressurser. For å oppnå bedre oversikt kan du lage anonyme variabler som holder bestemte områder.
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 // anonyme, og ubrukte, markeres med _
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}
Følgelig leses variablene uten etiketter. Selv om de anonyme verdiene faktisk lagres i en struktur, finnes det ingen etiketter eller lesbare merker i koden.
Konklusjon
- Å bruke Gos
syscallogunsafeer fortsatt tryggere enn C/CGo - Hvis du skriver et stort prosjekt som enkelt kan utvides:
- Ikke bruk anonyme variabler; gi hvert medlem et eget navn.
- Hvis du skriver et prosjekt med begrenset bruksområde:
- Du kan bruke anonyme variabler til å holde plasser som faktisk ikke brukes.
- Gos
syscaller kraftig og moderne når det gjelder håndtering av lavnivåkall