重新认识 go

go 的 Runtime

  • Runtime 作为程序的一部分打包进二进制产物
  • Runtime 随用户程序一起运行
  • Runtime 与用户程序没有明显界限,直接通过函数调用

Go Runtime 的能力:

  • 内存管理:它能够进行帮助我们分配内存,比如堆内存/栈内存等

  • 垃圾回收能力

  • 超强的并发能力(协程调度)

  • 有一定的屏蔽系统调用能力,go 是一门跨 OS 的语言,这个能力是 Runtime 提供的

  • go 关键字是 Runtime 提供的

    关键字 函数
    go newproc
    new newobject
    make makemap,makechan,makeslice...
    <- chansend1,chanrecv1

go 程序是如何编译的

go 复制代码
package main
import "fmt"

func main() {
  fmt.Println("hello world")
}

使用命令 go build -n 可以查看编译过程,这个指令的作用是不实际编译它,只是输出它的变易过程

bash 复制代码
#
# limit-go
#

mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg << 'EOF' # internal
# ① 文件引入包的地方
# import config
packagefile fmt=/Users/uccs/Library/Caches/go-build/9d/9d949eb387278de330b58e7ecbd6f130e51e64b0cd062f60fb7f69fad2c37c08-d  # fmt 包的路径
packagefile runtime=/Users/uccs/Library/Caches/go-build/d3/d35337e57cdb7ec3bc75cf5cc9526a5dd8c2cac47cf9bab7c63f0e2a1dd9ebcd-d # runtime 包的路径
EOF
cd /Users/uccs/Desktop/project/tools/limit-go
# ② 执行 go 的 compile 命令
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.21 -complete -buildid NPqG56mfdgtvjXh3cGFS/NPqG56mfdgtvjXh3cGFS -goversion go1.21.3 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
# ③ 执行 compile 命令后生成的一堆文件
packagefile limit-go=$WORK/b001/_pkg_.a
packagefile fmt=/Users/uccs/Library/Caches/go-build/9d/9d949eb387278de330b58e7ecbd6f130e51e64b0cd062f60fb7f69fad2c37c08-d
packagefile runtime=/Users/uccs/Library/Caches/go-build/d3/d35337e57cdb7ec3bc75cf5cc9526a5dd8c2cac47cf9bab7c63f0e2a1dd9ebcd-d
packagefile errors=/Users/uccs/Library/Caches/go-build/a6/a6b999568116b1b19d9204becc8ed5686d44eaa2dfbc7520a4fc3a0599c161f9-d
packagefile internal/fmtsort=/Users/uccs/Library/Caches/go-build/a7/a730efeb5a119d046d78935bc03a3ad2d212e19b1f178fe19b8682833211cfba-d
packagefile io=/Users/uccs/Library/Caches/go-build/bf/bf725efbcf3a96ed4510a4bfc7b9bb28ee1d60f62f99a2599f6d386f8fe8f433-d
packagefile math=/Users/uccs/Library/Caches/go-build/4a/4a9091add7da0dfae4c714cc84fe3d7059f182275f79231cdfcd9bae71ea4379-d
packagefile os=/Users/uccs/Library/Caches/go-build/eb/eb0e34a6a890ab326d4b88762deb41b547ba2f554a86f01adef7171130494f48-d
packagefile reflect=/Users/uccs/Library/Caches/go-build/fe/fe1d170bb3f71666c6ca69285a73d3bee470fa4e1c9f48e3d8d9f1d8a418a53c-d
packagefile sort=/Users/uccs/Library/Caches/go-build/82/82adbe8e360083bd9e5a60077c5a647e4bbbae49adbea005017d44ec318750d0-d
packagefile strconv=/Users/uccs/Library/Caches/go-build/57/57d0eba198b828f90d005e40a6c1c22289680468f45a469401f649638c812898-d
packagefile sync=/Users/uccs/Library/Caches/go-build/ce/ce53b6ab314126807707e24de3ee1dd56322c7e3b91c433ed0d208df9e51649d-d
packagefile unicode/utf8=/Users/uccs/Library/Caches/go-build/1d/1de56338243a23ad198d173ce5b6f3cf301ccfd4828ec217119e208e21b27a2d-d
packagefile internal/abi=/Users/uccs/Library/Caches/go-build/16/16f37713a7fa27904f42fa3ed1dbe862bf81d0c72bd0a1b38b36e2f94331be1a-d
packagefile internal/bytealg=/Users/uccs/Library/Caches/go-build/dc/dc487fe880bdec21754d595cf49acabce298368bc87380c41a5543cb7e742650-d
packagefile internal/coverage/rtcov=/Users/uccs/Library/Caches/go-build/e2/e259c5cf3c25ca7661db00e75b78c7f965245bedefc5e663bc68a5c45299fd1a-d
packagefile internal/cpu=/Users/uccs/Library/Caches/go-build/23/23bce7bdce8ba5590b9de76c20f919ea8ac3ecc7c43a5a09cd73a618735478c8-d
packagefile internal/goarch=/Users/uccs/Library/Caches/go-build/ae/ae6f27645ca02cd0a1e3995f4c05f7d783598bb7d3761513c659fd49eb187c1e-d
packagefile internal/godebugs=/Users/uccs/Library/Caches/go-build/22/221c1c6a2920cfc261ee242057b2d19d50508e1424a9373a3135044fd7f60824-d
packagefile internal/goexperiment=/Users/uccs/Library/Caches/go-build/55/55b8ec0fe0f74044d60047c074dac977577bc04ba5b7010ac1441fd69498c7bb-d
packagefile internal/goos=/Users/uccs/Library/Caches/go-build/b7/b7a2966bdde59f126d4b096834b362988db6ce07a3c29ffe6c14c37f5f5254b4-d
packagefile runtime/internal/atomic=/Users/uccs/Library/Caches/go-build/d2/d255ab5ec0f88cb395f79e4ea6b5638bf1d45549c03edcb4d46efb3887dd9c63-d
packagefile runtime/internal/math=/Users/uccs/Library/Caches/go-build/f6/f698b3ea1b8ecd800ead11dec7ecbf1684d6d6b3b4981b1b7047d38c33f32f8e-d
packagefile runtime/internal/sys=/Users/uccs/Library/Caches/go-build/37/37129d69476f12fec8ac3b920b665455dcdbc99c8d9a6d3d798d9a660f2696a8-d
packagefile internal/reflectlite=/Users/uccs/Library/Caches/go-build/39/39eed692bdf8ab45264cdfaa29ccf5d34029838ff0894086dd2ebd334d9f3e75-d
packagefile math/bits=/Users/uccs/Library/Caches/go-build/cf/cf6e5a72cd5709aecc627f48de82c928b14042f64ef105e54cc146d8b92a38d8-d
packagefile internal/itoa=/Users/uccs/Library/Caches/go-build/18/187330c6da05306533dbdc909c3777f9e8b81fb3b5151a8379e97ac6e7a1ebb8-d
packagefile internal/poll=/Users/uccs/Library/Caches/go-build/98/9898da8b84f86056f43b3f928394cf36998c076c6a2b76cf43c8af18c5a59d63-d
packagefile internal/safefilepath=/Users/uccs/Library/Caches/go-build/c1/c1675768be92a9ef26018894d92185870eb3eac31a4c1a88d939e56eb10f8dbd-d
packagefile internal/syscall/execenv=/Users/uccs/Library/Caches/go-build/1f/1f5b502d4235cea5d89967d55242eb307dd29b379b1396580a35594391ea5780-d
packagefile internal/syscall/unix=/Users/uccs/Library/Caches/go-build/51/51de91caecab68364694a5552a7620dce46cd7b3bd84871445fb313d29093fe6-d
packagefile internal/testlog=/Users/uccs/Library/Caches/go-build/7e/7e3b6f183732548ac8b075ffb24c67e5194eced94579e23dc231200a02a6efe3-d
packagefile io/fs=/Users/uccs/Library/Caches/go-build/d0/d0a1b5259344935c41cf177400f04253aeca321a1175255f76d4311264cff692-d
packagefile sync/atomic=/Users/uccs/Library/Caches/go-build/e7/e79a0bad378846779234909aa0a1cbd7fecdad4e23c925e019281783df45e1e1-d
packagefile syscall=/Users/uccs/Library/Caches/go-build/71/711660abd17c2c345f77e44978a4e13946f1e0004dec357425d62a14aa7acd50-d
packagefile time=/Users/uccs/Library/Caches/go-build/b6/b67da8eed4f067251323c9d9adad19a276195c4df5fd7c2dc62512a9a10b6c37-d
packagefile internal/unsafeheader=/Users/uccs/Library/Caches/go-build/b9/b9c4a61deef9c40fad8ca93466c77e4499cd49cf51dea13330f9919281b1ebc9-d
packagefile unicode=/Users/uccs/Library/Caches/go-build/8f/8fc9006eeb3b049e224651f51abbe5e8f32c1c56136ad340fb667ce8c2b53708-d
packagefile internal/race=/Users/uccs/Library/Caches/go-build/23/232609feea6a346964a0d1450379227ef10aa22861915e979143612885ad2ac8-d
packagefile internal/oserror=/Users/uccs/Library/Caches/go-build/82/82cf7ae7a533a2d9241b6e5d105da4ab4d48b17f96b66030a551ff723fa1f01d-d
packagefile path=/Users/uccs/Library/Caches/go-build/2a/2ae3945be4388190aa3f8037f296b0c2e236a15d752d491fb53ae1294cdb20cb-d
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tlimit-go\nmod\tlimit-go\t(devel)\t\nbuild\t-buildmode=exe\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
# ④ 将这些用到的文件打包成一个二进制文件 a.out
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=VINFY1yfalFx5VnyZHPS/NPqG56mfdgtvjXh3cGFS/NPqG56mfdgtvjXh3cGFS/VINFY1yfalFx5VnyZHPS -extld=cc $WORK/b001/_pkg_.a
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
# ⑤ 将 a.out 重命名为 limit-go
mv $WORK/b001/exe/a.out limit-go

