背景
- 在看源碼時,一些源碼方法沒有方法體,難道說明這些方法爲空?例如:
time.Now
調用的now()
,time.Sleep
,reflect.makechan
// Provided by package runtime.
func now() (sec int64, nsec int32, mono int64)
func Sleep(d Duration)
func makechan(typ *rtype, size int) (ch unsafe.Pointer)
- 在寫代碼時,如果我們想使用別的包下沒有導出的方法或者變量時,怎麼操作
go:linkname 的用法
實際上,上述提到的三個沒有方法體的方法,其實現都在 src/runtime
包下
- time.now timestub.go 文件中
//go:linkname time_now time.now
func time_now() (sec int64, nsec int32, mono int64) {
sec, nsec = walltime()
return sec, nsec, nanotime()
}
- time.Sleep time.go 文件中
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
if ns <= 0 {
return
}
gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady
t.arg = gp
t.nextwhen = nanotime() + ns
if t.nextwhen < 0 { // check for overflow.
t.nextwhen = maxWhen
}
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}
//go:linkname reflect_makechan reflect.makechan
func reflect_makechan(t *chantype, size int) *hchan {
return makechan(t, size)
}
我們可以看到具體實現都具有 go:linkname
, 可以推測就是這個東西把兩個不同包下的函數鏈接到一起了
官方介紹
//go:linkname localname [importpath.name]
這個特殊的指令的作用域並不是緊跟的下一行代碼,而是同一個包下生效。//go:linkname
告訴 Go 的編譯器把本地的(私有)變量或者方法localname
鏈接到指定的變量或者方法importpath.name
。簡單來說,localname
import.name
指向的變量或者方法是同一個。因爲這個指令破壞了類型系統和包的模塊化原則,只有在引入 unsafe
包的前提下才能使用這個指令。
使用實例
文件結構如下圖所示, inner
包模擬 go 源碼包/外部引入包,outer
包需要調用inner
包中的方法, main.go
調試
tree
.
├── go.mod
├── inner
│ └── inner.go
├── main
├── main.go
└── outer
├── 1.s
└── outer.go
空body方法復現:
模擬方法無 body方法,真實實現在另外一個包中的案例
outer.go
package outer
import (
_ "github.com/he2121/demos/linkname_example/inner" // 真實方法在 inner 包,必須引用這個編譯器才能找到鏈接
)
func Hello()
如果出現:missing function body
在包內加一個 .s
文件。因爲go build
默認加會加上-complete
參數,加這個 .s 可繞開這個限制。
inner.go
package inner
import (
_ "unsafe"
)
//go:linkname hello github.com/he2121/demos/linkname_example/outer.Hello
func hello() {
println("hello")
}
main.go
package main
import "github.com/he2121/demos/linkname_example/outer"
func main() {
outer.Hello()
}
// output:
// hello
這就是 go 源碼中這麼多沒有 body 的方法的原因了,一般到 src/runtime
包中搜索就能發現 //go:linkname
的實現。
實際使用
實現中,如果我們有一些騷操作需要使用源代碼/第三方包中的未導出變量/方法,就可使用 //go:linkname
實現, 如下例子
outer.go: 使用 inner 包中的未導出變量/方法
package outer
import (
_ "unsafe"
_ "github.com/he2121/demos/linkname_example/inner"
)
//go:linkname A github.com/he2121/demos/linkname_example/inner.a
var A int
//go:linkname Hello github.com/he2121/demos/linkname_example/inner.hello
func Hello()
inner.go
package inner
var a = 100
func hello() {
println("hello")
}
main.go
package main
import "github.com/he2121/demos/linkname_example/outer"
func main() {
outer.Hello()
println(outer.A)
}
// output
// hello
// 100