GoSuda

Go syscall является блестящей заменой низкоуровневого I/O

By Yunjin Lee
views ...

Резюме

Мы узнаем о прямом системном вызове в Go. Поскольку Go предлагает строгие ошибки компилятора и жесткий GC, гораздо лучше заменить низкоуровневые вызовы на чистый Go (Pure Go). К счастью, большинство вызовов функций C полностью переработаны в Go в хорошей и современной манере. Давайте рассмотрим это.

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

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

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

 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) // Сбой системного вызова sysinfo:
31		return
32	}
33
34	scale := float64(1 << 16)
35	fmt.Printf("Uptime: %d seconds\n", info.Uptime) // Время работы: %d секунд
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", // Память: всего=%d МБ свободно=%d МБ буфер=%d МБ
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", // Swap: всего=%d МБ свободно=%d МБ
45		info.Totalswap*uint64(info.MemUnit)/1024/1024,
46		info.Freeswap*uint64(info.MemUnit)/1024/1024)
47	fmt.Printf("Processes: %d\n", info.Procs) // Процессы: %d
48}

Этот пример включает все переменные и выводит обширную информацию о текущем состоянии системы. Мы можем сравнить этот код с замком и ключом.syscall.SYS_SYSINFO — это ключ, который отпирает замок, находящийся внутри ядра. Следовательно, важно использовать правильный ключ для замка. Что произойдет, если мы используем syscall.SYS_GETPID для этого вызова? Это ключ для замка, который содержит ID процесса (Process ID). Это будет попытка получить PID из пространства для системной информации. В результате ни одна из частей информации не может быть прочитана корректно; вызов должен быть возвращен как неудачный.

Теперь нам нужно знать, какие элементы содержатся и как они упорядочены. В первом слоте замка у нас есть Uptime (время работы), размером 2^64. Если мы попытаемся прочитать это с помощью 2^32, битовая последовательность будет прочитана не полностью. Мы не можем использовать такие частичные бинарные данные, если только мы не собираемся писать низкоуровневые трюки.

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

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

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

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

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

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

 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  // анонимные и неиспользуемые помечены как _
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) // Сбой системного вызова sysinfo:
32		return
33	}
34
35	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n", // Память: всего=%d МБ свободно=%d МБ буфер=%d МБ
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", // Swap: всего=%d МБ свободно=%d МБ
40		info.Totalswap*uint64(info.MemUnit)/1024/1024,
41		info.Freeswap*uint64(info.MemUnit)/1024/1024)
42}

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

Заключение

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

Дальнейшее чтение

syscall unsafe x/sys/unix