Goroutine的创建
src/runtime/proc.go
type funcval struct {
fn uintptr
// variable-size, fn-specific data here
}
//go:nosplit
// siz:参数长度 fn==>fn.fn才是申明的go func(){}
func newproc(siz int32, fn *funcval) {
argp := add(unsafe.Pointer(&fn), sys.PtrSize) // 将参数紧随fn.fn的地址
gp := getg() // 当前g
pc := getcallerpc() // 调用返回后的地址
systemstack(func() {
newproc1(fn, (*uint8)(argp), siz, gp, pc)
})
}
// fn.fn == go func(){}
// argp 参数的地址
// narg 参数的长度
// callergp 调用者g
// callerpc 调用返回后的地址
func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {
_g_ := getg()
if fn == nil {
_g_.m.throwing = -1 // do not dump full stacks
throw("go of nil func value")
}
_g_.m.locks++ // disable preemption because it can be holding p in a local var
siz := narg
siz = (siz + 7) &^ 7 // 进行8位对齐
// 参数长度不能超过2008字节(2048-4*8-8)
if siz >= _StackMin-4*sys.RegSize-sys.RegSize {
throw("newproc: function arguments too large for new goroutine")
}
// 获取到当前g的p
_p_ := _g_.m.p.ptr()
// 从p的缓存池中去拿一个g
newg := gfget(_p_)
if newg == nil {
// 缓冲池中没有,则需要新创建一个,栈大小为2k
newg = malg(_StackMin)
casgstatus(newg, _Gidle, _Gdead)
allgadd(newg) // 新创建的g置位_Gdead状态,防止GC扫描到
}
if newg.stack.hi == 0 {
throw("newproc1: newg missing stack")
}
if readgstatus(newg) != _Gdead {
throw("newproc1: new g is not Gdead")
}
totalSize := 4*sys.RegSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame
totalSize += -totalSize & (sys.SpAlign - 1) // align to spAlign
sp := newg.stack.hi - totalSize
spArg := sp
if usesLR {
// caller's LR
*(*uintptr)(unsafe.Pointer(sp)) = 0
prepGoExitFrame(sp)
spArg += sys.MinFrameSize
}
if narg > 0 {
memmove(unsafe.Pointer(spArg), unsafe.Pointer(argp), uintptr(narg))
// This is a stack-to-stack copy. If write barriers
// are enabled and the source stack is grey (the
// destination is always black), then perform a
// barrier copy. We do this *after* the memmove
// because the destination stack may have garbage on
// it.
if writeBarrier.needed && !_g_.m.curg.gcscandone {
f := findfunc(fn.fn)
stkmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
if stkmap.nbit > 0 {
// We're in the prologue, so it's always stack map index 0.
bv := stackmapdata(stkmap, 0)
bulkBarrierBitmap(spArg, spArg, uintptr(bv.n)*sys.PtrSize, 0, bv.bytedata)
}
}
}
memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
newg.sched.sp = sp // 保存栈顶
newg.stktopsp = sp
newg.sched.pc = funcPC(goexit) + sys.PCQuantum // 这里需要结合gostartcallfn去了解
newg.sched.g = guintptr(unsafe.Pointer(newg))
gostartcallfn(&newg.sched, fn)
newg.gopc = callerpc
newg.ancestors = saveAncestors(callergp)
newg.startpc = fn.fn
if _g_.m.curg != nil {
newg.labels = _g_.m.curg.labels
}
if isSystemGoroutine(newg, false) {
atomic.Xadd(&sched.ngsys, +1)
}
newg.gcscanvalid = false
casgstatus(newg, _Gdead, _Grunnable)
if _p_.goidcache == _p_.goidcacheend {
// Sched.goidgen is the last allocated id,
// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
// At startup sched.goidgen=0, so main goroutine receives goid=1.
_p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
_p_.goidcache -= _GoidCacheBatch - 1
_p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
}
newg.goid = int64(_p_.goidcache)
_p_.goidcache++
runqput(_p_, newg, true)
// 有空闲的p存在,并且处于自旋状态的m数量为0,而且在主函数已经运行的情况下
// 尝试去唤醒某个m
if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
wakep()
}
_g_.m.locks--
if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack
_g_.stackguard0 = stackPreempt
}
}
func gostartcallfn(gobuf *gobuf, fv *funcval) {
var fn unsafe.Pointer
if fv != nil {
fn = unsafe.Pointer(fv.fn)// fv.fn: gorotine的入口地址
} else {
fn = unsafe.Pointer(funcPC(nilfunc))
}
gostartcall(gobuf, fn, unsafe.Pointer(fv))
}
func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
sp := buf.sp
if sys.RegSize > sys.PtrSize {
sp -= sys.PtrSize
*(*uintptr)(unsafe.Pointer(sp)) = 0
}
sp -= sys.PtrSize // 栈顶往下移动8位,为返回地址预留空间
// 将fn伪装成是函数goexit函数调用的,函数fn执行完成返回到goexit继续执行,完成清理操作
*(*uintptr)(unsafe.Pointer(sp)) = buf.pc
buf.sp = sp
//这里才真正让newg的ip寄存器指向fn函数,注意,这里只是在设置newg的一些信息,newg还未执行,
//等到newg被调度起来运行时,调度器会把buf.pc放入cpu的IP寄存器,
//从而使newg得以在cpu上真正的运行起来
buf.pc = uintptr(fn)
buf.ctxt = ctxt
}
参考
https://mp.weixin.qq.com/s/8OffE6xDj4fD1q-aJw43ig
http://xiaorui.cc/archives/6483