Golang源码探索----GC的实现原理(4)

推荐文章:

Golang源码探索----GC的实现原理(1)

Golang源码探索----GC的实现原理(2)

Golang源码探索----GC的实现原理(3)

gcMarkTinyAllocs函数会标记所有tiny alloc等待合并的对象:

 1// gcMarkTinyAllocs greys all active tiny alloc blocks.
 2//
 3// The world must be stopped.
 4func gcMarkTinyAllocs() {
 5    for _, p := range &allp {
 6        if p == nil || p.status == _Pdead {
 7            break
 8        }
 9        c := p.mcache
10        if c == nil || c.tiny == 0 {
11            continue
12        }
13        // 标记各个P中的mcache中的tiny
14        // 在上面的mallocgc函数中可以看到tiny是当前等待合并的对象
15        _, hbits, span, objIndex := heapBitsForObject(c.tiny, 0, 0)
16        gcw := &p.gcw
17        // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)
18        greyobject(c.tiny, 0, 0, hbits, span, gcw, objIndex)
19        // gcBlackenPromptly变量表示当前是否禁止本地队列, 如果已禁止则把标记任务flush到全局队列
20        if gcBlackenPromptly {
21            gcw.dispose()
22        }
23    }
24}

startTheWorldWithSema函数会重新启动世界:

 1func startTheWorldWithSema() {
 2    _g_ := getg()
 3    // 禁止G被抢占
 4    _g_.m.locks++        // disable preemption because it can be holding p in a local var
 5    // 判断收到的网络事件(fd可读可写或错误)并添加对应的G到待运行队列
 6    gp := netpoll(false) // non-blocking
 7    injectglist(gp)
 8    // 判断是否要启动gc helper
 9    add := needaddgcproc()
10    lock(&sched.lock)
11    // 如果要求改变gomaxprocs则调整P的数量
12    // procresize会返回有可运行任务的P的链表
13    procs := gomaxprocs
14    if newprocs != 0 {
15        procs = newprocs
16        newprocs = 0
17    }
18    p1 := procresize(procs)
19    // 取消GC等待标记
20    sched.gcwaiting = 0
21    // 如果sysmon在等待则唤醒它
22    if sched.sysmonwait != 0 {
23        sched.sysmonwait = 0
24        notewakeup(&sched.sysmonnote)
25    }
26    unlock(&sched.lock)
27    // 唤醒有可运行任务的P
28    for p1 != nil {
29        p := p1
30        p1 = p1.link.ptr()
31        if p.m != 0 {
32            mp := p.m.ptr()
33            p.m = 0
34            if mp.nextp != 0 {
35                throw("startTheWorld: inconsistent mp->nextp")
36            }
37            mp.nextp.set(p)
38            notewakeup(&mp.park)
39        } else {
40            // Start M to run P.  Do not start another M below.
41            newm(nil, p)
42            add = false
43        }
44    }
45    // 如果有空闲的P,并且没有自旋中的M则唤醒或者创建一个M
46    // Wakeup an additional proc in case we have excessive runnable goroutines
47    // in local queues or in the global queue. If we don't, the proc will park itself.
48    // If we have lots of excessive work, resetspinning will unpark additional procs as necessary.
49    if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 {
50        wakep()
51    }
52    // 启动gc helper
53    if add {
54        // If GC could have used another helper proc, start one now,
55        // in the hope that it will be available next time.
56        // It would have been even better to start it before the collection,
57        // but doing so requires allocating memory, so it's tricky to
58        // coordinate. This lazy approach works out in practice:
59        // we don't mind if the first couple gc rounds don't have quite
60        // the maximum number of procs.
61        newm(mhelpgc, nil)
62    }
63    // 允许G被抢占
64    _g_.m.locks--
65    // 如果当前G要求被抢占则重新尝试
66    if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack
67        _g_.stackguard0 = stackPreempt
68    }
69}

