在运行你的代码之前go都做了什么

zhangxl_890207 · 2018-03-30 09:40:16 · 3520 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2018-03-30 09:40:16 的主题,其中的信息可能已经有所发展或是发生改变。

go程序是如何启动的?

测试环境:

$ uname -a
Linux zxl 4.9.0-deepin13-amd64 #1 SMP PREEMPT Deepin 4.9.57-1 (2017-10-19) x86_64 GNU/Linux
$ go version
go version go1.10 linux/amd64

我们来写一个程序hello.go:

  package main
  func main(){
    println("Hello,World!")
  }

使用go build -gcflags "-N -l" -o hello hello.go编译,-gcflags "-N -l"关闭编译器代码优化和内联 运行$ gdb ./hello

(gdb) info files
Symbols from "/data/Study/go/hello".
Local exec file:
    `/data/Study/go/hello', file type elf64-x86-64.
    Entry point: 0x448ef0
(gdb) b *0x448ef0
Breakpoint 1 at 0x448ef0: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
(gdb) b runtime.rt0_go
Breakpoint 2 at 0x445730: file /usr/local/go/src/runtime/asm_amd64.s, line 89.
(gdb) b runtime.args
Breakpoint 3 at 0x42f970: file /usr/local/go/src/runtime/runtime1.go, line 60.
(gdb) b runtime.osinit
Breakpoint 4 at 0x41ff80: file /usr/local/go/src/runtime/os_linux.go, line 272.
(gdb) b runtime.schedinit
Breakpoint 5 at 0x4245d0: file /usr/local/go/src/runtime/proc.go, line 472.
(gdb) b runtime.newproc
Breakpoint 6 at 0x42aec0: file /usr/local/go/src/runtime/proc.go, line 3235.
(gdb) b runtime.mstart
Breakpoint 7 at 0x426290: file /usr/local/go/src/runtime/proc.go, line 1170.
(gdb) b runtime.main
Breakpoint 8 at 0x423420: file /usr/local/go/src/runtime/proc.go, line 109

可以程序开始于/usr/local/go/src/runtime/rt0_linux_amd64.s:8,即rt0_arm64_linux函数, 源码包runtime下放可许多rt0_sys_arch.s文件

TEXT _rt0_arm64_linux(SB),NOSPLIT,$-8
    MOVD    0(RSP), R0    // argc
    ADD    $8, RSP, R1    // argv
    BL    main(SB)

来看main

TEXT main(SB),NOSPLIT,$-8
    MOVD    $runtime·rt0_go(SB), R2
    BL    (R2)
exit:
    MOVD $0, R0
    MOVD    $94, R8    // sys_exit
    SVC
    B    exit

源码中的·编译完之后会变成. 断点b runtime.rt0_go,定位到/usr/local/go/src/runtime/asm_amd64.s, line 89

TEXT runtime·rt0_go(SB),NOSPLIT,$0
  ...
  CALL    runtime·args(SB)//命令行参数初始化
  CALL    runtime·osinit(SB)// 系统初始化
  CALL    runtime·schedinit(SB)//调度器初始化

  // create a new goroutine to start program
  MOVQ    $runtime·mainPC(SB), AX        // entry
  PUSHQ    AX
  PUSHQ    $0            // arg size
  CALL    runtime·newproc(SB)//创建一个goroutine
  POPQ    AX
  POPQ    AX

  // start this M
  CALL    runtime·mstart(SB)
  MOVL    $0xf1, 0xf1  // crash
  RET
DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL    runtime·mainPC(SB),RODATA,$8

由此,汇编引导过程完成

断点runtime.args,在/usr/local/go/src/runtime/runtime1.go, line 60,初始化参数列表

func args(c int32, v **byte) {
    argc = c
    argv = v
    sysargs(c, v)
}

断点runtime.osinit,在/usr/local/go/src/runtime/os_linux.go, line 272,获取CPU核数

  func osinit() {
      ncpu = getproccount()
  }

断点runtime.schedinit,在/usr/local/go/src/runtime/proc.go, line 472,调度器初始化

// The bootstrap sequence is:
//    call osinit
//    call schedinit
//    make & queue new G
//    call runtime·mstart
// The new G calls runtime·main.
func schedinit() {
  // raceinit must be the first call to race detector.
  // In particular, it must be done before mallocinit below calls racemapshadow.
  _g_ := getg()
    if raceenabled {
    _g_.racectx, raceprocctx0 = raceinit()
  }
  sched.maxmcount = 10000 //最大系统线程数限制  runtime/debug.SetMaxThreads
  //初始化栈、内存分配器,调度器相关
  tracebackinit()
  moduledataverify()
  stackinit()
  mallocinit()
  mcommoninit(_g_.m)
  alginit()       // maps must not be used before this call
  modulesinit()   // provides activeModules
  typelinksinit() // uses maps, activeModules
  itabsinit()     // uses activeModules
  msigsave(_g_.m)
  initSigmask = _g_.m.sigmask
  //命令行参数与环境变量初始化
  goargs()
  goenvs()
  //处理GODEBUG,GOTRACEBACK调试相关的环境变量
  parsedebugvars()
  //垃圾回收器的初始化
  gcinit()
  //通过CPU Core和GOMAXPROCS环境变量确定P的数量
  sched.lastpoll = uint64(nanotime())
  procs := ncpu
  if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
    procs = n
  }
  // 调整P的数量
  if procresize(procs) != nil {
    throw("unknown runnable goroutine during bootstrap")
  }
  // For cgocheck > 1, we turn on the write barrier at all times
  // and check all pointer writes. We can't do this until after
  // procresize because the write barrier needs a P.
  if debug.cgocheck > 1 {
    writeBarrier.cgo = true
    writeBarrier.enabled = true
    for _, p := range allp {
      p.wbBuf.reset()
    }
  }
  if buildVersion == "" {
    // Condition should never trigger. This code just serves
    // to ensure runtime·buildVersion is kept in the resulting binary.
    buildVersion = "unknown"
  }
}

创建一个goroutine(G),断点runtime.newproc,在/usr/local/go/src/runtime/proc.go, line 3235//G就是goroutine实现的核心结构了,G维护了goroutine需要的栈、程序计数器以及它所在的M等信息。 启动一个内核级线程(M),一个开始执行程序,断点runtime.mstart,在/usr/local/go/src/runtime/proc.go, line 1170 //M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。

关于调度器是如何调度的,请查看goroutine与调度器其形象的描述了go调度器的工作方式

runtime.main 即 /usr/local/go/src/runtime/proc.go中的mian

Breakpoint 8, runtime.main () at /usr/local/go/src/runtime/proc.go:109
109    func main() {
(gdb) bt
#0  runtime.main () at /usr/local/go/src/runtime/proc.go:109
#1  0x0000000000447fc1 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:2361
#2  0x0000000000000000 in ?? ()
(gdb)
func main() {
  ...
  // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.Using decimal instead of binary GB and MB because they look nicer in the stack overflow failure message.
  if sys.PtrSize == 8 {
    maxstacksize = 1000000000
  } else {
    maxstacksize = 250000000
  }
  // Allow newproc to start new Ms.
  mainStarted = true
  //启动系统后台监控(定期垃圾回收,并发调度)
  systemstack(func() {
    newm(sysmon, nil)
  })

  //在初始化过程中,上主线程锁,大多数程序并不关心,但少数确实需要由主线程发出的某些调用,可以在初始化过程中的main_main调用runtime.lockosthread来保存锁。
  lockOSThread()
  ...
  //执行runtime包里的所有init函数 must be before defer
  runtime_init()
  ...
  // Defer unlock so that runtime.Goexit during init does the unlock too.
  needUnlock := true
  defer func() {
    if needUnlock {
      unlockOSThread()
    }
  }()
  //启动垃圾回收器后台操作
  gcenable()
  ...
  //
  fn := main_init //调用所有用户程序涉及包的init函数
  fn()
  //解锁
  needUnlock = false
  unlockOSThread()
  ...
  fn = main_main // 执行用户逻辑入口
  fn()
}

至此,一个go程序的启动逻辑完成

注:本文参考自go语言学习笔记


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3520 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传