GoSuda

Go syscall은 저수준 I/O의 훌륭한 대체재입니다.

By Yunjin Lee
views ...

Summary

Go에서의 직접적인 system call에 대해 학습할 것입니다. Go는 엄격한 컴파일러 오류와 엄격한 GC를 제공하기 때문에, 저수준 호출을 Pure Go로 대체하는 것이 훨씬 좋습니다. 다행히도, 대부분의 C 함수 호출은 훌륭하고 현대적인 방식으로 Go에서 완전히 재구현되었습니다. 이에 대해 살펴보겠습니다.

System Call

System call은 운영 체제에 대한 직접적인 요청입니다. 시스템은 하드웨어에서 바로 실행되기 때문에 보통 엄격하고 구식의 스타일로 작성되므로, 호출이 엄격하고 정확한 형식의 요청을 전달해야 함을 고려해야 합니다. 따라서, 일부 변수가 필요하지 않더라도, 사용 여부와 관계없이 크기를 채워야 합니다. 완전히 작동하는 예제를 통해 확인해 봅시다.

Full Example

 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}

이 예제는 모든 변수를 포함하며, 현재 시스템 정보에 대한 광범위한 정보를 출력합니다. 우리는 이 코드를 잠금 장치(locker)와 열쇠(key)로 비교할 수 있습니다.syscall.SYS_SYSINFO는 커널 내부에 있는 잠금 장치를 여는 열쇠입니다. 따라서, 잠금 장치에 맞는 올바른 열쇠를 사용하는 것이 중요합니다. 이 호출에 syscall.SYS_GETPID를 사용하면 어떻게 될까요? 이것은 프로세스 ID를 포함하는 잠금 장치의 열쇠입니다. 이것은 시스템 정보 공간에서 PID를 얻으려고 시도할 것입니다. 결과적으로, 정보 중 어느 것도 정확하게 읽을 수 없으며, 호출은 실패 상태로 반환되어야 합니다.

이제 어떤 항목들이 포함되어 있는지, 그리고 항목들이 어떻게 순서가 지정되는지 알아야 합니다. 잠금 장치의 첫 번째 슬롯에는 크기가 2^64인 Uptime이 있습니다. 이것을 2^32로 읽으려고 하면, 비트 시퀀스가 완전히 읽히지 않습니다. 저수준 트릭을 작성하려는 경우가 아니라면 이러한 종류의 부분적인 이진 데이터를 사용할 수 없습니다.

64비트의 이진 데이터를 읽은 후, 마침내 두 번째 슬롯에 도달합니다. 이것은 이전 64비트 크기의 정수를 읽었을 때만 정확하게 읽을 수 있습니다.

시스템으로부터 적절한 정보를 얻기 위해 이러한 엄격하고 논리적인 흐름을 반복함으로써, 우리는 읽은 데이터를 적절하게 처리할 수 있습니다.

'변수 이름'을 건너뛰는 방법

변수 자체를 '건너뛸' 수는 없지만, 사용된 변수와 버려진 변수를 구별하는 것이 중요합니다. 프로그램의 사용이 충분히 명확하다면, 영원히 사용되지 않을 값이라도 각각 레이블을 지정하는 것보다 익명 변수를 자리 표시자로 사용하는 것이 좋습니다. "Free Memory Checker"라는 예제를 통해 이를 확인해 봅시다.

Example - 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  // 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}

결과적으로, 변수들은 레이블 없이 읽힙니다. 익명 값들이 실제로 구조체에 저장되지만, 코드상에는 레이블/식별 가능한 표시가 없습니다.

Conclusion

  • Go의 syscallunsafe를 사용하는 것은 여전히 C/CGo보다 안전합니다.
  • 쉽게 확장될 수 있는 대규모 프로젝트를 작성하는 경우:
    • 익명 변수를 만들지 말고, 멤버들에 대해 각각 이름을 만드십시오.
  • 사용이 제한적인 프로젝트를 작성하는 경우:
    • 실제로 사용되지 않는 공간을 보유하기 위해 익명 변수를 사용할 수 있습니다.
  • Go의 syscall은 저수준 호출을 처리하는 데 강력하고 현대적입니다.

Read Further

syscall unsafe x/sys/unix