重启世界后各个M会重新开始调度, 调度时会优先使用上面提到的findRunnableGCWorker函数查找任务, 之后就有大约25%的P运行后台标记任务.
后台标记任务的函数是gcBgMarkWorker:

  1func gcBgMarkWorker(_p_ *p) {
  2    gp := getg()
  3    // 用于休眠后重新获取P的构造体
  4    type parkInfo struct {
  5        m      muintptr // Release this m on park.
  6        attach puintptr // If non-nil, attach to this p on park.
  7    }
  8    // We pass park to a gopark unlock function, so it can't be on
  9    // the stack (see gopark). Prevent deadlock from recursively
 10    // starting GC by disabling preemption.
 11    gp.m.preemptoff = "GC worker init"
 12    park := new(parkInfo)
 13    gp.m.preemptoff = ""
 14    // 设置当前的M并禁止抢占
 15    park.m.set(acquirem())
 16    // 设置当前的P(需要关联到的P)
 17    park.attach.set(_p_)
 18    // 通知gcBgMarkStartWorkers可以继续处理
 19    // Inform gcBgMarkStartWorkers that this worker is ready.
 20    // After this point, the background mark worker is scheduled
 21    // cooperatively by gcController.findRunnable. Hence, it must
 22    // never be preempted, as this would put it into _Grunnable
 23    // and put it on a run queue. Instead, when the preempt flag
 24    // is set, this puts itself into _Gwaiting to be woken up by
 25    // gcController.findRunnable at the appropriate time.
 26    notewakeup(&work.bgMarkReady)
 27    for {
 28        // 让当前G进入休眠
 29        // Go to sleep until woken by gcController.findRunnable.
 30        // We can't releasem yet since even the call to gopark
 31        // may be preempted.
 32        gopark(func(g *g, parkp unsafe.Pointer) bool {
 33            park := (*parkInfo)(parkp)
 34            // 重新允许抢占
 35            // The worker G is no longer running, so it's
 36            // now safe to allow preemption.
 37            releasem(park.m.ptr())
 38            // 设置关联的P
 39            // 把当前的G设到P的gcBgMarkWorker成员, 下次findRunnableGCWorker会使用
 40            // 设置失败时不休眠
 41            // If the worker isn't attached to its P,
 42            // attach now. During initialization and after
 43            // a phase change, the worker may have been
 44            // running on a different P. As soon as we
 45            // attach, the owner P may schedule the
 46            // worker, so this must be done after the G is
 47            // stopped.
 48            if park.attach != 0 {
 49                p := park.attach.ptr()
 50                park.attach.set(nil)
 51                // cas the worker because we may be
 52                // racing with a new worker starting
 53                // on this P.
 54                if !p.gcBgMarkWorker.cas(0, guintptr(unsafe.Pointer(g))) {
 55                    // The P got a new worker.
 56                    // Exit this worker.
 57                    return false
 58                }
 59            }
 60            return true
 61        }, unsafe.Pointer(park), "GC worker (idle)", traceEvGoBlock, 0)
 62        // 检查P的gcBgMarkWorker是否和当前的G一致, 不一致时结束当前的任务
 63        // Loop until the P dies and disassociates this
 64        // worker (the P may later be reused, in which case
 65        // it will get a new worker) or we failed to associate.
 66        if _p_.gcBgMarkWorker.ptr() != gp {
 67            break
 68        }
 69        // 禁止G被抢占
 70        // Disable preemption so we can use the gcw. If the
 71        // scheduler wants to preempt us, we'll stop draining,
 72        // dispose the gcw, and then preempt.
 73        park.m.set(acquirem())
 74        if gcBlackenEnabled == 0 {
 75            throw("gcBgMarkWorker: blackening not enabled")
 76        }
 77        // 记录开始时间
 78        startTime := nanotime()
 79        decnwait := atomic.Xadd(&work.nwait, -1)
 80        if decnwait == work.nproc {
 81            println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)
 82            throw("work.nwait was > work.nproc")
 83        }
 84        // 切换到g0运行
 85        systemstack(func() {
 86            // 设置G的状态为等待中这样它的栈可以被扫描(两个后台标记任务可以互相扫描对方的栈)
 87            // Mark our goroutine preemptible so its stack
 88            // can be scanned. This lets two mark workers
 89            // scan each other (otherwise, they would
 90            // deadlock). We must not modify anything on
 91            // the G stack. However, stack shrinking is
 92            // disabled for mark workers, so it is safe to
 93            // read from the G stack.
 94            casgstatus(gp, _Grunning, _Gwaiting)
 95            // 判断后台标记任务的模式
 96            switch _p_.gcMarkWorkerMode {
 97            default:
 98                throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
 99            case gcMarkWorkerDedicatedMode:
100                // 这个模式下P应该专心执行标记
101                // 执行标记, 直到被抢占, 并且需要计算后台的扫描量来减少辅助GC和唤醒等待中的G
102                gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
103                // 被抢占时把本地运行队列中的所有G都踢到全局运行队列
104                if gp.preempt {
105                    // We were preempted. This is
106                    // a useful signal to kick
107                    // everything out of the run
108                    // queue so it can run
109                    // somewhere else.
110                    lock(&sched.lock)
111                    for {
112                        gp, _ := runqget(_p_)
113                        if gp == nil {
114                            break
115                        }
116                        globrunqput(gp)
117                    }
118                    unlock(&sched.lock)
119                }
120                // 继续执行标记, 直到无更多任务, 并且需要计算后台的扫描量来减少辅助GC和唤醒等待中的G
121                // Go back to draining, this time
122                // without preemption.
123                gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)
124            case gcMarkWorkerFractionalMode:
125                // 这个模式下P应该适当执行标记
126                // 执行标记, 直到被抢占, 并且需要计算后台的扫描量来减少辅助GC和唤醒等待中的G
127                gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
128            case gcMarkWorkerIdleMode:
129                // 这个模式下P只在空闲时执行标记
130                // 执行标记, 直到被抢占或者达到一定的量, 并且需要计算后台的扫描量来减少辅助GC和唤醒等待中的G
131                gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
132            }
133            // 恢复G的状态到运行中
134            casgstatus(gp, _Gwaiting, _Grunning)
135        })
136        // 如果标记了禁止本地标记队列则flush到全局标记队列
137        // If we are nearing the end of mark, dispose
138        // of the cache promptly. We must do this
139        // before signaling that we're no longer
140        // working so that other workers can't observe
141        // no workers and no work while we have this
142        // cached, and before we compute done.
143        if gcBlackenPromptly {
144            _p_.gcw.dispose()
145        }
146        // 累加所用时间
147        // Account for time.
148        duration := nanotime() - startTime
149        switch _p_.gcMarkWorkerMode {
150        case gcMarkWorkerDedicatedMode:
151            atomic.Xaddint64(&gcController.dedicatedMarkTime, duration)
152            atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 1)
153        case gcMarkWorkerFractionalMode:
154            atomic.Xaddint64(&gcController.fractionalMarkTime, duration)
155            atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 1)
156        case gcMarkWorkerIdleMode:
157            atomic.Xaddint64(&gcController.idleMarkTime, duration)
158        }
159        // Was this the last worker and did we run out
160        // of work?
161        incnwait := atomic.Xadd(&work.nwait, +1)
162        if incnwait > work.nproc {
163            println("runtime: p.gcMarkWorkerMode=", _p_.gcMarkWorkerMode,
164                "work.nwait=", incnwait, "work.nproc=", work.nproc)
165            throw("work.nwait > work.nproc")
166        }
167        // 判断是否所有后台标记任务都完成, 并且没有更多的任务
168        // If this worker reached a background mark completion
169        // point, signal the main GC goroutine.
170        if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
171            // 取消和P的关联
172            // Make this G preemptible and disassociate it
173            // as the worker for this P so
174            // findRunnableGCWorker doesn't try to
175            // schedule it.
176            _p_.gcBgMarkWorker.set(nil)
177            // 允许G被抢占
178            releasem(park.m.ptr())
179            // 准备进入完成标记阶段
180            gcMarkDone()
181            // 休眠之前会重新关联P
182            // 因为上面允许被抢占, 到这里的时候可能就会变成其他P
183            // 如果重新关联P失败则这个任务会结束
184            // Disable preemption and prepare to reattach
185            // to the P.
186            //
187            // We may be running on a different P at this
188            // point, so we can't reattach until this G is
189            // parked.
190            park.m.set(acquirem())
191            park.attach.set(_p_)
192        }
193    }
194}

