Go syscall является превосходной заменой низкоуровневого I/O
Резюме
Мы рассмотрим прямой системный вызов в Go. Поскольку Go предлагает строгие ошибки компиляции и жёсткий GC, гораздо лучше заменять низкоуровневые вызовы на чистый 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)
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}
Этот пример включает все переменные и выводит обширную информацию о текущем состоянии системы.
Мы можем сравнить этот код с замком и ключом.syscall.SYS_SYSINFO — это ключ, который открывает замок внутри ядра.
Следовательно, использование правильного ключа для замка важно.
Что произойдёт, если мы используем syscall.SYS_GETPID для этого вызова?
Это ключ для замка, который содержит Process ID.
Это приведёт к попытке получить PID из области системной информации.
В результате ни одна из сведений не может быть прочитана корректно; вызов должен быть возвращён как неуспешное состояние.
Теперь нам нужно знать, какие элементы содержатся и как они упорядочены. В первом слоте замка у нас есть Uptime размером 2^64. Если мы попытаемся прочитать это с 2^32, битовая последовательность не будет прочитана полностью. Мы не можем использовать такие частичные бинарные данные, если только мы не собираемся писать низкоуровневые трюки.
После чтения 64 бит бинарных данных, мы, наконец, находимся во втором слоте. Он может быть прочитан точно только после того, как мы прочитали предыдущее 64-битное целое число.
Повторяя эти строгие и логичные последовательности для получения правильной информации от системы, мы можем корректно обрабатывать прочитанные данные.
Как пропустить «имена переменных»
Несмотря на то, что мы не можем «пропустить» сами переменные, важно различать используемые и отброшенные переменные. Если использование программы достаточно ясно, лучше использовать безымянные переменные в качестве заполнителей, чем маркировать каждое значение, даже если оно никогда не используется. Рассмотрим это на примере «Проверка свободной памяти».
Пример — Проверка свободной памяти
При проверке свободной памяти/свопов нам не нужна другая информация, указывающая на различные ресурсы. Для достижения лучшей наглядности вы можете создавать анонимные переменные для хранения определённых мест.
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)
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}
Следовательно, переменные читаются без меток. Хотя анонимные значения фактически хранятся в структуре, в коде нет меток/разборчивых обозначений.
Вывод
- Использование
syscallиunsafeв Go всё ещё безопаснее, чем C/CGo. - Если вы пишете большой проект, который может быть легко расширен:
- Не используйте анонимные переменные; создавайте имена для каждого члена.
- Если вы пишете проект с ограниченным использованием:
- Вы можете использовать анонимные переменные для хранения мест, которые фактически не используются.
syscallв Go является мощным и современным инструментом для обработки низкоуровневых вызовов.