这篇文章主要介绍“如何使用go:linkname指令”,在日常操作中,相信很多人在如何使用go:linkname指令问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用go:linkname指令”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
//go:linkname local remote
remote 可以没有,此时 remote 使用 local 的值,效果就是 local 被导出。
local 作为占位符,remote 作为实现者
标准库中的例子:
// 来自 time 包 //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 // 来自 runtime 包 //go:nosplit func nanotime() int64 { return nanotime1() }
此时二进制文件中并没有runtimeNano,直接转化为对runtime.nanotime的调用。
local 作为实现者,remote 作为占位符
同样来自标准库。这里存在函数没有函数体,但是被反向引用。
// 在标准库的一个 internal 中 //go:linkname runtime_cmpstring runtime.cmpstring func runtime_cmpstring(a, b string) int { l := len(a) if len(b) < l { l = len(b) } for i := 0; i < l; i++ { c1, c2 := a[i], b[i] if c1 < c2 { return -1 } if c1 > c2 { return +1 } } if len(a) < len(b) { return -1 } if len(a) > len(b) { return +1 } return 0 } // 来自 runtime func cmpstring(string, string) int
此时二进制文件中并没有runtime_cmpstring,对应的函数已经被命名为runtime.cmpstring。也就是说,实现在 internal 包,但最终通过 runtime.cmpstring 来引用。
一个占位符+一个汇编函数
// 在标准库的一个 internal 中 //go:linkname abigen_runtime_memequal runtime.memequal func abigen_runtime_memequal(a, b unsafe.Pointer, size uintptr) bool
注意runtime.memequal的实现并不在runtime包中,使用汇编实现的话并不要求必须在相应的包中。
# memequal(a, b unsafe.Pointer, size uintptr) bool TEXT runtime·memequal(SB),NOSPLIT,$0-25 MOVQ a+0(FP), SI MOVQ b+8(FP), DI CMPQ SI, DI JEQ eq MOVQ size+16(FP), BX LEAQ ret+24(FP), AX JMP memeqbody<>(SB) eq: MOVB $1, ret+24(FP) RET
两个常规变量
//go:linkname overflowError runtime.overflowError var overflowError error //go:linkname divideError runtime.divideError var divideError error //go:linkname zeroVal runtime.zeroVal var zeroVal [maxZero]byte //go:linkname _iscgo runtime.iscgo var _iscgo bool = true //go:cgo_import_static x_cgo_setenv //go:linkname x_cgo_setenv x_cgo_setenv //go:linkname _cgo_setenv runtime._cgo_setenv var x_cgo_setenv byte var _cgo_setenv = &x_cgo_setenv //go:cgo_import_static x_cgo_unsetenv //go:linkname x_cgo_unsetenv x_cgo_unsetenv //go:linkname _cgo_unsetenv runtime._cgo_unsetenv var x_cgo_unsetenv byte var _cgo_unsetenv = &x_cgo_unsetenv
一个占位符+一个伪符号
//go:linkname runtime_inittask runtime..inittask var runtime_inittask initTask //go:linkname main_inittask main..inittask var main_inittask initTask
注意是..inittask不是.inittask,而且.inittask只存在于编译阶段,任何包中都无法声明该变量。
这里额外解释下 ..inittask 为什么两个点。第一个点就是普通的 runtime. 这种调用方式,第二个点和 inittask 一起构成一个符号(变量)。注意,Go 中的变量是不允许以 . 开头的,所以,这个叫伪符号,只在不编译阶段存在。
研究 //go:linkname 是因为如下的背景:
Java 里有 InheritableThreadLocal,SpringWeb 在 ServletActionContext 里使用它,达到在任何地方都能方便的获取HttpServletRequest。
Go 并没有提供类似的机制,即使通过 stack 找到 goroutine id(99% 的文章都是这么介绍的),再配合 sync.Map,也只是实现了一个比较粗糙的 ThreadLocal,在子协程里仍然获取不到父协程的内容。
g.label 虽然不是给这种场景准备的,但它具备了 InheritableThreadLocal 的一切要求,只要我们能够访问到 label 私有字段,我们就有了完整版的 InheritableThreadLocal。
下面这个例子是作者真实项目中用的。
在 runtime 和 runtime/pprof 包中有两个函数:runtime_setProfLabel 和 runtime_getProfLabel。其中,runtime 包中的提供了实现,而 pprof 中的没有提供实现。如果基于它们创建另外的函数,如下:
//go:linkname SetPointer runtime/pprof.runtime_setProfLabel func SetPointer(ptr unsafe.Pointer) //go:linkname GetPointer runtime/pprof.runtime_getProfLabel func GetPointer() unsafe.Pointer
根据前面的分析,虽然runtime.runtime_setProfLabel/runtime.runtime_getProfLabel提供了函数实现,但是二进制文件中并不会出现(见下方代码),此时想要调用必须通过runtime/pprof.runtime_setProfLabel/runtime/pprof.runtime_getProfLabel,这也是上面linkname到pprof而不是runtime的根本原因。
// 来自 runtime 包 //go:linkname runtime_setProfLabel runtime/pprof.runtime_setProfLabel func runtime_setProfLabel(labels unsafe.Pointer) { if raceenabled { racereleasemerge(unsafe.Pointer(&labelSync)) } getg().labels = labels } // 来自 runtime/pprof 包 func runtime_setProfLabel(labels unsafe.Pointer) // 来自 runtime 包 //go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel func runtime_getProfLabel() unsafe.Pointer { return getg().labels } // 来自 runtime/pprof 包 func runtime_getProfLabel() unsafe.Pointer
到此,关于“如何使用go:linkname指令”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。