gcDrain函数用于执行标记:

  1// gcDrain scans roots and objects in work buffers, blackening grey
  2// objects until all roots and work buffers have been drained.
  3//
  4// If flags&gcDrainUntilPreempt != 0, gcDrain returns when g.preempt
  5// is set. This implies gcDrainNoBlock.
  6//
  7// If flags&gcDrainIdle != 0, gcDrain returns when there is other work
  8// to do. This implies gcDrainNoBlock.
  9//
 10// If flags&gcDrainNoBlock != 0, gcDrain returns as soon as it is
 11// unable to get more work. Otherwise, it will block until all
 12// blocking calls are blocked in gcDrain.
 13//
 14// If flags&gcDrainFlushBgCredit != 0, gcDrain flushes scan work
 15// credit to gcController.bgScanCredit every gcCreditSlack units of
 16// scan work.
 17//
 18//go:nowritebarrier
 19func gcDrain(gcw *gcWork, flags gcDrainFlags) {
 20    if !writeBarrier.needed {
 21        throw("gcDrain phase incorrect")
 22    }
 23    gp := getg().m.curg
 24    // 看到抢占标志时是否要返回
 25    preemptible := flags&gcDrainUntilPreempt != 0
 26    // 没有任务时是否要等待任务
 27    blocking := flags&(gcDrainUntilPreempt|gcDrainIdle|gcDrainNoBlock) == 0
 28    // 是否计算后台的扫描量来减少辅助GC和唤醒等待中的G
 29    flushBgCredit := flags&gcDrainFlushBgCredit != 0
 30    // 是否只执行一定量的工作
 31    idle := flags&gcDrainIdle != 0
 32    // 记录初始的已扫描数量
 33    initScanWork := gcw.scanWork
 34    // 扫描idleCheckThreshold(100000)个对象以后检查是否要返回
 35    // idleCheck is the scan work at which to perform the next
 36    // idle check with the scheduler.
 37    idleCheck := initScanWork + idleCheckThreshold
 38    // 如果根对象未扫描完, 则先扫描根对象
 39    // Drain root marking jobs.
 40    if work.markrootNext < work.markrootJobs {
 41        // 如果标记了preemptible, 循环直到被抢占
 42        for !(preemptible && gp.preempt) {
 43            // 从根对象扫描队列取出一个值(原子递增)
 44            job := atomic.Xadd(&work.markrootNext, +1) - 1
 45            if job >= work.markrootJobs {
 46                break
 47            }
 48            // 执行根对象扫描工作
 49            markroot(gcw, job)
 50            // 如果是idle模式并且有其他工作, 则返回
 51            if idle && pollWork() {
 52                goto done
 53            }
 54        }
 55    }
 56    // 根对象已经在标记队列中, 消费标记队列
 57    // 如果标记了preemptible, 循环直到被抢占
 58    // Drain heap marking jobs.
 59    for !(preemptible && gp.preempt) {
 60        // 如果全局标记队列为空, 把本地标记队列的一部分工作分过去
 61        // (如果wbuf2不为空则移动wbuf2过去, 否则移动wbuf1的一半过去)
 62        // Try to keep work available on the global queue. We used to
 63        // check if there were waiting workers, but it's better to
 64        // just keep work available than to make workers wait. In the
 65        // worst case, we'll do O(log(_WorkbufSize)) unnecessary
 66        // balances.
 67        if work.full == 0 {
 68            gcw.balance()
 69        }
 70        // 从本地标记队列中获取对象, 获取不到则从全局标记队列获取
 71        var b uintptr
 72        if blocking {
 73            // 阻塞获取
 74            b = gcw.get()
 75        } else {
 76            // 非阻塞获取
 77            b = gcw.tryGetFast()
 78            if b == 0 {
 79                b = gcw.tryGet()
 80            }
 81        }
 82        // 获取不到对象, 标记队列已为空, 跳出循环
 83        if b == 0 {
 84            // work barrier reached or tryGet failed.
 85            break
 86        }
 87        // 扫描获取到的对象
 88        scanobject(b, gcw)
 89        // 如果已经扫描了一定数量的对象(gcCreditSlack的值是2000)
 90        // Flush background scan work credit to the global
 91        // account if we've accumulated enough locally so
 92        // mutator assists can draw on it.
 93        if gcw.scanWork >= gcCreditSlack {
 94            // 把扫描的对象数量添加到全局
 95            atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)
 96            // 减少辅助GC的工作量和唤醒等待中的G
 97            if flushBgCredit {
 98                gcFlushBgCredit(gcw.scanWork - initScanWork)
 99                initScanWork = 0
100            }
101            idleCheck -= gcw.scanWork
102            gcw.scanWork = 0
103            // 如果是idle模式且达到了检查的扫描量, 则检查是否有其他任务(G), 如果有则跳出循环
104            if idle && idleCheck <= 0 {
105                idleCheck += idleCheckThreshold
106                if pollWork() {
107                    break
108                }
109            }
110        }
111    }
112    // In blocking mode, write barriers are not allowed after this
113    // point because we must preserve the condition that the work
114    // buffers are empty.
115done:
116    // 把扫描的对象数量添加到全局
117    // Flush remaining scan work credit.
118    if gcw.scanWork > 0 {
119        atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)
120        // 减少辅助GC的工作量和唤醒等待中的G
121        if flushBgCredit {
122            gcFlushBgCredit(gcw.scanWork - initScanWork)
123        }
124        gcw.scanWork = 0
125    }
126}

