源码分析go调度器二: main goroutine的创建

585 阅读2分钟

前一节schedinit完成了调度器的初始化,接下来会通过newproc创建一个goroutine来执行runtime.main函数

创建main goroutine

asm_amd64.s:214

// create a new goroutine to start program
MOVQ   $runtime·mainPC(SB), AX       // entry 这里mainPC是runtime.main
PUSHQ  AX                            // 作为newproc的第二个参数
PUSHQ  $0       // arg size          // 作为newproc的第一个参数
CALL   runtime·newproc(SB)
POPQ   AX
POPQ   AX

proc.go:4250

func newproc(siz int32, fn *funcval) {
   argp := add(unsafe.Pointer(&fn), sys.PtrSize)     // 获得fn的参数的地址
   gp := getg()                     //gp=g0
   pc := getcallerpc()              // 返回caller‘s PC,就是下一条指令的地址,为POPQ AX
   systemstack(func() {             // 切换到g0的栈空间,但是此时本来就是执行在g0上
      newg := newproc1(fn, argp, siz, gp, pc)

      _p_ := getg().m.p.ptr()
      runqput(_p_, newg, true)

      if mainStarted {
         wakep()
      }
   })
}

proc.go:4275

newproc1的参数如下:

  1. fn其实就是runtime.main的地址
  2. argp为runtime.main的参数开始地址
  3. narg为runtime.main的参数个数
  4. calllergp此时为g0
  5. callerpc为CALL runtime·newproc(SB)下一条指令POPQ AX的地址
// Create a new g in state _Grunnable, starting at fn, with narg bytes
// of arguments starting at argp. callerpc is the address of the go
// statement that created this. The caller is responsible for adding
// the new g to the scheduler.
//
// This must run on the system stack because it's the continuation of
// newproc, which cannot split the stack.
//
//go:systemstack
func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g {
   if goexperiment.RegabiDefer && narg != 0 {
      // TODO: When we commit to GOEXPERIMENT=regabidefer,
      // rewrite the comments for newproc and newproc1.
      // newproc will no longer have a funny stack layout or
      // need to be nosplit.
      throw("go with non-empty frame")
   }

   _g_ := getg()   // _g_=g0

   if fn == nil {
      _g_.m.throwing = -1 // do not dump full stacks
      throw("go of nil func value")
   }
   acquirem() // disable preemption because it can be holding p in a local var
   siz := narg
   siz = (siz + 7) &^ 7

   // We could allocate a larger initial stack if necessary.
   // Not worth it: this is almost always an error.
   // 4*PtrSize: extra space added below
   // PtrSize: caller's LR (arm) or return address (x86, in gostartcall).
   if siz >= _StackMin-4*sys.PtrSize-sys.PtrSize {
      throw("newproc: function arguments too large for new goroutine")
   }

   _p_ := _g_.m.p.ptr() // _p_=allp[0]
   newg := gfget(_p_)   // 初始化阶段,gf里面是空的,所以接下来要new一个
   if newg == nil {
      newg = malg(_StackMin)   // 2K大小
      casgstatus(newg, _Gidle, _Gdead)
      allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
   }
   if newg.stack.hi == 0 {
      throw("newproc1: newg missing stack")
   }

   if readgstatus(newg) != _Gdead {
      throw("newproc1: new g is not Gdead")
   }

   totalSize := 4*sys.PtrSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame
   totalSize += -totalSize & (sys.StackAlign - 1)               // align to StackAlign
   sp := newg.stack.hi - totalSize   //totalSize=32
   spArg := sp        // 确定参数位置
   if usesLR {
      // caller's LR
      *(*uintptr)(unsafe.Pointer(sp)) = 0
      prepGoExitFrame(sp)
      spArg += sys.MinFrameSize
   }
   if narg > 0 {
      memmove(unsafe.Pointer(spArg), argp, uintptr(narg)) //将参数从g0栈拷贝到newg的栈
      (...)
   }

   (...)
}

image.png

proc.go:4346

memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))  //将newg.sched成员变量清空
newg.sched.sp = sp  //sched用来保存newg的调度信息,依靠它来将goroutine切到cpu上
newg.stktopsp = sp
// newg.sched.pc代表newg被调度起来时执行第一行代码的位置,为goexit的第二条指令
newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
newg.sched.g = guintptr(unsafe.Pointer(newg))
gostartcallfn(&newg.sched, fn)
// adjust Gobuf as if it executed a call to fn
// and then stopped before the first instruction in fn.
func gostartcallfn(gobuf *gobuf, fv *funcval) {
   var fn unsafe.Pointer
   if fv != nil {
      fn = unsafe.Pointer(fv.fn)
   } else {
      fn = unsafe.Pointer(funcPC(nilfunc))
   }
   gostartcall(gobuf, fn, unsafe.Pointer(fv))
}
// adjust Gobuf as if it executed a call to fn with context ctxt
// and then stopped before the first instruction in fn.
func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
   sp := buf.sp
   sp -= sys.PtrSize
   *(*uintptr)(unsafe.Pointer(sp)) = buf.pc
   buf.sp = sp
   buf.pc = uintptr(fn)
   buf.ctxt = ctxt
}

proc.go:4254

systemstack(func() {
   newg := newproc1(fn, argp, siz, gp, pc)

   _p_ := getg().m.p.ptr()
   runqput(_p_, newg, true)   //将newg放入p的队列里

   if mainStarted {
      wakep()
   }
})

image.png