---
id: c90263519492728e7cc2d0ce840057b6
author: Yunjin Lee
title: Direkte Linux-systemkald i ren Go med syscall.Syscall og unsafe.Pointer
description: Kald af Linux-kernelens systemkald, såsom SYS_SYSINFO, direkte fra ren Go ved hjælp af ABI-justerede structs, unsafe.Pointer og den almindelige syscall-pakke uden cgo.
language: da
date: 2025-10-26T12:31:21.655228078Z
path: /blog/posts/go-syscall-is-a-brilliant-replacement-of-low-level-i-o-za50951e1
category: lowlevel
---

## Resumé

Vi vil lære om direkte systemkald i Go.
Da Go tilbyder strikse compilerfejl og en stram GC, er det langt bedre at erstatte lavniveaukald med ren Go.
Heldigvis er de fleste af C-funktionskaldene fuldt genimplementeret i Go, på en god og moderne måde.
Lad os se nærmere på det.

## Systemkald

Et systemkald er en direkte anmodning til operativsystemet.
Da systemet normalt er skrevet i en stiv, gammeldags stil, fordi det kører direkte på hardwaren, må vi tage i betragtning, at dets kald skal levere en streng og korrekt form for anmodning.
Selv hvis vi ikke har brug for nogle variable, skal vi derfor stadig udfylde størrelsen uanset anvendelse.
Lad os se på et fuldt fungerende eksempel.

## Fuldt eksempel

```go
package main
import (
	"fmt"
	"syscall"
	"unsafe"
)

type sysinfo_t struct {
	Uptime    int64
	Loads     [3]uint64
	Totalram  uint64
	Freeram   uint64
	Sharedram uint64
	Bufferram uint64
	Totalswap uint64
	Freeswap  uint64
	Procs     uint16
	Pad       uint16
	_         [4]byte
	Totalhigh uint64
	Freehigh  uint64
	MemUnit   uint32
	_         [4]byte
}

func main() {
	var info sysinfo_t
	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
	if errno != 0 {
		fmt.Println("sysinfo syscall failed:", errno)
		return
	}

	scale := float64(1 << 16)
	fmt.Printf("Uptime: %d seconds\n", info.Uptime)
	fmt.Printf("Load Average: %.2f %.2f %.2f\n",
		float64(info.Loads[0])/scale,
		float64(info.Loads[1])/scale,
		float64(info.Loads[2])/scale)
	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
		info.Totalram*uint64(info.MemUnit)/1024/1024,
		info.Freeram*uint64(info.MemUnit)/1024/1024,
		info.Bufferram*uint64(info.MemUnit)/1024/1024)
	fmt.Printf("Swap: total=%d MB free=%d MB\n",
		info.Totalswap*uint64(info.MemUnit)/1024/1024,
		info.Freeswap*uint64(info.MemUnit)/1024/1024)
	fmt.Printf("Processes: %d\n", info.Procs)
}
```

Dette eksempel inkluderer alle variable og udskriver omfattende information om de aktuelle systemoplysninger.
Vi kan sammenligne denne kode med et låsesystem og en nøgle.
`syscall.SYS_SYSINFO` er en nøgle, der åbner et låsesystem, som befinder sig inde i kernen.
Derfor er det vigtigt at bruge den korrekte nøgle til låsesystemet.
Hvad sker der, når vi bruger `syscall.SYS_GETPID` til dette kald?
Dette er en nøgle til et låsesystem, der indeholder proces-ID'et.
Dette vil forsøge at hente et PID fra et område med systemoplysninger.
Som resultat kan ingen af oplysningerne læses korrekt; kaldet må derfor returneres med en fejltilstand.

Nu skal vi vide, hvilke elementer der er indeholdt, og hvordan elementerne er ordnet.
I det første felt i låsesystemet findes Uptime, med en størrelse på 2^64.
Hvis vi forsøger at læse dette med 2^32, læses bitsekvensen ikke fuldt ud.
Vi kan ikke bruge sådanne delvise binærdata, medmindre vi skal skrive lavniveau-tricks.

Efter læsning af 64 bit binære data er vi endelig ved det andet felt.
Det kan kun læses korrekt, når vi har læst det foregående 64-bit heltal.

Ved at gentage disse strikse og logiske forløb for at opnå korrekte oplysninger fra systemet, kan vi håndtere de læste data korrekt.

## Sådan springer du over 'variabelnavne'

Selvom vi ikke kan 'springe over' variable i sig selv, er det vigtigt at skelne mellem anvendte og kasserede variable.
Hvis programmets formål er tilstrækkeligt klart, er det bedre at bruge navnløse variable som pladsholdere end at mærke hver værdi, selvom de ikke bruges permanent.
Lad os se dette med et eksempel, "Free Memory Checker"

## Eksempel - Free Memory Checker

Når man kontrollerer ledig hukommelse/swap, har vi ikke brug for andre oplysninger, som angiver andre ressourcer.
For at opnå bedre overskuelighed kan du lave anonyme variable til at holde bestemte pladser.

```go
package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

type sysinfo_t struct {
	_          int64
	_         [3]uint64
	Totalram  uint64
	Freeram   uint64
	Sharedram uint64
	Bufferram uint64
	Totalswap uint64
	Freeswap  uint64
	_         uint16  // anonymous, and unused ones are marked as _
	_         uint16
	_         [4]byte
	_         uint64
	_         uint64
	MemUnit   uint32
	_         [4]byte
}

func main() {
	var info sysinfo_t
	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
	if errno != 0 {
		fmt.Println("sysinfo syscall failed:", errno)
		return
	}

	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
		info.Totalram*uint64(info.MemUnit)/1024/1024,
		info.Freeram*uint64(info.MemUnit)/1024/1024,
		info.Bufferram*uint64(info.MemUnit)/1024/1024)
	fmt.Printf("Swap: total=%d MB free=%d MB\n",
		info.Totalswap*uint64(info.MemUnit)/1024/1024,
		info.Freeswap*uint64(info.MemUnit)/1024/1024)
}
```

Følgelig læses variablerne uden etiketter.
Selvom de anonyme værdier faktisk gemmes i en struktur, findes der ingen etiketter eller læselige markeringer i koden.

## Konklusion

- Brug af Gos `syscall` og `unsafe` er stadig sikrere end C/CGo
- Hvis du skriver et stort projekt, der nemt kan udvides:
  - Lav ikke anonyme variable; giv i stedet hvert medlem et navn.
- Hvis du skriver et projekt med begrænset anvendelse:
  - Du kan bruge anonyme variable til at holde pladser, der faktisk ikke bruges.
- Gos `syscall` er kraftfuld og moderne til at håndtere lavniveaukald

## Læs videre

[syscall](https://pkg.go.dev/syscall)
[unsafe](https://pkg.go.dev/unsafe)
[x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix)