markroot函数用于执行根对象扫描工作:

  1// markroot scans the i'th root.
  2//
  3// Preemption must be disabled (because this uses a gcWork).
  4//
  5// nowritebarrier is only advisory here.
  6//
  7//go:nowritebarrier
  8func markroot(gcw *gcWork, i uint32) {
  9    // 判断取出的数值对应哪种任务
 10    // (google的工程师觉得这种办法可笑)
 11    // TODO(austin): This is a bit ridiculous. Compute and store
 12    // the bases in gcMarkRootPrepare instead of the counts.
 13    baseFlushCache := uint32(fixedRootCount)
 14    baseData := baseFlushCache + uint32(work.nFlushCacheRoots)
 15    baseBSS := baseData + uint32(work.nDataRoots)
 16    baseSpans := baseBSS + uint32(work.nBSSRoots)
 17    baseStacks := baseSpans + uint32(work.nSpanRoots)
 18    end := baseStacks + uint32(work.nStackRoots)
 19    // Note: if you add a case here, please also update heapdump.go:dumproots.
 20    switch {
 21    // 释放mcache中的所有span, 要求STW
 22    case baseFlushCache <= i && i < baseData:
 23        flushmcache(int(i - baseFlushCache))
 24    // 扫描可读写的全局变量
 25    // 这里只会扫描i对应的block, 扫描时传入包含哪里有指针的bitmap数据
 26    case baseData <= i && i < baseBSS:
 27        for _, datap := range activeModules() {
 28            markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-baseData))
 29        }
 30    // 扫描只读的全局变量
 31    // 这里只会扫描i对应的block, 扫描时传入包含哪里有指针的bitmap数据
 32    case baseBSS <= i && i < baseSpans:
 33        for _, datap := range activeModules() {
 34            markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-baseBSS))
 35        }
 36    // 扫描析构器队列
 37    case i == fixedRootFinalizers:
 38        // Only do this once per GC cycle since we don't call
 39        // queuefinalizer during marking.
 40        if work.markrootDone {
 41            break
 42        }
 43        for fb := allfin; fb != nil; fb = fb.alllink {
 44            cnt := uintptr(atomic.Load(&fb.cnt))
 45            scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw)
 46        }
 47    // 释放已中止的G的栈
 48    case i == fixedRootFreeGStacks:
 49        // Only do this once per GC cycle; preferably
 50        // concurrently.
 51        if !work.markrootDone {
 52            // Switch to the system stack so we can call
 53            // stackfree.
 54            systemstack(markrootFreeGStacks)
 55        }
 56    // 扫描各个span中特殊对象(析构器列表)
 57    case baseSpans <= i && i < baseStacks:
 58        // mark MSpan.specials
 59        markrootSpans(gcw, int(i-baseSpans))
 60    // 扫描各个G的栈
 61    default:
 62        // 获取需要扫描的G
 63        // the rest is scanning goroutine stacks
 64        var gp *g
 65        if baseStacks <= i && i < end {
 66            gp = allgs[i-baseStacks]
 67        } else {
 68            throw("markroot: bad index")
 69        }
 70        // 记录等待开始的时间
 71        // remember when we've first observed the G blocked
 72        // needed only to output in traceback
 73        status := readgstatus(gp) // We are not in a scan state
 74        if (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {
 75            gp.waitsince = work.tstart
 76        }
 77        // 切换到g0运行(有可能会扫到自己的栈)
 78        // scang must be done on the system stack in case
 79        // we're trying to scan our own stack.
 80        systemstack(func() {
 81            // 判断扫描的栈是否自己的
 82            // If this is a self-scan, put the user G in
 83            // _Gwaiting to prevent self-deadlock. It may
 84            // already be in _Gwaiting if this is a mark
 85            // worker or we're in mark termination.
 86            userG := getg().m.curg
 87            selfScan := gp == userG && readgstatus(userG) == _Grunning
 88            // 如果正在扫描自己的栈则切换状态到等待中防止死锁
 89            if selfScan {
 90                casgstatus(userG, _Grunning, _Gwaiting)
 91                userG.waitreason = "garbage collection scan"
 92            }
 93            // 扫描G的栈
 94            // TODO: scang blocks until gp's stack has
 95            // been scanned, which may take a while for
 96            // running goroutines. Consider doing this in
 97            // two phases where the first is non-blocking:
 98            // we scan the stacks we can and ask running
 99            // goroutines to scan themselves; and the
100            // second blocks.
101            scang(gp, gcw)
102            // 如果正在扫描自己的栈则把状态切换回运行中
103            if selfScan {
104                casgstatus(userG, _Gwaiting, _Grunning)
105            }
106        })
107    }
108}

