GoSuda

Go syscall jest znakomitym substytutem niskopoziomowego I/O

By Yunjin Lee
views ...

Podsumowanie

Zapoznamy się z bezpośrednim wywołaniem systemowym w Go. Ponieważ Go oferuje rygorystyczne błędy kompilatora i sztywny GC, znacznie lepiej jest zastąpić wywołania niskopoziomowe w Pure Go. Na szczęście większość wywołań funkcji C została w pełni zaimplementowana ponownie 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 pisany w sztywnym, staroświeckim stylu, gdyż działa bezpośrednio na sprzęcie, musimy wziąć pod uwagę, że jego wywołanie musi dostarczyć rygorystyczną 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 bieżącym systemie. Możemy porównać ten kod do szafki i klucza.syscall.SYS_SYSINFO jest kluczem, który odblokowuje szafkę znajdującą się wewnątrz jądra. Dlatego użycie poprawnego klucza do szafki jest ważne. Co się stanie, gdy użyjemy syscall.SYS_GETPID dla tego wywołania? Jest to klucz do szafki, która zawiera Process ID (identyfikator procesu). Spowoduje to próbę uzyskania 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ć, które elementy są zawarte i jak są uporządkowane. W pierwszym slocie szafki mamy Uptime, o rozmiarze 2^64. Jeśli spróbujemy odczytać to za pomocą 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 znajdujemy się w drugim slocie. Może on zostać odczytany dokładnie tylko wtedy, gdy odczytaliśmy poprzednią liczbę całkowitą o rozmiarze 64 bitów.

Powtarzając te rygorystyczne i logiczne przepływy, aby uzyskać właściwą informację z systemu, możemy prawidłowo obsłużyć odczytane dane.

Jak pominąć 'nazwy zmiennych'

Chociaż nie możemy 'ominąć' samych zmiennych, ważne jest, aby rozróżnić zmienne używane i te odrzucone. Jeśli użycie programu jest wystarczająco jasne, lepiej jest użyć bezimiennych zmiennych jako symboli zastępczych, niż etykietować każdą wartość, nawet jeśli nigdy nie zostaną użyte. Sprawdźmy to na przykładzie „Kontroler Wolnej Pamięci”

Przykład - Kontroler Wolnej Pamięci

Podczas sprawdzania Wolnej Pamięci/Swap, nie potrzebujemy innych informacji wskazujących na różne zasoby. Aby osiągnąć lepszą czytelność, można utworzyć anonimowe zmienne, które będą utrzymywać określone przestrzenie.

 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 _ (anonimowe i nieużywane są oznaczone jako _)
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 rezultacie zmienne są odczytywane bez etykiet. Chociaż wartości anonimowe są faktycznie przechowywane w strukturze, w kodzie nie ma etykiet/czytelnych oznaczeń.

Konkluzja

  • Używanie syscall i unsafe w Go jest nadal bezpieczniejsze niż C/CGo
  • Jeśli piszesz ogromny projekt, który można łatwo rozszerzyć:
    • Nie twórz anonimowych zmiennych; nadaj nazwy każdemu członkowi.
  • Jeśli piszesz projekt o ograniczonym zastosowaniu:
    • Możesz użyć anonimowych zmiennych do przechowywania przestrzeni, które faktycznie nie są używane.
  • syscall w Go jest potężny i nowoczesny do obsługi wywołań niskopoziomowych

Dalsza lektura

syscall unsafe x/sys/unix