这个库听说是哪个版本才加的?是用来加载插件的,简单使用了一下,似乎只能加载 go 自己编译的动态链接库,而且还有核心函数是在 runtime 实现的,这里只能看到表面的简单处理

plugin.go

首先是数据结构

  1. // src/plugin/plugin.go ---- line 20
  2. // Plugin is a loaded Go plugin.
  3. type Plugin struct {
  4. pluginpath string
  5. err string // set if plugin failed to load
  6. loaded chan struct{} // closed when loaded
  7. syms map[string]interface{}
  8. }
  9. // src/plugin/plugin.go ---- line 72
  10. type Symbol interface{}

Pluginloaded 用处挺大的,相当于是锁,但在这里还讲不太清楚,得看另一个文件的函数才知道。

plugin_dlopen.go

是基于 cgo 实现的噢,先贴出前面的 C 代码

  1. // src/plugin/plugin_dlopen.go ---- line 9
  2. /*
  3. #cgo linux LDFLAGS: -ldl
  4. #include <dlfcn.h>
  5. #include <limits.h>
  6. #include <stdlib.h>
  7. #include <stdint.h>
  8. #include <stdio.h>
  9. static uintptr_t pluginOpen(const char* path, char** err) {
  10. void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
  11. if (h == NULL) {
  12. *err = (char*)dlerror();
  13. }
  14. return (uintptr_t)h;
  15. }
  16. static void* pluginLookup(uintptr_t h, const char* name, char** err) {
  17. void* r = dlsym((void*)h, name);
  18. if (r == NULL) {
  19. *err = (char*)dlerror();
  20. }
  21. return r;
  22. }
  23. */

可以看出其实动态链接库的加载和其中 symbols 的查找都是通过 C 的函数实现的,然后 Go 负责调用和管理。


那么来看一下 Go 是怎么调用和管理的。

  1. // src/plugin/plugin_dlopen.go ---- line 42
  2. func open(name string) (*Plugin, error) {
  3. cPath := make([]byte, C.PATH_MAX+1)
  4. cRelName := make([]byte, len(name)+1)
  5. copy(cRelName, name)
  6. if C.realpath(
  7. (*C.char)(unsafe.Pointer(&cRelName[0])),
  8. (*C.char)(unsafe.Pointer(&cPath[0]))) == nil {
  9. return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
  10. }
  11. filepath := C.GoString((*C.char)(unsafe.Pointer(&cPath[0])))
  12. pluginsMu.Lock()
  13. if p := plugins[filepath]; p != nil {
  14. pluginsMu.Unlock()
  15. if p.err != "" {
  16. return nil, errors.New(`plugin.Open("` + name + `"): ` + p.err + ` (previous failure)`)
  17. }
  18. <-p.loaded
  19. return p, nil
  20. }
  21. var cErr *C.char
  22. h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr)
  23. if h == 0 {
  24. pluginsMu.Unlock()
  25. return nil, errors.New(`plugin.Open("` + name + `"): ` + C.GoString(cErr))
  26. }
  27. // TODO(crawshaw): look for plugin note, confirm it is a Go plugin
  28. // and it was built with the correct toolchain.
  29. if len(name) > 3 && name[len(name)-3:] == ".so" {
  30. name = name[:len(name)-3]
  31. }
  32. if plugins == nil {
  33. plugins = make(map[string]*Plugin)
  34. }
  35. pluginpath, syms, errstr := lastmoduleinit()
  36. if errstr != "" {
  37. plugins[filepath] = &Plugin{
  38. pluginpath: pluginpath,
  39. err: errstr,
  40. }
  41. pluginsMu.Unlock()
  42. return nil, errors.New(`plugin.Open("` + name + `"): ` + errstr)
  43. }
  44. // This function can be called from the init function of a plugin.
  45. // Drop a placeholder in the map so subsequent opens can wait on it.
  46. p := &Plugin{
  47. pluginpath: pluginpath,
  48. loaded: make(chan struct{}),
  49. }
  50. plugins[filepath] = p
  51. pluginsMu.Unlock()
  52. initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL
  53. copy(initStr, pluginpath)
  54. copy(initStr[len(pluginpath):], "..inittask")
  55. initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
  56. if initTask != nil {
  57. doInit(initTask)
  58. }
  59. // Fill out the value of each plugin symbol.
  60. updatedSyms := map[string]interface{}{}
  61. for symName, sym := range syms {
  62. isFunc := symName[0] == '.'
  63. if isFunc {
  64. delete(syms, symName)
  65. symName = symName[1:]
  66. }
  67. fullName := pluginpath + "." + symName
  68. cname := make([]byte, len(fullName)+1)
  69. copy(cname, fullName)
  70. p := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&cname[0])), &cErr)
  71. if p == nil {
  72. return nil, errors.New(`plugin.Open("` + name + `"): could not find symbol ` + symName + `: ` + C.GoString(cErr))
  73. }
  74. valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
  75. if isFunc {
  76. (*valp)[1] = unsafe.Pointer(&p)
  77. } else {
  78. (*valp)[1] = p
  79. }
  80. // we can't add to syms during iteration as we'll end up processing
  81. // some symbols twice with the inability to tell if the symbol is a function
  82. updatedSyms[symName] = sym
  83. }
  84. p.syms = updatedSyms
  85. close(p.loaded)
  86. return p, nil
  87. }
  88. // src/plugin/plugin_dlopen.go ---- line 144
  89. var (
  90. pluginsMu sync.Mutex
  91. plugins map[string]*Plugin
  92. )
  93. // lastmoduleinit is defined in package runtime
  94. func lastmoduleinit() (pluginpath string, syms map[string]interface{}, errstr string)
  95. // doInit is defined in package runtime
  96. //go:linkname doInit runtime.doInit
  97. func doInit(t unsafe.Pointer) // t should be a *runtime.initTask