scang函数负责扫描G的栈:

 1// scang blocks until gp's stack has been scanned.
 2// It might be scanned by scang or it might be scanned by the goroutine itself.
 3// Either way, the stack scan has completed when scang returns.
 4func scang(gp *g, gcw *gcWork) {
 5    // Invariant; we (the caller, markroot for a specific goroutine) own gp.gcscandone.
 6    // Nothing is racing with us now, but gcscandone might be set to true left over
 7    // from an earlier round of stack scanning (we scan twice per GC).
 8    // We use gcscandone to record whether the scan has been done during this round.
 9    // 标记扫描未完成
10    gp.gcscandone = false
11    // See http://golang.org/cl/21503 for justification of the yield delay.
12    const yieldDelay = 10 * 1000
13    var nextYield int64
14    // 循环直到扫描完成
15    // Endeavor to get gcscandone set to true,
16    // either by doing the stack scan ourselves or by coercing gp to scan itself.
17    // gp.gcscandone can transition from false to true when we're not looking
18    // (if we asked for preemption), so any time we lock the status using
19    // castogscanstatus we have to double-check that the scan is still not done.
20loop:
21    for i := 0; !gp.gcscandone; i++ {
22        // 判断G的当前状态
23        switch s := readgstatus(gp); s {
24        default:
25            dumpgstatus(gp)
26            throw("stopg: invalid status")
27        // G已中止, 不需要扫描它
28        case _Gdead:
29            // No stack.
30            gp.gcscandone = true
31            break loop
32        // G的栈正在扩展, 下一轮重试
33        case _Gcopystack:
34        // Stack being switched. Go around again.
35        // G不是运行中, 首先需要防止它运行
36        case _Grunnable, _Gsyscall, _Gwaiting:
37            // Claim goroutine by setting scan bit.
38            // Racing with execution or readying of gp.
39            // The scan bit keeps them from running
40            // the goroutine until we're done.
41            if castogscanstatus(gp, s, s|_Gscan) {
42                // 原子切换状态成功时扫描它的栈
43                if !gp.gcscandone {
44                    scanstack(gp, gcw)
45                    gp.gcscandone = true
46                }
47                // 恢复G的状态, 并跳出循环
48                restartg(gp)
49                break loop
50            }
51        // G正在扫描它自己, 等待扫描完毕
52        case _Gscanwaiting:
53        // newstack is doing a scan for us right now. Wait.
54        // G正在运行
55        case _Grunning:
56            // Goroutine running. Try to preempt execution so it can scan itself.
57            // The preemption handler (in newstack) does the actual scan.
58            // 如果已经有抢占请求, 则抢占成功时会帮我们处理
59            // Optimization: if there is already a pending preemption request
60            // (from the previous loop iteration), don't bother with the atomics.
61            if gp.preemptscan && gp.preempt && gp.stackguard0 == stackPreempt {
62                break
63            }
64            // 抢占G, 抢占成功时G会扫描它自己
65            // Ask for preemption and self scan.
66            if castogscanstatus(gp, _Grunning, _Gscanrunning) {
67                if !gp.gcscandone {
68                    gp.preemptscan = true
69                    gp.preempt = true
70                    gp.stackguard0 = stackPreempt
71                }
72                casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning)
73            }
74        }
75        // 第一轮休眠10毫秒, 第二轮休眠5毫秒
76        if i == 0 {
77            nextYield = nanotime() + yieldDelay
78        }
79        if nanotime() < nextYield {
80            procyield(10)
81        } else {
82            osyield()
83            nextYield = nanotime() + yieldDelay/2
84        }
85    }
86    // 扫描完成, 取消抢占扫描的请求
87    gp.preemptscan = false // cancel scan request if no longer needed
88}

