---
id: c90263519492728e7cc2d0ce840057b6
author: Yunjin Lee
title: Директни системни извиквания на Linux в чист Go с syscall.Syscall и unsafe.Pointer
description: Извикване на системни повиквания на ядрото на Linux, като SYS_SYSINFO, директно от чист Go с помощта на структури, съгласувани с ABI, unsafe.Pointer и стандартния пакет syscall, без cgo.
language: bg
date: 2025-10-26T12:31:21.655228078Z
path: /blog/posts/go-syscall-is-a-brilliant-replacement-of-low-level-i-o-za50951e1
category: lowlevel
---

## Резюме

Ще научим за директните системни извиквания в Go.
Тъй като Go предлага строги компилаторни грешки и стриктен GC, е много по-добре да заменяме low-level извикванията с чист Go.
За щастие, повечето функции от C са напълно преизпълнени в Go, по добър и съвременен начин.
Нека ги разгледаме.

## Системно извикване

Системното извикване е директна заявка към операционната система.
Тъй като системата обикновено е написана в строг, старомоден стил, тъй като работи directly върху хардуера, трябва да имаме предвид, че нейното извикване трябва да предаде строга и коректна форма на заявката.
Така че, дори ако не се нуждаем от някои променливи, все пак трябва да попълним размера им независимо от използването.
Нека проверим с напълно работещ пример.

## Пълен пример

```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)
}
```

Този пример включва всички променливи и извежда подробна информация за текущата системна информация.
Можем да сравним този код с ключалка и ключ.
`syscall.SYS_SYSINFO` е ключ, който отключва ключалката, намираща се в ядрото.
Затова използването на правилния ключ за ключалката е важно.
Какво ще се случи, ако използваме `syscall.SYS_GETPID` за това извикване?
Това е ключ за ключалка, която съдържа идентификатор на процес (PID).
Това ще се опита да вземе PID от пространството за системна информация.
В резултат нито една от информациите не може да бъде прочетена коректно; извикването трябва да върне неуспешно състояние.

Сега трябва да знаем кои елементи се съдържат и как са подредени.
В първия слот на ключалката имаме Uptime, с размер 2^64.
Ако се опитаме да го прочетем с 2^32, битовата последователност не се прочита напълно.
Не можем да използваме такива частични двоични данни, освен ако не пишем low-level трикове.

След четене на 64 бита двоични данни, най-накрая сме на втория слот.
Той може да бъде прочетен точно само когато сме прочели предишното 64-битово цяло число.

Чрез повтаряне на тези строги и логически стъпки за получаване на правилна информация от системата, можем коректно да обработваме прочетените данни.

## Как да пропуснем 'имената на променливите'

Въпреки че не можем да "пропуснем" самите променливи, е важно да разграничаваме използваните и отхвърлените променливи.
Ако предназначението на програмата е достатъчно ясно, по-добре е да използваме безименни променливи като запазени места, вместо да етикетираме всяка стойност, дори ако те не се използват завинаги.
Нека проверим това с пример, "Проверка на свободната памет".

## Пример – Проверка на свободната памет

При проверка на свободната памет/swap, не се нуждаем от другата информация, която показва различни ресурси.
За по-добра яснота можете да създадете анонимни променливи, които да съхраняват конкретните места.

```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  // анонимни и неизползваните се маркират с _
	_         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)
}
```

Следователно променливите се четат без етикети.
Въпреки че анонимните стойности всъщност се съхраняват в структура, в кода няма етикети или ясно четливи маркировки.

## Заключение

- Използването на `syscall` и `unsafe` в Go все още е по-безопасно от C/CGo
- Ако пишете голям проект, който лесно може да се разширява:
  - Не използвайте анонимни променливи; дайте имена на всеки член.
- Ако пишете проект с ограничена употреба:
  - Можете да използвате анонимни променливи, за да запазите места, които всъщност не се използват.
- `syscall` в Go е мощен и модерен инструмент за работа с low-level извиквания

## За по-нататъшно четене

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