逐块分析一下这个函数。以下的行号都是指上述代码块的行号。


3 - 12 行是调用 C 的 realpath 函数将参数的相对路径转换成绝对路径(这个功能不是直接 Go 也可以完成吗,为啥还要调 C 的,难道其中有什么奥秘?当然我没调试过没有发言权)。


14 - 22 行是检查当前要加载的这个 plugin 在 plugins 中是否已经存在,如果已存在且没有错误且已加载完成,就直接返回,避免重复加载。注意怎么判断已加载完成的,如果没有加载完成那么第 20 行会一直阻塞,直到插件加载完成在第 93 关闭这个 channel,第 20 行才会成功读到一个空值,成功返回已加载的插件。所以说这个加载过程,其实是并发安全的。


37 行调用了一个 runtime 中的函数。这个函数的具体实现也不清楚,得等到看 runtime 的时候再看,在测试的时候,如果加载的不是 Go 的动态链接库就会在这个函数直接 fatal error(至少加载 C 的动态链接库确实直接 fatal error 了)


如果 37 行没有 panic,那基本就是加载成功了,于是接下来的代码就是找出链接库中所有的 symbols 并且保存在一个 map 中。全部完成后在 93 行关闭 channel,于是重复加载也可以正确返回了。

test

似乎 plugin_test.go 中并没有实际的测试代码。于是就给出自己写的简单的测试吧

  1. // add.go
  2. // go build --buildmode=plugin -o add.so add.go
  3. package main
  4. import "fmt"
  5. func Add(a, b int) int {
  6. fmt.Printf("In Add: a = %d, b = %d, a + b = %d\n", a, b, a+b)
  7. return a+b
  8. }

需要注意的是 package main,否则在编译时会得到以下报错 :::warning -buildmode=plugin requires exactly one main package :::

  1. // main.go
  2. // go run main.go
  3. package main
  4. import (
  5. "plugin"
  6. )
  7. func main() {
  8. plg, err := plugin.Open("add.so")
  9. if err != nil {
  10. panic(err)
  11. }
  12. if sym, err := plg.Lookup("Add"); err == nil {
  13. if f, ok := sym.(func(int, int) int); ok {
  14. result := f(3, 4)
  15. println("3 + 4 =", result)
  16. }
  17. } else {
  18. panic(err)
  19. }
  20. }