设置preemptscan后, 在抢占G成功时会调用scanstack扫描它自己的栈, 具体代码在这里.
扫描栈用的函数是scanstack:

 1// scanstack scans gp's stack, greying all pointers found on the stack.
 2//
 3// scanstack is marked go:systemstack because it must not be preempted
 4// while using a workbuf.
 5//
 6//go:nowritebarrier
 7//go:systemstack
 8func scanstack(gp *g, gcw *gcWork) {
 9    if gp.gcscanvalid {
10        return
11    }
12    if readgstatus(gp)&_Gscan == 0 {
13        print("runtime:scanstack: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", hex(readgstatus(gp)), "\n")
14        throw("scanstack - bad status")
15    }
16    switch readgstatus(gp) &^ _Gscan {
17    default:
18        print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
19        throw("mark - bad status")
20    case _Gdead:
21        return
22    case _Grunning:
23        print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
24        throw("scanstack: goroutine not stopped")
25    case _Grunnable, _Gsyscall, _Gwaiting:
26        // ok
27    }
28    if gp == getg() {
29        throw("can't scan our own stack")
30    }
31    mp := gp.m
32    if mp != nil && mp.helpgc != 0 {
33        throw("can't scan gchelper stack")
34    }
35    // Shrink the stack if not much of it is being used. During
36    // concurrent GC, we can do this during concurrent mark.
37    if !work.markrootDone {
38        shrinkstack(gp)
39    }
40    // Scan the stack.
41    var cache pcvalueCache
42    scanframe := func(frame *stkframe, unused unsafe.Pointer) bool {
43        // scanframeworker会根据代码地址(pc)获取函数信息
44        // 然后找到函数信息中的stackmap.bytedata, 它保存了函数的栈上哪些地方有指针
45        // 再调用scanblock来扫描函数的栈空间, 同时函数的参数也会这样扫描
46        scanframeworker(frame, &cache, gcw)
47        return true
48    }
49    // 枚举所有调用帧, 分别调用scanframe函数
50    gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
51    // 枚举所有defer的调用帧, 分别调用scanframe函数
52    tracebackdefers(gp, scanframe, nil)
53    gp.gcscanvalid = true
54}

