Go syscall is a brilliant replacement of low-level I/O
Summary
We will learn about direct system call on Go. 我们将探讨 Go 语言中的直接系统调用(direct system call)。 Since Go is offering strict compiler errors and rigid GC, it is much better to replace low-level calls in Pure Go. 鉴于 Go 语言提供了严格的编译器错误检查和固定的 GC(Garbage Collection,垃圾回收)机制,在 Pure Go(纯 Go 语言)中替代低层级调用是更优的选择。 Luckily, most of the C function calls are fully reimplemented in Go, in a good and contemporary manner. 幸运的是,大多数 C 语言函数调用已以良好且现代的方式在 Go 语言中得到了完全重新实现。 Let's take a look at it. 让我们对此进行考察。
System Call
System call is a direct request to the operating system. 系统调用(System call)是对操作系统发出的直接请求。 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.syscall.SYS_SYSINFO 是打开内核内部一个“储物柜”(locker)的钥匙。
Therefore, using correct key for a locker is important.
因此,为储物柜使用正确的钥匙至关重要。
What will happen when we use syscall.SYS_GETPID for this call?
如果我们在这次调用中使用 syscall.SYS_GETPID 会发生什么?
This is a key for a locker that contains Process ID.
这是包含进程 ID(Process ID)的储物柜的钥匙。
This will attempt to get a PID from a space for system information.
这将试图从系统信息的空间中获取一个 PID。
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. 在储物柜的第一个槽位中,我们有 Uptime(系统运行时间),其大小为 2^64。 If we try to read this with 2^32, the bit sequence is not fully read. 如果我们尝试用 2^32 的大小来读取它,则位序列将不会被完全读取。 We cannot use these kinds of partial binaries unless we are going to write low-level tricks. 除非我们打算编写低层级技巧(low-level tricks),否则我们不能使用这种部分二进制数据。
After reading 64 bits of binary data, finally we are on a second slot. 在读取了 64 位的二进制数据之后,我们最终到达了第二个槽位。 It can only be read accurately when we have read previous 64-bit sized integer. 只有当我们读取了前一个 64 位大小的整数后,它才能被准确读取。
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. 尽管我们不能“跳过”(skip)变量本身,但区分已使用的变量和被舍弃的变量是很重要的。 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" 我们通过一个示例“空闲内存检查器”(Free Memory Checker)来验证这一点。
Example - Free Memory Checker
When checking Free Memory/Swaps, we don't need other information that is indicating different resources. 在检查空闲内存/交换空间(Free Memory/Swaps)时,我们不需要指示不同资源的其他信息。 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 - 使用 Go 语言的
syscall和unsafe仍然比 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 - Go 语言的
syscall强大且现代化,能够处理低层级调用。