---
id: c90263519492728e7cc2d0ce840057b6
author: Yunjin Lee
title: Прямые системные вызовы Linux на чистом Go с помощью syscall.Syscall и unsafe.Pointer
description: Вызов системных вызовов ядра Linux, таких как SYS_SYSINFO, напрямую из чистого Go с использованием структур, выровненных по ABI, unsafe.Pointer и стандартного пакета syscall без cgo.
language: ru
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), гораздо лучше заменять низкоуровневые вызовы чистым Go.
К счастью, большинство функций вызова C полностью перереализованы на Go современным и качественным способом.
Давайте рассмотрим это.

## Системный вызов

Системный вызов представляет собой прямой запрос к операционной системе.
Поскольку система обычно написана в жёстком, устаревшем стиле, так как она работает непосредственно на аппаратном обеспечении, мы должны учитывать, что её вызов должен передавать строго и корректно оформленный запрос.
Поэтому, даже если нам не нужны некоторые переменные, мы всё равно должны заполнить их размер независимо от использования.
Давайте проверим это на полностью работающем примере.

## Полный пример

```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, битовая последовательность будет прочитана не полностью.
Мы не можем использовать такие частичные двоичные данные, если только не собираемся писать низкоуровневые трюки.

После чтения 64 бит двоичных данных мы, наконец, переходим ко второму слоту.
Его можно прочитать корректно только после чтения предыдущего 64-битного целого числа.

Повторяя эти строгие и логичные действия для получения корректной информации из системы, мы можем правильно обрабатывать прочитанные данные.

## Как пропустить «имена переменных»

Хотя мы не можем «пропустить» сами переменные, важно различать используемые и отброшенные переменные.
Если назначение программы достаточно понятно, лучше использовать безымянные переменные в качестве заполнителей, а не давать метки каждому значению, даже если они не будут использоваться постоянно.
Давайте проверим это на примере «Проверка свободной памяти».

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

При проверке свободной памяти и подкачки нам не нужна другая информация, относящаяся к различным ресурсам.
Для лучшей наглядности можно создать анонимные переменные для хранения определённых областей.

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

Следовательно, переменные читаются без меток.
Хотя анонимные значения фактически сохраняются в структуре, в коде нет меток или понятных обозначений.

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

- Использование `syscall` и `unsafe` в Go всё ещё безопаснее, чем C/CGo
- Если вы пишете большой проект, который может легко расширяться:
  - Не используйте анонимные переменные; задавайте имена каждому полю.
- Если вы пишете проект с ограниченным применением:
  - Можно использовать анонимные переменные для заполнения действительно неиспользуемых областей.
- `syscall` в Go является мощным и современным инструментом для работы с низкоуровневыми вызовами

## Читать далее

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