go 的编译过程如下:

  1. 词法分析:将源码翻译成 tokentoken 是代码中的最小语义结构
  2. 句法分析:token 经过处理,变成语法树(ast)
  3. 语义分析:
    • 类型检查
    • 类型推断
    • 查看类型是否匹配
    • 函数调用内联
    • 逃逸分析(变量是放在堆上还是栈上)
  4. 代码优化,每一步都会做
  5. 中间码生成:(与平台无关的汇编代码)
    • 为了处理不同平台的差异,先生产中间代码(SSA)
    • 查看中间码
      1. 设置环境变量,指示编译器输出某一个函数的中间码

        bash 复制代码
        export GOSSAFUNC=main
      2. 运行 go build 命令,会看到输出一个 ssa.html 的文件

  6. 机器码生成
    • 先生成 plan9 的汇编代码(与平台相关)

      • 查看 plan9 的汇编代码

        bash 复制代码
        go build -gcflags -S main.go
    • 最后编译成机器码

    • 输出的机器码为 -d 结尾的文件

  7. 将各个包进行链接生成一个可执行文件,包括 runtime

go 程序是如何运行的

rt0_linux_amd64.sgo 的启动文件,它是 go 的入口文件,它会调用 main.main 函数

文件名的意思:

  • rtruntime 的缩写
  • 0 代表是 runtime 的第一个文件
  • linux 代表是 linux 平台
  • amd64 代表是 amd64 架构

