Go syscall is a brilliant replacement of low-level I/O
Summary
We will learn about direct system call on Go. Since Go is offering strict compiler errors and rigid GC, it is much better to replace low-level calls in Pure Go. Luckily, most of the C function calls are fully reimplemented in Go, in a good and contemporary manner. Let's take a look at it.
System Call
System call is a direct request to the operating system. Since system is usually written in rigid, old-fashioned style since it is running right on a hardware, we need to consider that its call must deliver strict, and correct form of a request. So, even if we don't need some variables, we still need to fill out the size regardless of use. Let's check with fully-working example.
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}
This example includes all variables, and print extensive information of current system information.
We can compare this code, as a locker and a key.syscall.SYS_SYSINFO is a key that unlocks a locker that is inside of a kernel.
Therefore, using correct key for a locker is important.
What will happen when we use syscall.SYS_GETPID for this call?
This is a key for a locker that contains Process ID.
This will attempt to get a PID from a space for system information.
As a result, none of the information can be read correctly; the call must be retured as failed state.
Now, we need to know which items are contained, and how items are ordered. In a first slot of a locker, we have Uptime, with a size of : c90263519492728e7cc2d0ce840057b6 If we try to read this with : c90263519492728e7cc2d0ce840057b6 We cannot use these kinds of partial binaries unless we are going to write low-level tricks.
After reading 64 bits of binary data, finally we are on a second slot. It can only be read accurately when we have read previous 64-bit sized integer.
With repeating those strict, and logical flows to obtain a proper information from a system, we can properly handle read data.
How to skip 'variable names'
Even though we cannot 'skip' variables themselves, it is important to distinguish used variables and discarded ones. If use of the program is clear enough, it is better to use nameless variables as placeholders than labeling each values even if they are not used forever. Let's check this with an example, "Free Memory Checker"
Example - Free Memory Checker
When checking Free Memory/Swaps, we don't need other information that is indicating different resources. To achieve a better visibility, you can make anonymous variables to hold specific spaces.
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}
Consequently, variables are read without labels. Although anonymous values are actually stored into a structure, there is no labels/legible marks on a code.
Conclusion
- Using Go's
syscallandunsafeis still safer than C/CGo - If you are writing a huge project that can be expaneded easily:
- Don't make anonymous variables; make each names for the members.
- If you are writing a project that has limited use:
- You can use anonymous variables to hold spaces those are actually unused.
- Go's
syscallis powerful and modern to handle low-level calls