Go syscall is a brilliant replacement of low-level I/O
by
gg582 · 2026-05-25 01:53:10 · 23 views
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
package main
import (
"fmt"
"syscall"
"unsafe"
)
type sysinfo_t struct {
Uptime int64
Loads [3]uint64
Totalram uint64
Freeram uint64
Sharedram uint64
Bufferram uint64
Totalswap uint64
Freeswap uint64
Procs uint16
Pad uint16
_ [4]byte
Totalhigh uint64
Freehigh uint64
MemUnit uint32
_ [4]byte
}
func main() {
var info sysinfo_t
_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
if errno != 0 {
fmt.Println("sysinfo syscall failed:", errno)
return
}
scale := float64(1 << 16)
fmt.Printf("Uptime: %d seconds\n", info.Uptime)
fmt.Printf("Load Average: %.2f %.2f %.2f\n",
float64(info.Loads[0])/scale,
float64(info.Loads[1])/scale,
float64(info.Loads[2])/scale)
fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
info.Totalram*uint64(info.MemUnit)/1024/1024,
info.Freeram*uint64(info.MemUnit)/1024/1024,
info.Bufferram*uint64(info.MemUnit)/1024/1024)
fmt.Printf("Swap: total=%d MB free=%d MB\n",
info.Totalswap*uint64(info.MemUnit)/1024/1024,
info.Freeswap*uint64(info.MemUnit)/1024/1024)
fmt.Printf("Processes: %d\n", info.Procs)
}
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 2^64. If we try to read this with 2^32, the bit sequence is not fully read. 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.
package main
import (
"fmt"
"syscall"
"unsafe"
)
type sysinfo_t struct {
_ int64
_ [3]uint64
Totalram uint64
Freeram uint64
Sharedram uint64
Bufferram uint64
Totalswap uint64
Freeswap uint64
_ uint16 // anonymous, and unused ones are marked as _
_ uint16
_ [4]byte
_ uint64
_ uint64
MemUnit uint32
_ [4]byte
}
func main() {
var info sysinfo_t
_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
if errno != 0 {
fmt.Println("sysinfo syscall failed:", errno)
return
}
fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
info.Totalram*uint64(info.MemUnit)/1024/1024,
info.Freeram*uint64(info.MemUnit)/1024/1024,
info.Bufferram*uint64(info.MemUnit)/1024/1024)
fmt.Printf("Swap: total=%d MB free=%d MB\n",
info.Totalswap*uint64(info.MemUnit)/1024/1024,
info.Freeswap*uint64(info.MemUnit)/1024/1024)
}
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