scanblock函数是一个通用的扫描函数, 扫描全局变量和栈空间都会用它, 和scanobject不同的是bitmap需要手动传入:

 1// scanblock scans b as scanobject would, but using an explicit
 2// pointer bitmap instead of the heap bitmap.
 3//
 4// This is used to scan non-heap roots, so it does not update
 5// gcw.bytesMarked or gcw.scanWork.
 6//
 7//go:nowritebarrier
 8func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {
 9    // Use local copies of original parameters, so that a stack trace
10    // due to one of the throws below shows the original block
11    // base and extent.
12    b := b0
13    n := n0
14    arena_start := mheap_.arena_start
15    arena_used := mheap_.arena_used
16    // 枚举扫描的地址
17    for i := uintptr(0); i < n; {
18        // 找到bitmap中对应的byte
19        // Find bits for the next word.
20        bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8)))
21        if bits == 0 {
22            i += sys.PtrSize * 8
23            continue
24        }
25        // 枚举byte
26        for j := 0; j < 8 && i < n; j++ {
27            // 如果该地址包含指针
28            if bits&1 != 0 {
29                // 标记在该地址的对象存活, 并把它加到标记队列(该对象变为灰色)
30                // Same work as in scanobject; see comments there.
31                obj := *(*uintptr)(unsafe.Pointer(b + i))
32                if obj != 0 && arena_start <= obj && obj < arena_used {
33                    // 找到该对象对应的span和bitmap
34                    if obj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0 {
35                        // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)
36                        greyobject(obj, b, i, hbits, span, gcw, objIndex)
37                    }
38                }
39            }
40            // 处理下一个指针下一个bit
41            bits >>= 1
42            i += sys.PtrSize
43        }
44    }
45}

