Go syscall er en strålende erstatning for I/O på lavt nivå
Sammendrag
Vi skal lære om direkte systemkall (system call) i Go. Siden Go tilbyr strenge kompilatorfeil og rigid GC, er det mye bedre å erstatte lavnivåkall i Pure Go. Heldigvis er de fleste C funksjonskall fullt ut reimplementert i Go, på en god og moderne måte. La oss se nærmere på det.
System Call
System call er en direkte forespørsel til operativsystemet. Siden systemet vanligvis er skrevet i en rigid, gammeldags stil ettersom det kjører direkte på maskinvare, må vi vurdere at kallet må levere en streng og korrekt form for forespørsel. Så, selv om vi ikke trenger visse variabler, må vi likevel fylle ut størrelsen uavhengig av bruk. La oss sjekke med 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 en lås og en nøkkel.syscall.SYS_SYSINFO er en nøkkel som låser opp en lås som befinner seg inne i en kjerne (kernel).
Derfor er det viktig å bruke riktig nøkkel til låsen.
Hva vil skje når vi bruker syscall.SYS_GETPID for dette kallet?
Dette er en nøkkel for en lås som inneholder Prosess ID (PID).
Dette vil forsøke å hente en PID fra plassen for systeminformasjon.
Som et resultat kan ingen av informasjonen leses korrekt; kallet må returneres som en mislykket tilstand.
Nå må vi vite hvilke elementer som er inkludert, og hvordan elementene er ordnet. I det første sporet av låsen har vi Uptime, med en størrelse på 2^64. Hvis vi prøver å lese dette med 2^32, blir ikke bitsekvensen fullstendig lest. Vi kan ikke bruke denne typen delvise binære data med mindre vi skal skrive lavnivå-triks.
Etter å ha lest 64 biter med binære data, er vi endelig på det andre sporet. Det kan kun leses nøyaktig når vi har lest det forrige 64-biters heltallet.
Ved å gjenta disse strenge og logiske flytene for å oppnå riktig informasjon fra et system, kan vi behandle leste data på en forsvarlig måte.
Hvordan hoppe over 'variabelnavn'
Selv om vi ikke kan 'hoppe over' variablene selv, er det viktig å skille mellom brukte variabler og forkastede variabler. Hvis bruken av programmet er tydelig nok, er det bedre å bruke navnløse variabler som plassholdere enn å merke hver verdi selv om de aldri blir brukt. La oss sjekke dette med et eksempel, "Free Memory Checker".
Eksempel - Free Memory Checker
Når vi sjekker Free Memory/Swaps, trenger vi ikke annen informasjon som indikerer forskjellige ressurser. For å oppnå bedre synlighet, kan du lage anonyme variabler for å holde spesifikke plasser.
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}
Følgelig blir variabler lest uten etiketter. Selv om anonyme verdier faktisk lagres i en struktur, er det ingen etiketter/lesbare merker i koden.
Konklusjon
- Bruk av Go's
syscallogunsafeer fortsatt tryggere enn C/CGo - Hvis du skriver et stort prosjekt som lett kan utvides:
- Ikke lag anonyme variabler; lag egne navn for medlemmene.
- Hvis du skriver et prosjekt som har begrenset bruk:
- Du kan bruke anonyme variabler for å holde plasser som faktisk ikke brukes.
- Go's
syscaller kraftig og moderne for å håndtere lavnivåkall