首发于:https://studygolang.com/articles/12134
在 golang 中如何调用私有函数(绑定隐藏的标识符)
2016 年 4 月 28 日
名字在 golang 中的重要性和在其他任何一种语言是一样的。他们甚至含有语义的作用:在一个包的外部某个名字的可见性是由这个名字首字母是否是大写来决定的。
有时为了更好的组织代码或者在其他包使用某些隐藏的函数时需要克服这种限制。
在过去美好的日子,有 2 种实现方式,它们能绕过编译器的检查:不能引用未导出的名称 pkg.symbol :
旧的方式,现在已经不再使用 - 汇编级隐式连接到所需符号,称为 assembly stubs ,详见 go runtime, os/signal: use //go:linkname instead of assembly stubs to get access to runtime functions 。
现行的方式 - go 编译器通过 go:linkname 支持名称重定向,引用于 11.11.14 dev.cc code review 169360043: cmd/gc: changes for removing runtime C code (issue 169360043 by r…@golang.org) ,在 github.com 的 issue 上有可以找到 cmd/compile: “missing function body” error when using the //go:linkname compiler directive #15006 。
用这些技巧我曾设法绑定 golang 运行时调度器相关的函数用以减少过度使用 go 的协程和内部锁机制导致的 gc 停顿。
使用 assembly stubs
想法很简单 - 为需要的标识符提供直接跳转汇编指令 stubs 。链接器并不知道标识符是否已导出。
详见旧版的代码 src/os/signal/sig.s :
而 signal_unix.go 的绑定如下:
使用 go:linkname
为了使用这种方法,代码中必须 包。为了解决 go 编译器 参数的限制,一种可能的方法是在 main 包目录加一个空的汇编 stub 文件以禁用编译器的检查。
详见 os/signal/sig.s:
这个指令的格式是 。使用这种方法可以将新的标识符链接(导出)或绑定到已存在的标识符(导入)。
用 go:linkname 导出
在 runtime/proc.go 中一个函数的实现
这里明确地向编译器指示,将另一个名字添加到 sync 包的 runtime_doSpin 函数代码中。并且 sync 包简单的重用了在 sync/runtime.go 中的代码:
用 go:linkname 导入
在 net/parse.go 中有一个很好的例子:
使用这种技巧的方法:
import _ “unsafe” 包。
提供一个没有函数体的函数,比如: func byteIndex(s string, c byte) int
在定义函数前,正确的放上 //go:linkname 指令,例如 //go:linkname byteIndex strings.IndexByte,byteIndex 是本地名称,strings.IndexByte 是远程名称。
提供 .s 后缀的 stub 文件,以便编译器绕过 -complete 的检查,允许不完整的函数定义(译注:指没有函数体)。
例子 goparkunlock
源码
可在这里获取 https://github.com/sitano/gsysint 。
领取专属 10元无门槛券
私享最新 技术干货