GoSuda

Go syscall е превъзходен заместител на I/O на ниско ниво

By Yunjin Lee
views ...

Резюме

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

Системно повикване (System Call)

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

Пълен пример

 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 MB свободна=%d MB буферна=%d MB
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", // Суап: общ=%d MB свободен=%d MB
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 е ключ, който отключва шкафче, което се намира вътре в ядрото (kernel). Следователно, използването на правилния ключ за шкафчето е важно. Какво ще се случи, ако използваме syscall.SYS_GETPID за това повикване? Това е ключ за шкафче, което съдържа ID на процеса (Process ID). Това ще се опита да получи PID от пространството за системна информация. В резултат на това нито една от информациите не може да бъде прочетена коректно; повикването трябва да бъде върнато като неуспешно състояние.

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

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

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

Как да пропуснем „имена на променливи“

Въпреки че не можем да „пропуснем“ самите променливи, важно е да се прави разлика между използваните променливи и отхвърлените. Ако употребата на програмата е достатъчно ясна, е по-добре да се използват безименни променливи като заместители (placeholders), отколкото да се етикетират всички стойности, дори ако те не се използват никога. Нека проверим това с пример, „Проверка на свободната памет“ (Free Memory Checker)

Пример - Проверка на свободната памет (Free Memory Checker)

Когато проверяваме свободната памет/суап (Free Memory/Swaps), не се нуждаем от друга информация, която указва различни ресурси. За да постигнете по-добра видимост, можете да създадете анонимни променливи, които да държат определени пространства.

 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 MB свободна=%d MB буферна=%d MB
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", // Суап: общ=%d MB свободен=%d MB
40		info.Totalswap*uint64(info.MemUnit)/1024/1024,
41		info.Freeswap*uint64(info.MemUnit)/1024/1024)
42}

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

Заключение

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

Прочетете повече (Read Further)

syscall unsafe x/sys/unix