
本文旨在深入解析 Go 语言 `syscall` 包中的 `RawSyscall` 和 `Syscall` 函数。我们将详细解释 `RawSyscall` 的参数和返回值,解读其汇编实现的关键部分,并阐明 `Syscall` 与 `RawSyscall` 的本质区别。此外,本文还将指导开发者在需要自定义系统调用时,如何选择和使用这两个函数。
RawSyscall 函数详解
RawSyscall 函数是 Go 语言中直接进行系统调用的底层接口。其函数签名如下:
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
- trap: 系统调用号。每个操作系统都定义了一组系统调用,每个调用都有一个唯一的编号。trap 参数指定了要执行的系统调用的编号。
- a1, a2, a3: 系统调用的参数。大多数系统调用都需要一些输入参数。a1、a2 和 a3 分别代表系统调用的前三个参数。如果系统调用需要的参数超过三个,则需要通过其他方式传递(例如,通过指针传递结构体)。
- r1, r2: 系统调用的返回值。系统调用执行完成后,通常会返回一些结果。r1 和 r2 分别代表系统调用的前两个返回值。
- err: 错误码。如果系统调用执行失败,则 err 返回一个 Errno 类型的值,表示错误码。
以下是 RawSyscall 在 darwin/amd64 架构下的汇编代码片段:
TEXT ·RawSyscall(SB),7,$0
MOVQ 16(SP), DI
MOVQ 24(SP), SI
MOVQ 32(SP), DX
MOVQ $0, R10
MOVQ $0, R8
MOVQ $0, R9
MOVQ 8(SP), AX // syscall entry
ADDQ $0x2000000, AX
SYSCALL
JCC ok1
MOVQ $-1, 40(SP) // r1
MOVQ $0, 48(SP) // r2
MOVQ AX, 56(SP) // errno
RET
ok1:
MOVQ AX, 40(SP) // r1
MOVQ DX, 48(SP) // r2
MOVQ $0, 56(SP) // errno
RET- TEXT ·RawSyscall(SB),7,$0: 定义了 RawSyscall 函数的入口点。
- MOVQ 16(SP), DI 等: 将参数从栈 (SP) 中移动到对应的寄存器中。DI, SI, DX 是通用寄存器,用于传递系统调用的参数。
- MOVQ 8(SP), AX: 将系统调用号从栈中移动到 AX 寄存器。AX 寄存器用于指定系统调用号。
- ADDQ $0x2000000, AX: 在 macOS 上,系统调用号需要加上 0x2000000 的偏移量。
- SYSCALL: 执行系统调用指令。
- JCC ok1: 如果系统调用成功,则跳转到 ok1 标签。JCC (Jump if Carry Clear) 是一个条件跳转指令,它会根据 CPU 的标志位来决定是否跳转。
- MOVQ AX, 40(SP) 等: 将返回值从寄存器移动到栈中,以便 Go 代码可以访问它们。
- RET: 从函数返回。
ok1: 标签
ok1: 标签是一个代码标签,用于标记代码中的一个位置。在上面的汇编代码中,JCC ok1 指令会根据系统调用的执行结果跳转到 ok1 标签。如果系统调用成功,则跳转到 ok1 标签,并将返回值存储到栈中。如果系统调用失败,则不跳转到 ok1 标签,而是执行后面的代码,将错误码存储到栈中。
Syscall 与 RawSyscall 的区别
Syscall 和 RawSyscall 的主要区别在于 Syscall 会调用 runtime·entersyscall(SB) 和 runtime·exitsyscall(SB),而 RawSyscall 不会。
- runtime·entersyscall(SB): 通知 Go 运行时系统,当前 Goroutine 即将进入一个阻塞的系统调用。这允许 Go 运行时系统将 CPU 时间片让给其他 Goroutine,从而提高程序的并发性。
- runtime·exitsyscall(SB): 通知 Go 运行时系统,当前 Goroutine 已经从系统调用返回。
因此,Syscall 适用于那些可能阻塞的系统调用,而 RawSyscall 适用于那些不会阻塞的系统调用或者对性能要求非常高的场景。
何时使用 Syscall 和 RawSyscall
- Syscall: 当你需要执行一个可能阻塞的系统调用时,例如文件 I/O、网络 I/O 等。
- RawSyscall: 当你需要执行一个不会阻塞的系统调用,或者对性能要求非常高,并且你确信该系统调用不会阻塞时。例如,一些底层的硬件操作。
zsyscall 的含义
在 syscall 包中,以 zsyscall 开头的文件名通常表示该文件是自动生成的,用于提供特定操作系统和架构的系统调用实现。这些文件通常由 go tool cgo 工具根据 C 头文件自动生成。
编写自定义系统调用函数
如果你需要使用 Go 语言调用一些操作系统提供的,但 syscall 包没有封装的系统调用,你可以使用 RawSyscall 或 Syscall 函数。
示例
以下是一个使用 RawSyscall 调用 getpid 系统调用的示例:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func Getpid() (pid int) {
pid, _, _ = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
return
}
func main() {
pid := Getpid()
fmt.Println("Process ID:", pid)
}注意事项
- 在编写自定义系统调用函数时,需要仔细查阅操作系统的文档,了解系统调用的参数和返回值。
- 需要注意不同操作系统和架构的系统调用号可能不同。
- 使用 RawSyscall 时,需要确保系统调用不会阻塞,否则可能会导致整个程序hang住。建议优先使用 Syscall。
- 系统调用属于底层操作,务必谨慎使用,避免出现安全漏洞或程序崩溃。
总结
RawSyscall 和 Syscall 是 Go 语言中进行系统调用的两个底层接口。Syscall 会通知 Go 运行时系统,当前 Goroutine 即将进入一个阻塞的系统调用,而 RawSyscall 不会。因此,Syscall 适用于那些可能阻塞的系统调用,而 RawSyscall 适用于那些不会阻塞的系统调用或者对性能要求非常高的场景。 在编写自定义系统调用函数时,需要仔细查阅操作系统的文档,了解系统调用的参数和返回值,并谨慎使用。










