环境说明
OS:linux
ARCH:amd64
GOVERSION:1.14.1
syscall实现
在linux下golang调用syscall的接口,文件路径syscall/syscall_unix.go
1 | func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) |
这些函数的实现都是汇编,按照 linux 的 syscall 调用规范,我们只要在汇编中把参数依次传入寄存器,并调用 SYSCALL 指令即可进入内核处理逻辑,系统调用执行完毕之后,返回值放在 RAX 中:
| RDI | RSI | RDX | R10 | R8 | R9 | RAX |
|---|---|---|---|---|---|---|
| 参数一 | 参数二 | 参数三 | 参数四 | 参数五 | 参数六 | 系统调用编号/返回值 |
这些函数的底层实现都是汇编代码,文件路径syscall/asm_linux_amd64.s
1 | TEXT ·Syscall(SB),NOSPLIT,$0-56 |
可以看到Syscall和Syscall6函数没有区别,只是在传参的个数上有区别,且在开始系统和结束系统调用时会调用runtime·entersyscall(SB)和runtime·exitsyscall(SB),这样可以让系统调用和runtime进行沟通,让runtime进行调度当前正在调用syscall的g。
1 | // func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) |
RawSyscall和RawSyscall6函数也只是只有传参数目的不同,但是Syscall和RawSyscall的区别在于没有调用runtime·entersyscall(SB)和runtime·exitsyscall(SB),这样 runtime 理论上是没有办法通过调度把这个 g 的 m 的 p 调度走的,所以如果用户代码使用了 RawSyscall 来做一些阻塞的系统调用,是有可能阻塞其它的 g 的,下面是官方开发的原话:
Yes, if you call RawSyscall you may block other goroutines from running. The system monitor may start them up after a while, but I think there are cases where it won’t. I would say that Go programs should always call Syscall. RawSyscall exists to make it slightly more efficient to call system calls that never block, such as getpid. But it’s really an internal mechanism.
syscall管理
golang实现了部分系统调用,定义在syscall/syscall_linux.go中。
可以把系统调用分为三类:
- 阻塞系统调用
- 非阻塞系统调用
- wrapped 系统调用
这里截取部分代码注释
1 | //sys Setpriority(which int, who int, prio int) (err error) |
其中有//sys表示阻塞的系统调用,//sysnb表示非阻塞系统调用。然后,根据这些注释,mksyscall.pl 脚本会生成对应的平台的具体实现。mksyscall.pl 是一段 perl 脚本,感兴趣的同学可以自行查看,这里就不再赘述了。生成的代码前面会有一段// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT的注释。
如果是标记为阻塞的系统调用生成的代码是调用Syscall和Syscall6,标记为非阻塞的系统调用会RawSyscall和RawSyscall6。
wrapped系统调用是封装了一层系统调用,可能是觉得命名风格不是很golang。
runtime中的syscall
除了上面提到的阻塞非阻塞和 wrapped syscall,runtime 中还定义了一些 low-level 的 syscall,这些是不暴露给用户的。
提供给用户的 syscall 库,在使用时,会使 goroutine 和 p 分别进入 Gsyscall 和 Psyscall 状态。但 runtime 自己封装的这些 syscall 无论是否阻塞,都不会调用 entersyscall 和 exitsyscall。虽说是 “low-level” 的 syscall,不过和暴露给用户的 syscall 本质是一样的。
runtime定义的系统调用列表,定义在runtime/sys_linux_arm64.s中。
1 | #define SYS_exit 93 |
这些 syscall 理论上都是不会在执行期间被调度器剥离掉 p 的,所以执行成功之后 goroutine 会继续执行,而不像用户的 goroutine 一样,若被剥离 p 会进入等待队列。
调度交互
既然要和调度交互,那友好地通知我要 syscall 了: entersyscall,我完事了: exitsyscall。
所以这里的交互指的是用户代码使用 syscall 库时和调度器的交互。runtime 里的 syscall 不走这套流程。
文件路径runtime/proc.go:2974
1 | // Standard syscall entry used by the go syscall library and normal cgo calls. |
1 | //go:nosplit |