GoSuda

Go syscall er en strålende erstatning for lavnivå I/O

By Yunjin Lee
views ...

Sammendrag

Vi skal lære om direkte systemkall i Go. Siden Go tilbyr strenge compiler errors og rigid GC (Garbage Collection), er det mye bedre å erstatte lavnivåkall med Pure Go. Heldigvis er de fleste C-funksjonskall fullstendig reimplementert i Go, på en god og tidsriktig måte. La oss se nærmere på det.

Systemkall

Systemkall er en direkte forespørsel til operativsystemet. Siden systemet vanligvis er skrevet i en rigid, gammeldags stil ettersom det kjører direkte på maskinvaren, må vi vurdere at kallet må levere en streng og korrekt form for en forespørsel. Så, selv om vi ikke trenger noen variabler, må vi likevel fylle ut størrelsen uavhengig av bruken. La oss sjekke med et fullt fungerende eksempel.

Fullt 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 eksempelet 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 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 til en lås som inneholder Process ID. Dette vil forsøke å hente en PID fra et område for systeminformasjon. Som et resultat kan ingen av informasjonen leses korrekt; kallet må returneres som en feilet tilstand.

Nå må vi vite hvilke elementer som er inkludert, og hvordan elementene er ordnet. I det første sporet av en lås 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 partielle binære data med mindre vi skal skrive lavnivå-triks.

Etter å ha lest 64 bits med binær data, er vi endelig på det andre sporet. Det kan kun leses nøyaktig når vi har lest det forrige 64-biters heltallsverdien.

Ved å gjenta disse strenge og logiske flytene for å innhente 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 i seg 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 å navngi hver verdi selv om de aldri blir brukt. La oss sjekke dette med et eksempel, "Free Memory Checker" (Sjekk av ledig minne).

Eksempel - Free Memory Checker

Når vi sjekker ledig minne/Swaps, trenger vi ikke annen informasjon som indikerer andre 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 _ (anonyme og ubrukte er merket som _)
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 markeringer i koden.

Konklusjon

  • Bruk av Go's syscall og unsafe er 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 syscall er kraftfull og moderne for å håndtere lavnivåkall

Les videre

syscall unsafe x/sys/unix