推荐文章:
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
游戏服务器架构丨分布式技术丨大数据丨游戏算法学习