在一般的用户线程上下文切换实现(如setjmp/longjmp
和function return
方式(中,我们保存和恢复被调用方保存的寄存器,但golang仅在gobuf中保存和恢复%rsp
,%rip
和%rbp
。
以x86_64为例,golang 使用 runtime.gosave 保存 goroutine 上下文,并使用 runtime.gogo 恢复 goroutine 上下文。
那么戈朗为什么要这样做呢?
显然,GoLang仍然使用低效的调用约定,其中唯一的调用保留(也称为非易失性(寄存器是RSP和RBP。
对runtime.gosave
的调用在编译器看来就像任何其他函数调用一样(即它最终在完成一些操作后返回,并且不会修改其自身堆栈帧上方的任何内容(。 与任何其他函数调用一样,调用方必须假定它销毁了所有调用破坏(易失性(寄存器(除 RSP 和 RBP 之外的所有内容(。 因此,它想要在调用中存活的任何值都必须溢出到堆栈插槽(或它们所属的其他内存位置(。
出于同样的原因,Csetjmp
只需要保存调用保留的寄存器。 内核上下文切换函数是相同的。
这篇 2017 年的谷歌群组帖子说这就是它的调用约定/ABI 的工作方式,从链接的代码来看,它仍然没有得到改进。
Go 的调用约定也低效地传递堆栈上的所有参数,不像 x86-64 System V ABI 在寄存器中传递前 6 个整数参数(和前 8 个 FP(。