为什么 Golang 在切换上下文时只保存 %rsp、%rip 和 %rbp(没有其他被调用者保存的寄存器)?

Why does Golang only save %rsp, %rip and %rbp(no other callee-saved registers) when switch context?

在一般的用户线程上下文切换实现中(如setjmp/longjmpfunction return方式),我们保存和恢复被调用者保存的寄存器,但golang只保存和恢复%rsp , %rip%rbpgobuf 中。

以x86_64为例,golang将goroutine上下文保存为runtime.gosave and restore goroutine context with runtime.gogo

那么golang为什么要这样做呢?

显然 GoLang 仍然使用低效的调用约定,其中 唯一的调用保留(也称为非易失性)寄存器是 RSP 和 RBP。

runtime.gosave 的调用在编译器看来就像任何其他函数调用一样(即它最终 returns 在做了一些事情之后,并且不会修改其自身堆栈框架之上的任何内容)。与任何其他函数调用一样,调用者必须假定它会破坏所有被调用破坏的(易失性)寄存器(除了 RSP 和 RBP 之外的所有寄存器)。因此,它希望在调用中存活的任何值都必须溢出到堆栈槽(或它们所属的其他内存位置)。

同理,Csetjmp只需要保存调用保留寄存器即可。和内核上下文切换功能相同。


这个 2017 google groups post 说这就是它的调用约定/ABI 的工作方式,从链接的代码来看,它看起来仍然没有改进。

Go 的调用约定也低效地传递堆栈上的所有参数,这与 x86-64 System V ABI 不同,它在寄存器中传递前 6 个整数参数(和前 8 个 FP)。