Go syscall은 저수준 I/O의 훌륭한 대체재입니다.
요약
Go에서의 직접적인 system call에 대해 학습하겠습니다. Go는 엄격한 컴파일러 오류와 견고한 GC를 제공하므로, Pure Go로 저수준 호출을 대체하는 것이 훨씬 좋습니다. 다행히도 대부분의 C 함수 호출은 Go에서 훌륭하고 현대적인 방식으로 완전히 재구현되었습니다. 살펴보겠습니다.
System Call
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)
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비트 크기 정수를 읽었을 때만 정확하게 읽을 수 있습니다.
시스템으로부터 적절한 정보를 얻기 위해 이러한 엄격하고 논리적인 흐름을 반복함으로써, 우리는 읽은 데이터를 올바르게 처리할 수 있습니다.
'변수 이름' 건너뛰는 방법
변수 자체를 '건너뛸' 수는 없지만, 사용되는 변수와 버려지는 변수를 구별하는 것이 중요합니다. 프로그램의 사용 목적이 충분히 명확하다면, 사용되지 않는 값이라 할지라도 각각의 값에 레이블을 붙이는 것보다 이름 없는 변수를 플레이스홀더로 사용하는 것이 좋습니다. "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)
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}
결과적으로, 변수들은 레이블 없이 읽힙니다. 익명 값들은 실제로 구조체에 저장되지만, 코드에는 레이블/식별 가능한 표시가 없습니다.
결론
- Go의
syscall및unsafe를 사용하는 것이 C/CGo보다 여전히 안전합니다. - 쉽게 확장될 수 있는 대규모 프로젝트를 작성하는 경우:
- 익명 변수를 만들지 마십시오; 멤버마다 이름을 지정하십시오.
- 사용이 제한적인 프로젝트를 작성하는 경우:
- 익명 변수를 사용하여 실제로 사용되지 않는 공간을 확보할 수 있습니다.
- Go의
syscall은 저수준 호출을 처리하는 데 강력하고 현대적입니다.