GoSuda

Go syscall er en strålende erstatning for I/O på lavt nivå

By Yunjin Lee
views ...

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 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 kraftig og moderne for å håndtere lavnivåkall

Les Videre

syscall unsafe x/sys/unix