首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >golang plugin源码分析

golang plugin源码分析

作者头像
golangLeetcode
发布2022-08-02 19:00:40
发布2022-08-02 19:00:40
1.2K0
举报

什么是Go Plugin

Golang是静态编译型语言,在编译时就将所有引用的包(库)全部加载打包到最终的可执行程序(或库文件)中,因此并不能在运行时动态加载其他共享库。Go Plugin提供了这样一种方式,能够让你在运行时动态加载外部功能。go在1.8 支持了这个功能,类似c语言的动态链接库。

为什么用Go Plugin

其实应该问为什么要用Plugin,我觉得原因有很多,比如:

可插拔:有了Plugin,我的程序可以根据需要随时替换其中某些部件而不用修改我的程序;

动态加载的需要:有些模块只有在运行时才能确定,需要动态加载外部的功能模块;

独立开发:Plugin 可以和主程序独立建设,主程序只需要制定好框架,实现默认(模版)功能。Plugin 可根据用户需求随时自行扩展开发,运行时随意替换,提高了程序的可定制性;

怎么用Go plugin

Golang 对 Plugin 的实现在标准库plugin中。整个接口可以说相当简洁了。

代码语言:javascript
复制
type Plugin struct{ ... }
    func Open(path string) (*Plugin, error)
    func (p *Plugin) Lookup(symName string) (Symbol, error)
type Symbol interface{}

是的,你没有看错,就只有两个type和两个方法。

代码语言:javascript
复制
type Plugin

即Golang加载的插件,与之有关的两个方法:

Open: 根据参数path提供的插件路径加载这个插件,并返回插件这个插件结构的指针*Glugin

代码语言:javascript
复制
Lookup: *Plugin

的惟一方法,通过名称symName在插件中寻找对应的变量或方法,以Symbol的形式返回

Symbol

根据定义

代码语言:javascript
复制
type Symbol interface{}

,Symbol是interface的别名,也就是说,我们可以从插件里面拿到任何类型的可导出元素。

注意几点问题:

插件中定义的 struct 无法暴露出来,可以让主程序和插件程序import公共的 package 来解决

私有方法、变量不会被暴露出来

官方文档在此:https://golang.org/pkg/plugin/

编写一个 Plugin 基本有以下几步:

1.Plguin 需要有自己的 main package

2.编译的时候,使用 go build -buildmode=plugin file.go 来编译

3.使用 plugin.Open(path string) 来打开.so文件,同一插件只能打开一次,重复打开会报错

4.使用 plugin.LookUp(name string) 来获取插件中对外暴露的方法或者类型

5.使用类型断言,断言后执行相应的方法

使用实例

定义plugin

代码语言:javascript
复制
//plugin.go
package main

import "fmt"

var V int

func F() {
  fmt.Printf("Hello, number %d\n", V)
}

加载使用plugin

代码语言:javascript
复制
//main.go
package main

import "plugin"

func main()  {
  p, err := plugin.Open("plugin.so")
  if err != nil {
    panic(err)
  }
  v, err := p.Lookup("V")
  if err != nil {
    panic(err)
  }
  f, err := p.Lookup("F")
  if err != nil {
    panic(err)
  }
  *v.(*int) = 7
  f.(func())() // prints "Hello, number 7"
}

编译

代码语言:javascript
复制
#!/bin/bash
go build --buildmode=plugin -o plugin.so plugin.go
go run main.go
#Hello, number 7

plugin链接进golang程序会大量增加占用的内存。所以在使用plugin热更新的时候,当发现程序占用内存陡增的时候

下面看下plugin的源码,包含了4个文件

代码语言:javascript
复制
plugin.go
plugin_dlopen.go
plugin_stubs.go
plugin_test.go

plugin.go

代码语言:javascript
复制
// Plugin is a loaded Go plugin.
type Plugin struct {
  pluginpath string
  err        string        // set if plugin failed to load
  loaded     chan struct{} // closed when loaded
  syms       map[string]interface{}
}

pluginpath:库的path

err:用于记录过程中的err

loaded:这个用于防止并发加载同一个库时候用

syms:这个记录的是库中所有的符号和其对应的值(可能是var、func等)

Open函数,封装了open函数

Lookup函数封装了lookup函数

plugin_stubs.go

代码语言:javascript
复制
// +build !linux,!darwin !cgo

这里是针对不支持平台的空实现,!linux,!darwin !cgo。可以看出,和文档中说的一样,非Linux,非darwin平台的时候编译成空实现。当然还有一个cgo,如果不支持cgo的话,也是无法实现plugin的。

plugin_dlopen.go

编译命令中,显示支持linux 和 darwin平台,当然要求是要支持cgo。

然后就是一个cgo的代码。其中封装了两个函数dlopen,dlsym。

代码语言:javascript
复制
#cgo linux LDFLAGS: -ldl
static uintptr_t pluginOpen(const char* path, char** err) {
  void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
  
代码语言:javascript
复制
static void* pluginLookup(uintptr_t h, const char* name, char** err) {
  void* r = dlsym((void*)h, name);

这个是linux中标准的动态链接加载接口。

当然plugin只实现了封装了dlopen,dlsym,两个函数。这个和文档中所提供的接口和描述是符合的。

只提供了加载,并没有提供关闭

全局变量

代码语言:javascript
复制
var (
  pluginsMu sync.Mutex
  plugins   map[string]*Plugin
)

pluginsMu:全局锁

plugins:保存加载的动态库

进入函数,一开始是一些字符串的转换。

重点是加锁后,会判断是否已经在加载,或者已经加载过的plugin。

这个时候,如果刚好plugin还在加载中,

<- p.loaded 会等待plugin加载完毕后,close掉p.loaded。

这种方式就是合并加载

这里就是调用了cgo代码pluginOpen,加载so库

初始化plugin结构体,并将其放入到全局的plugins这个map中。然后unlock全局锁。

继续,调用了cgo代码pluginLookup,查找init函数,并执行。

接着就是循环读取所有的符号,并将符号与其对应的值保存下来。保存在p.syms中。

最后close p.loaded,表示加载过程结束了。

所有的符号都保存在p.syms中,这个时候的查找,就只需要直接查找syms就可以了。

对于上面的问题,有如下解决方案:

1、每次生成的so带一个版本号比如game.1001.so

2、编译的时候新增--ldflags="-pluginpath=xxx"参数

3、使用unsafe进行转换(下面还会有注意事项)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档