go:linkname

简介: go:linkname

初入门径


查看一些官方库的源码时,经常发现找不到其具体的实现,以常用的time.Sleep()为例:

// Sleep至少在持续时间d内暂停当前goroutine,
// 持续时间为负数或零会导致Sleep立即返回
// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)

但该方法的实现在哪里? 寻遍time包,也不见Sleep的实现.

其实其实现在 runtime/time.go:

// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
// timeSleep使当前的goroutine睡眠至少ns纳秒。
//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
  gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}

go:linkname是源码中最常用的Go指令,其官方定义如下:

//go:linkname localname [importpath.name]
This special directive does not apply to the Go code that follows it. Instead, the //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. If the “importpath.name” argument is omitted, the directive uses the symbol's default object file symbol name and only has the effect of making the symbol accessible to other packages. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".

这个指令告诉编译器 为当前源文件中的私有函数或者变量在编译时链接到指定的方法或变量。

因为这个指令破坏了类型系统和包的模块化,因此在使用时必须导入unsafe包.

可以看到runtime/time.go文件是有导入unsafe包的

微信截图_20230627112031.png

对于//go:linkname localname [importpath.name], 这里localname对应timeSleep, importpath.name 对应time.Sleep

但为什么要这么做?

这是因为timeSleepruntime包里,是不可导出的. 使用go:linkname的目的,就是让time包,可以调用runtime包中原本不可导出的方法




Demo实验


新建demo文件夹,结构如下:

➜  tree
.
├── linkname
│   └── a.go
├── main.go
└── outer
    └── world.go
2 directories, 3 files

a.go:

package linkname
import _ "unsafe"
//go:linkname hello demo/outer.World
func hello() {
  println("hello,world!")
}

world.go:

package outer
import _"demo/linkname"
func World()

main.go:

package main
import "demo/outer"
func main() {
  outer.World()
}

执行 go run main.go,会出现报错:

main.go:3:8: package note/demo/outer is not in GOROOT (/usr/local/opt/go@1.14/libexec/src/note/demo/outer)

参考报错package xxx is not in GOROOT, 将demo文件夹放到GOPATH下:

➜  src echo $GOPATH
/Users/dashen/go
// 将demo文件夹移动到GOPATH下
➜  pwd
/Users/dashen/go/src/demo

执行 go run main.go:

报错如下:

# demo/outer
outer/world.go:5:6: missing function body

这是因为go build默认加会加上-complete参数,这个参数检查到**World()**没有方法,从而抛出如上错误

可以在outer文件夹中增加一个空的.s文件即可绕过这个限制(也可以选择用单独的compile命令进行编译):

➜  demo tree
.
├── linkname
│   └── a.go
├── main.go
└── outer
    ├── i.s
    └── world.go
2 directories, 4 files

执行 go run main.go:

hello,world!




总结&注意事项

微信截图_20230627112417.png

linkname注解非常广泛地应用于Go的源码中, 其指令的格式如下:

//go:linkname hello(具体的实现) demo/outer.World(导出用到哪个地方)

  • //后面不能有空格
  • //go:linkname xxx xxxxx 必须在具体实现的正上方,之间不能有空行

go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量,第一个参数表示当前方法或变量,第二个参数表示目标方法或变量,因为这关指令会破坏系统和包的模块化,因此在使用时必须导入unsafe

该指令不经常用(最好在业务开发中也不要用),但了解这个指令可以帮助理解核心包的很多代码. 在标准库中,为了可以使用另一个包的unexported的方法或者变量,正常情况这些unexported资源是不可包外访问的,但是运行时用这个命令hack一下,就变得可以访问

目录
相关文章
|
5月前
|
存储 JSON JavaScript
GO中`gjson`的应用和分享
GO中`gjson`的应用和分享
|
7月前
|
人工智能 网络协议 Java
|
8月前
|
Java Go
Go的forcegc
go中的强制垃圾回收
44 0
|
8月前
|
JSON Go 数据格式
Go slog
Go slog
123 0
Go slog
|
9月前
|
存储 数据采集 XML
GO中 gjson 的应用和分享
咱们上次分享到使用 GO 爬取静态网页的数据,一起来回顾一下 • 分享静态网页和动态网页的简要说明 • GO 爬取静态网页简单数据 • GO 爬取网页上的图片 • 并发爬取网页上的资源
GO中 gjson 的应用和分享
|
9月前
|
安全 Java 编译器
Go到底能不能实现安全的双检锁?1
Go到底能不能实现安全的双检锁?1
69 0
|
9月前
|
缓存 安全 编译器
Go到底能不能实现安全的双检锁?2
Go到底能不能实现安全的双检锁?2
53 0
|
11月前
|
存储 JSON 安全
关于Go你不得不知道的小技巧2
关于Go你不得不知道的小技巧2
76 0
|
11月前
|
安全 编译器 Go
关于Go你不得不知道的小技巧1
关于Go你不得不知道的小技巧
57 0
|
安全 Java Go
Go | 讲解GOROOT、GOPATH、GOBIN
Go | 讲解GOROOT、GOPATH、GOBIN
173 0