_rt0_amd64_linux -> _rt0_amd64

找到函数 _rt0_amd64,它是汇编语言函数

  1. 这个函数的作用是读取命令行参数,并将 argcargv 放到栈上

    nasm 复制代码
    TEXT _rt0_amd64(SB),NOSPLIT,$-8
      MOVQ	0(SP), DI	// argc
      LEAQ	8(SP), SI	// argv
      JMP	runtime·rt0_go(SB)
  2. 初始化 g0 执行栈(这是在 runtime.rt0_go 函数中完成的)

    • g0go 第一个协程,g0 是一母协程

    • g0 是为了调度协程而产生的协程

      nasm 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
        // xxxx
        MOVQ	$runtime·g0(SB), DI
        LEAQ	(-64*1024)(SP), BX
        MOVQ	BX, g_stackguard0(DI)
        MOVQ	BX, g_stackguard1(DI)
        MOVQ	BX, (g_stack+stack_lo)(DI)
        MOVQ	SP, (g_stack+stack_hi)(DI)
    • 运行时检测(调用 go 语言的 runtime.check)

      • 检查各种类型的长度
      • 检查指针操作
      • 监测结构体字段的偏移量
      • 检查 atomic 原子操作
      • 检查 CAS 操作
      • 检查栈大小是否是 2 的幂次
      nasm 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
         // xxx
        CALL	runtime·check(SB)
    • 参数初始化(调用 go 语言的 runtime.args)

      • 对命令行中的参数进行处理
        • 将参数数量赋值给 argc int32
        • 将参数值赋值给 argv **byte
      nasm 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
        // xxx
        CALL	runtime·args(SB)
    • 判断系统的字长和核数(调用 go 语言的 runtime.osinit),后续调度器初始化需要使用

    nasm 复制代码
    TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
      // xxx
      CALL	runtime·osinit(SB)
    • 调度器初始化(调用 go 语言的 runtime.schedinit)

      • 全局栈空间内存分配
      • 加载命令行参数到 os.Args
      • 堆内存空间的初始化
      • 加载操作系统环境变量
      • 初始化当前系统线程
      • 垃圾回收器的参数初始化
      • 算法初始化(maphash
      • 设置 process 数量
      go 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
        // xxx
        CALL	runtime·schedinit(SB)
    • 创建一个新的协程

      nasm 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
        MOVQ	$runtime·mainPC(SB), AX		// entry
    • 初始化 M

      • 初始化一个 M 用来调用主协程
      nasm 复制代码
      TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
        CALL	runtime·mstart(SB)
    • 调用 go 语言的 main

      nasm 复制代码
      DATA	runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
  3. runtime.mainRuntime 的入口函数,在这里面会调用 main.main 函数

    • go:linkname 标签在链接时会跳转到 main.main 函数,这个 main.mian 函数就是我们写的 main 函数
    go 复制代码
    //go:linkname main_main main.main
    func main_main()
    func main(){
       fn := main_main
       fn()
    }

主协程执行主函数主要做了 4 件事:

  1. 执行 runtime 包中的 init 方法
  2. 启动 GC 垃圾收集器
  3. 执行用户依赖的 init 方法
  4. 执行用户主函数 main.main()

go 包管理方法

  1. 初始化项目 go mod init <git repo address>

  2. 替换本地文件 replace github.com/xxx/xxx => /xxx/xxx

  3. 缓存到本地

    bash 复制代码
    go mod vendor  # 将依赖包缓存到本地
    go build mod vendeor # 编译项目
  4. 无法访问 github,使用 goproxy.cn 作为代理

    bash 复制代码
    go env -w GOPROXY=https://goproxy.cn,direct
相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue4 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml47 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠8 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#