syscall
是 Go 语言标准库中提供的一个用于调用底层操作系统 API 的包。它支持多种操作系统,包括 Linux、Windows、Darwin 等。我们可以使用syscall
包来实现一些底层的系统功能,如进程管理、信号处理、文件操作等。
在开始介绍go sys call 库之前先介绍下Linux syscall的几个概念
Linux syscall(系统调用)
Linux syscall(系统调用)是一种Linux内核提供的编程接口,允许应用程序直接请求操作系统核心的服务,例如读写文件、网络通信、进程管理等。。在Linux中,系统调用是应用程序与操作系统之间进行交互的主要方法之一,也是编写底层系统软件、优化性能和增强安全性的重要手段。
用户态与内核态
在Linux操作系统中,内核是操作系统的核心部分,用于管理计算机硬件、进程调度、内存管理等。为了保证内核运行的稳定性和安全性,Linux采用了一种特殊的运行模式:用户态和内核态。
用户态是指应用程序运行的环境,应用程序只能访问自己的内存空间和系统资源,不能直接访问操作系统内核,必须通过系统调用来请求内核执行操作。在用户态中,CPU访问内存的地址是虚拟地址,操作系统会将虚拟地址映射到物理地址上。
内核态是指操作系统内核运行的环境,具有更高的权限和更广泛的访问权限。在内核态中,CPU访问内存的地址是物理地址,操作系统可以直接访问硬件资源。操作系统内核运行时处于内核态。
系统调用类型
在Go语言中,syscall库支持的系统调用类型与Linux syscall(是一种Linux内核提供的编程接口,允许应用程序直接请求操作系统核心的服务)类似,该包中的每个函数都直接映射到相应的Linux系统调用。由于Go语言具有跨平台的优势,因此syscall包在各种平台上都可以使用。主要包括以下几类:
- 进程控制:创建、终止、等待进程、设置进程优先级等。
- 文件操作:打开、读取、写入文件等。
- 网络操作:建立、连接、断开网络连接等。
- 内存管理:申请、释放内存等。
- 时间管理:获取、设置系统时间等。
- 设备操作:读写设备、设置设备参数等。
我们写一些demo 来实际体验下这个包的应用
使用 syscall
包实现了以下功能:
syscall.Getpid():获取当前进程的进程 ID。
syscall.Getuid():获取当前进程的用户 ID。
syscall.Getgid():获取当前进程的组 ID。
syscall.Getpagesize():获取系统的页大小。
syscall.Open():打开一个文件。
syscall.Close():关闭一个文件。
syscall.Read():读取一个文件的内容。
代码语言:javascript复制syscall_demo.go
代码语言:javascript复制
代码语言:javascript复制package main
import (
"fmt"
"syscall"
)
func main() {
// 获取进程 ID
pid := syscall.Getpid()
fmt.Println("Process ID:", pid)
// 获取进程用户 ID
uid := syscall.Getuid()
fmt.Println("User ID:", uid)
// 获取进程组 ID
gid := syscall.Getgid()
fmt.Println("Group ID:", gid)
// 获取系统页大小
pagesize := syscall.Getpagesize()
fmt.Println("Page size:", pagesize)
// 打开一个文件
fd, err := syscall.Open("/etc/hosts", syscall.O_RDONLY, 0)
if err != nil {
fmt.Println("Failed to open file:", err)
return
}
defer syscall.Close(fd)
// 读取文件内容
//声明并初始化一个 byte 类型的切片(slice),其长度为 1024 个字节。这个切片用于存储从文件中读取的内容。
buf := make([]byte, 1024)
//使用 syscall 包中的 Read 函数从文件中读取数据,并将结果存储在 buf 变量中。Read 函数接受两个参数:文件描述符(fd)和一个 byte 类型的切片(buf),它返回读取的字节数(n)和一个可能出现的错误(err)。这里的 fd 是之前打开文件得到的文件描述符。
n, err := syscall.Read(fd, buf)
if err != nil {
fmt.Println("Failed to read file:", err)
return
}
fmt.Printf("Read %d bytes from file:n%sn", n, buf[:n])
}
代码语言:javascript复制
执行结果
代码语言:javascript复制
代码语言:javascript复制go run syscall_demo.go
pid : 135442
uid : 0
gid : 0
pagesize : 4096
read 39 bytes from file :
test
test1
let us use syscall
bingo!!!
代码语言:javascript复制
在使用 syscall
包时,我们需要注意一些与底层操作系统相关的细节。例如,文件描述符(fd
)在 Unix 系统中是一种重要的概念,我们需要使用 syscall.Open()
函数打开一个文件,并返回一个文件描述符。使用完文件描述符后,我们需要使用 syscall.Close()
函数将其关闭。在读取文件时,我们需要使用 syscall.Read()
函数,而不是标准库中的 io.Read()
函数。
同时,由于不同操作系统支持的功能不同,我们在使用 syscall
包时需要根据实际情况进行调用。例如,Windows 系统中可能不支持某些功能,或者实现方式与 Unix 系统中不同。
使用syscall 对系统调用的优化
使用syscall.Mmap()提高文件I/O性能
与Linux syscall类似,使用syscall.Mmap()可以将文件映射到进程的虚拟内存中,减少I/O操作次数,提高I/O性能
demo code
代码语言:javascript复制
代码语言:javascript复制package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// 打开文件
file, err := os.OpenFile("syscall_mmap.txt", os.O_RDWR, 0666)
if err != nil {
panic(err)
}
defer file.Close()
// 将文件映射到进程的虚拟内存中
//使用 syscall.Mmap() 函数将文件 "yscall_mmap.txt" 的前 1024 字节映射到进程的虚拟内存中,并设置映射的权限为 PROT_READ|PROT_WRITE,映射的方式为 MAP_SHARED,表示映射后的文件内容可以被多个进程共享。
data, err := syscall.Mmap(int(file.Fd()), 0, 1024, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
panic(err)
}
//在函数结束前调用 syscall.Munmap() 函数释放映射后的内存。
defer syscall.Munmap(data)
// 修改映射后的文件内容
//修改映射后的文件内容,将 "hi go syscall mmap" 的字节序列复制到映射后的内存中。因为映射后的内存已经和文件关联,所以修改内存的内容也会修改文件的内容
copy(data, []byte("hi go syscall mmap"))
fmt.Println(string(data))
}
代码语言:javascript复制
运行
代码语言:javascript复制
代码语言:javascript复制echo "hi go syscall mmap" >syscall_mmap.txt
echo "Learn SRE in five minutes , become an expert" >>syscall_mmap.txt
go run syscal_mmap.go
代码语言:javascript复制
打印
代码语言:javascript复制
代码语言:javascript复制hi go syscall mmapp
Learn SRE in five minutes , become an expert
代码语言:javascript复制
常见的gin框架就使用到了syscall.Mmap()进行优化,Gin 框架使用了 x/net/trace 包来对 HTTP 请求进行跟踪,而在跟踪时需要将请求和响应的信息写入文件,因此 Gin 框架使用 syscall.Mmap() 将日志文件映射到进程的虚拟内存中,以提高日志文件的写入性能。
使用 syscall.Poll() 提高网络 I/O 性能
syscall.Poll() 函数可以用来等待文件描述符上的事件。在进行网络 I/O 时,我们可以使用 syscall.Poll() 函数来等待网络事件,从而避免了频繁的系统调用,提高了网络 I/O 的性能。
gnet 是一个基于 Reactor 模式的高性能网络库,它使用 syscall.Poll() 函数来等待网络事件,并使用 Goroutine 来处理网络事件。这种方式可以显著提高网络 I/O 的性能。
使用 syscall.Syscall() 调用系统调用
syscall.Syscall() 函数可以用来直接调用操作系统提供的系统调用。在进行一些底层操作时,我们可以使用 syscall.Syscall() 函数来直接调用系统调用,从而避免了一些高层的封装,提高了程序的性能。
containerd 是一个面向容器的运行时环境,它需要进行一些底层的操作,例如创建进程、挂载文件系统等。在进行这些操作时,containerd 使用 syscall.Syscall() 函数直接调用系统调用,从而避免了一些高层的封装,提高了程序的性能。