重新认识 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
相关推荐
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