greyobject用于标记一个对象存活, 并把它加到标记队列(该对象变为灰色):

 1// obj is the start of an object with mark mbits.
 2// If it isn't already marked, mark it and enqueue into gcw.
 3// base and off are for debugging only and could be removed.
 4//go:nowritebarrierrec
 5func greyobject(obj, base, off uintptr, hbits heapBits, span *mspan, gcw *gcWork, objIndex uintptr) {
 6    // obj should be start of allocation, and so must be at least pointer-aligned.
 7    if obj&(sys.PtrSize-1) != 0 {
 8        throw("greyobject: obj not pointer-aligned")
 9    }
10    mbits := span.markBitsForIndex(objIndex)
11    if useCheckmark {
12        // checkmark是用于检查是否所有可到达的对象都被正确标记的机制, 仅除错使用
13        if !mbits.isMarked() {
14            printlock()
15            print("runtime:greyobject: checkmarks finds unexpected unmarked object obj=", hex(obj), "\n")
16            print("runtime: found obj at *(", hex(base), "+", hex(off), ")\n")
17            // Dump the source (base) object
18            gcDumpObject("base", base, off)
19            // Dump the object
20            gcDumpObject("obj", obj, ^uintptr(0))
21            getg().m.traceback = 2
22            throw("checkmark found unmarked object")
23        }
24        if hbits.isCheckmarked(span.elemsize) {
25            return
26        }
27        hbits.setCheckmarked(span.elemsize)
28        if !hbits.isCheckmarked(span.elemsize) {
29            throw("setCheckmarked and isCheckmarked disagree")
30        }
31    } else {
32        if debug.gccheckmark > 0 && span.isFree(objIndex) {
33            print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")
34            gcDumpObject("base", base, off)
35            gcDumpObject("obj", obj, ^uintptr(0))
36            getg().m.traceback = 2
37            throw("marking free object")
38        }
39        // 如果对象所在的span中的gcmarkBits对应的bit已经设置为1则可以跳过处理
40        // If marked we have nothing to do.
41        if mbits.isMarked() {
42            return
43        }
44        // 设置对象所在的span中的gcmarkBits对应的bit为1
45        // mbits.setMarked() // Avoid extra call overhead with manual inlining.
46        atomic.Or8(mbits.bytep, mbits.mask)
47        // 如果确定对象不包含指针(所在span的类型是noscan), 则不需要把对象放入标记队列
48        // If this is a noscan object, fast-track it to black
49        // instead of greying it.
50        if span.spanclass.noscan() {
51            gcw.bytesMarked += uint64(span.elemsize)
52            return
53        }
54    }
55    // 把对象放入标记队列
56    // 先放入本地标记队列, 失败时把本地标记队列中的部分工作转移到全局标记队列, 再放入本地标记队列
57    // Queue the obj for scanning. The PREFETCH(obj) logic has been removed but
58    // seems like a nice optimization that can be added back in.
59    // There needs to be time between the PREFETCH and the use.
60    // Previously we put the obj in an 8 element buffer that is drained at a rate
61    // to give the PREFETCH time to do its work.
62    // Use of PREFETCHNTA might be more appropriate than PREFETCH
63    if !gcw.putFast(obj) {
64        gcw.put(obj)
65    }
66}

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。


Golang语言社区

ID:GolangWeb

www.ByteEdu.Com

游戏服务器架构丨分布式技术丨大数据丨游戏算法学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值