Go syscall stanowi wyśmienite zastąpienie niskopoziomowego I/O
Podsumowanie
Poznamy bezpośrednie wywołania systemowe w Go. Ponieważ Go oferuje ścisłe błędy kompilatora i sztywny GC, znacznie lepiej jest zastąpić wywołania niskopoziomowe w C czystym Go. Na szczęście większość wywołań funkcji C została w pełni zaimplementowana w Go, w dobry i nowoczesny sposób. Przyjrzyjmy się temu.
Wywołanie Systemowe
Wywołanie systemowe jest bezpośrednim żądaniem skierowanym do systemu operacyjnego. Ponieważ system jest zazwyczaj napisany w sztywnym, staroświeckim stylu, gdyż działa bezpośrednio na sprzęcie, musimy wziąć pod uwagę, że jego wywołanie musi dostarczać ścisłą i poprawną formę żądania. Zatem, nawet jeśli nie potrzebujemy niektórych zmiennych, nadal musimy wypełnić rozmiar niezależnie od użycia. Sprawdźmy to na w pełni działającym przykładzie.
Pełny Przykład
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}
Ten przykład zawiera wszystkie zmienne i wyświetla obszerne informacje o aktualnym systemie.
Możemy porównać ten kod do szafki i klucza.syscall.SYS_SYSINFO jest kluczem, który otwiera szafkę znajdującą się wewnątrz jądra.
Dlatego ważne jest użycie poprawnego klucza do szafki.
Co się stanie, gdy użyjemy syscall.SYS_GETPID dla tego wywołania?
Jest to klucz do szafki zawierającej Process ID.
Spowoduje to próbę pobrania PID z przestrzeni przeznaczonej na informacje systemowe.
W rezultacie żadna z informacji nie może zostać poprawnie odczytana; wywołanie musi zostać zwrócone jako stan niepowodzenia.
Teraz musimy wiedzieć, jakie elementy są zawarte i w jakiej kolejności. W pierwszym miejscu szafki mamy Uptime, o rozmiarze 2^64. Jeśli spróbujemy odczytać to z 2^32, sekwencja bitów nie zostanie w pełni odczytana. Nie możemy używać tego rodzaju częściowych wartości binarnych, chyba że zamierzamy pisać niskopoziomowe sztuczki.
Po odczytaniu 64 bitów danych binarnych, w końcu jesteśmy na drugim miejscu. Może ono być dokładnie odczytane tylko wtedy, gdy odczytaliśmy poprzednią liczbę całkowitą o rozmiarze 64-bitowym.
Poprzez powtarzanie tych ścisłych i logicznych przepływów w celu uzyskania właściwej informacji z systemu, możemy właściwie przetwarzać odczytane dane.
Jak pominąć 'nazwy zmiennych'
Mimo że nie możemy 'pominąć' samych zmiennych, ważne jest rozróżnienie zmiennych używanych od odrzucanych. Jeśli użycie programu jest wystarczająco jasne, lepiej jest używać bezimiennych zmiennych jako symboli zastępczych (placeholders) niż etykietować każdą wartość, nawet jeśli nigdy nie będą użyte. Sprawdźmy to na przykładzie, "Sprawdzanie Wolnej Pamięci"
Przykład - Sprawdzanie Wolnej Pamięci
Podczas sprawdzania wolnej pamięci/swapów, nie potrzebujemy innych informacji wskazujących na różne zasoby. Aby osiągnąć lepszą czytelność, można utworzyć anonimowe zmienne, aby zajmowały określone miejsca.
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 // anonymous, and unused ones are marked as _
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}
W konsekwencji, zmienne są odczytywane bez etykiet. Chociaż anonimowe wartości są faktycznie przechowywane w strukturze, w kodzie nie ma etykiet/czytelnych oznaczeń.
Konkluzja
- Używanie
syscalliunsafew Go jest nadal bezpieczniejsze niż C/CGo - Jeśli piszesz duży projekt, który można łatwo rozbudować:
- Nie twórz anonimowych zmiennych; utwórz nazwy dla każdego elementu.
- Jeśli piszesz projekt o ograniczonym zastosowaniu:
- Możesz używać anonimowych zmiennych do zajmowania miejsc, które faktycznie nie są używane.
syscallw Go jest potężny i nowoczesny do obsługi wywołań niskopoziomowych