// Standard syscall entry used by the go syscall library and normal cgo calls.//// This is exported via linkname to assembly in the syscall package.////go:nosplit//go:linkname entersyscallfuncentersyscall() {
reentersyscall(getcallerpc(), getcallersp())
}
//go:nosplitfuncreentersyscall(pc, sp uintptr) {
_g_ :=getg()
// Disable preemption because during this function g is in Gsyscall status,// but can have inconsistent g->sched, do not let GC observe it. _g_.m.locks++// Entersyscall must not call any function that might split/grow the stack.// (See details in comment above.)// Catch calls that might, by replacing the stack guard with something that// will trip any stack check and leaving a flag to tell newstack to die. _g_.stackguard0 = stackPreempt
_g_.throwsplit = true// Leave SP around for GC and traceback.// 更新 g.sched 相关信息,后期需要读取这些信息save(pc, sp)
// 记录g的系统调用SP/PC _g_.syscallsp = sp
_g_.syscallpc = pc
// 设置g的状态为 _Gsyscallcasgstatus(_g_, _Grunning, _Gsyscall)
if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
systemstack(func() {
print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]n")
throw("entersyscall")
})
}
if trace.enabled {
systemstack(traceGoSysCall)
// systemstack itself clobbers g.sched.{pc,sp} and we might// need them later when the G is genuinely blocked in a// syscallsave(pc, sp)
}
if atomic.Load(&sched.sysmonwait) !=0 {
systemstack(entersyscall_sysmon)
save(pc, sp)
}
if _g_.m.p.ptr().runSafePointFn !=0 {
// runSafePointFn may stack split if run on this stacksystemstack(runSafePointFn)
save(pc, sp)
}
// 更新与当前g关联P的执行调用系统调用次数 _g_.m.syscalltick = _g_.m.p.ptr().syscalltick
_g_.sysblocktraced = true// 设置当前 m 所关联的 P 为下次优先使用的P(m.oldp = p), 实现亲和性 pp := _g_.m.p.ptr()
pp.m = 0 _g_.m.oldp.set(pp)
// 解除 m 与 p 的关联 _g_.m.p = 0// 修改P的状态为 _Psyscall atomic.Store(&pp.status, _Psyscall)
if sched.gcwaiting !=0 {
systemstack(entersyscall_gcwait)
save(pc, sp)
}
_g_.m.locks--}
主要有以下工作:
在开始前需要进行 _g_.m.locks++, 以防止GC,函数执行结束时再减少一个锁
要进行禁止栈分裂
通过 save() 函数将当前调度需要的信息暂存到 _g_.sched
修改 G 和 P 的状态为系统调用状态
将本次 m 绑定的 P 保存到 _g_.oldp 字段中,以备系统调用完毕后可以优先使用当前P继续执行
当一个goroutine系统调用结束后,需要再次将GM重新与P关联继续执行,Golang 为了亲和性,会优先与上次执行的P绑定,如果上次关联的 P 正在被使用,这时再考虑重新找一个新的P 关联。
1
2
3
4
5
6
7
8
9
10
11
12
13
// The goroutine g exited its system call.// Arrange for it to run on a cpu again.// This is called only from the go syscall library, not// from the low-level system calls used by the runtime.//// Write barriers are not allowed because our P may have been stolen.//// This is exported via linkname to assembly in the syscall package.////go:nosplit//go:nowritebarrierrec//go:linkname exitsyscallfuncexitsyscall() {}
funcexitsyscall() {
_g_ :=getg()
_g_.m.locks++// see comment in entersyscallifgetcallersp() > _g_.syscallsp {
throw("exitsyscall: syscall frame is no longer valid")
}
// 重置g阻塞时间为0 _g_.waitsince = 0// 取出上次关联的P,优先使用 oldp := _g_.m.oldp.ptr()
_g_.m.oldp = 0// 与原来的P进行关联ifexitsyscallfast(oldp) {
if trace.enabled {
if oldp != _g_.m.p.ptr() || _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
systemstack(traceGoStart)
}
}
// There's a cpu for us, so we can run. _g_.m.p.ptr().syscalltick++// We need to cas the status and scan before resuming...// G 的状态恢复为 _Grunningcasgstatus(_g_, _Gsyscall, _Grunning)
// Garbage collector isn't running (since we are),// so okay to clear syscallsp. _g_.syscallsp = 0 _g_.m.locks--if _g_.preempt {
// restore the preemption request in case we've cleared it in newstack _g_.stackguard0 = stackPreempt
} else {
// otherwise restore the real _StackGuard, we've spoiled it in entersyscall/entersyscallblock _g_.stackguard0 = _g_.stack.lo + _StackGuard
}
_g_.throwsplit = falseif sched.disable.user && !schedEnabled(_g_) {
// Scheduling of this goroutine is disabled.Gosched()
}
return }
_g_.sysexitticks = 0if trace.enabled {
// Wait till traceGoSysBlock event is emitted.// This ensures consistency of the trace (the goroutine is started after it is blocked).for oldp !=nil&& oldp.syscalltick == _g_.m.syscalltick {
osyield()
}
// We can't trace syscall exit right now because we don't have a P.// Tracing code can invoke write barriers that cannot run without a P.// So instead we remember the syscall exit time and emit the event// in execute when we have a P. _g_.sysexitticks = cputicks()
}
_g_.m.locks--// Call the scheduler.// 在g0上调用 exitsyscall0() 函数, 执行流程到这里的话,说明并没有找到空闲的P,此时需要将G和M进行解绑,分别入相应的队列等待下次执行mcall(exitsyscall0)
// Scheduler returned, so we're allowed to run now.// Delete the syscallsp information that we left for// the garbage collector during the system call.// Must wait until now because until gosched returns// we don't know for sure that the garbage collector// is not running.// 以下信息必须等待调度器返回才可以重置,gc考虑 _g_.syscallsp = 0 _g_.m.p.ptr().syscalltick++ _g_.throwsplit = false}
//go:nosplitfuncexitsyscallfast(oldp *p) bool {
_g_ :=getg()
// Freezetheworld sets sweightwait but does not retake P's.if sched.sweightwait == freezeSweightWait {
returnfalse }
// Try to re-acquire the last P.// 尝试与上次的P关联if oldp !=nil&& oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
// There's a cpu for us, so we can run.wirep(oldp)
exitsyscallfast_reacquired()
returntrue }
// Try to get any other idle P.if sched.pidle !=0 {
var ok boolsystemstack(func() {
// 获取另一个P ok = exitsyscallfast_pidle()
if ok && trace.enabled {
if oldp !=nil {
// Wait till traceGoSysBlock event is emitted.// This ensures consistency of the trace (the goroutine is started after it is blocked).for oldp.syscalltick == _g_.m.syscalltick {
osyield()
}
}
traceGoSysExit(0)
}
})
if ok {
returntrue }
}
returnfalse}
// Try get a p from _Pidle list.// Sched must be locked.// May run during STW, so write barriers are not allowed.//go:nowritebarrierrecfuncpidleget() *p {
_p_ := sched.pidle.ptr()
if _p_ !=nil {
sched.pidle = _p_.link
atomic.Xadd(&sched.npidle, -1) // TODO: fast atomic }
return _p_
}