Go Version: 1.17 System: MacOS signal in the program is nothing more than registration, signal sending and signal processing these three processes, the following process is analyzed one by one
Registration of signals
func mstart1(a){...if _g_.m == &m0 {
mstartm0()
}
...
}
Copy the code
func mstartm0(a){... initsig(false)}Copy the code
func initsig(preinit bool) {
if! preinit {// It's now OK for signal handlers to run.
signalsOK = true
}
// For c-archive/c-shared this is called by libpreinit with
// preinit == true.
if(isarchive || islibrary) && ! preinit {return
}
for i := uint32(0); i < _NSIG; i++ {
t := &sigtable[i]
if t.flags == 0|| t.flags&_SigDefault ! =0 {
continue
}
// We don't need to use atomic operations here because
// there shouldn't be any other goroutines running yet.
fwdSig[i] = getsig(i)
// Check if this signal needs to be set to signal handler
if! sigInstallGoHandler(i) {// Even if we are not installing a signal handler,
// set SA_ONSTACK if necessary.
iffwdSig[i] ! = _SIG_DFL && fwdSig[i] ! = _SIG_IGN { setsigstack(i) }else if fwdSig[i] == _SIG_IGN {
sigInitIgnored(i)
}
continue
}
handlingSig[i] = 1
setsig(i, funcPC(sighandler))
}
}
Copy the code
Sighandler is set for a signal by setsig
func setsig(i uint32, fn uintptr) {
var sa usigactiont
sa.sa_flags = _SA_SIGINFO | _SA_ONSTACK | _SA_RESTART
sa.sa_mask = ^uint32(0)
if fn == funcPC(sighandler) { // funcPC(sighandler) matches the callers in signal_unix.go
if iscgo {
fn = abi.FuncPCABI0(cgoSigtramp)
} else {
// sighandler is replaced with sigtramp
fn = abi.FuncPCABI0(sigtramp)
}
}
*(*uintptr)(unsafe.Pointer(&sa.__sigaction_u)) = fn
// The signal is registered here, and sigtramp is called when the signal is received
sigaction(i, &sa, nil)}Copy the code
At this point, the signal is registered, and note that when FN is sighandler, it is replaced by sigtramp
Processing after receiving the signal
// This is the function registered during sigaction and is invoked when
// a signal is received. It just redirects to the Go function sigtrampgo.
// Called using C ABI.The TEXT, the runtime sigtramp (SB), NOSPLIT, $0
// Transition from C ABI to Go ABI.
PUSH_REGS_HOST_TO_ABI0()
// Call into the Go signal handler
NOP SP // disable vet stack checking
ADJSP $24
MOVL DI, 0(SP) // sig
MOVQ SI, 8(SP) // info
MOVQ DX, 16(SP) // ctxCALL · sigtrampgo ADJSP $(SB)- 24
POP_REGS_HOST_TO_ABI0()
RET
Copy the code
Upon receiving the signal, the previously registered function SIGtramp is called and sigtrampgo continues to be called
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
if sigfwdgo(sig, info, ctx) {
return
}
c := &sigctxt{info, ctx}
g := sigFetchG(c)
setg(g)
...
sighandler(sig, info, ctx, g)
setg(g)
...
Copy the code
func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
_g_ := getg()
c := &sigctxt{info, ctxt}
if sig == _SIGPROF {
sigprof(c.sigpc(), c.sigsp(), c.siglr(), gp, _g_.m)
return
}
ifsig == _SIGTRAP && testSigtrap ! =nil && testSigtrap(info, (*sigctxt)(noescape(unsafe.Pointer(c))), gp) {
return
}
ifsig == _SIGUSR1 && testSigusr1 ! =nil && testSigusr1(gp) {
return
}
// represents a preemption signal
if sig == sigPreempt && debug.asyncpreemptoff == 0 {
// Might be a preemption signal.
doSigPreempt(gp, c)
// Even if this was definitely a preemption signal, it
// may have been coalesced with another signal, so we
// still let it through to the application.}}Copy the code
If the received signal is a preemption signal, doSigPreempt is called
// doSigPreempt handles a preemption signal on gp.
func doSigPreempt(gp *g, ctxt *sigctxt) {
// Check if this G wants to be preempted and is safe to
// preempt.
// Check if g is to be preempted and can be preempted safely
if wantAsyncPreempt(gp) {
if ok, newpc := isAsyncSafePoint(gp, ctxt.sigpc(), ctxt.sigsp(), ctxt.siglr()); ok {
// Adjust the PC and inject a call to asyncPreempt.
ctxt.pushCall(funcPC(asyncPreempt), newpc)
}
}
// Acknowledge the preemption.
atomic.Xadd(&gp.m.preemptGen, 1)
atomic.Store(&gp.m.signalPending, 0)
if GOOS == "darwin" || GOOS == "ios" {
atomic.Xadd(&pendingPreemptSignals, - 1)}}Copy the code
func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
// Make it look like we called target at resumePC.
sp := uintptr(c.rsp())
sp -= sys.PtrSize
*(*uintptr)(unsafe.Pointer(sp)) = resumePC
c.set_rsp(uint64(sp))
c.set_rip(uint64(targetPC))
}
Copy the code
pushCall
This function is crucial. He does it by changingPC register
To perform a preemption call
TEXT, asyncPreempt < ABIInternal > (SB), NOSPLIT | NOFRAME, $00
PUSHQ BP
MOVQ SP, BP
// Save flags before clobbering them
PUSHFQ
// obj doesn't understand ADD/SUB on SP, but does understand ADJSP
ADJSP $368
// But vet doesn't know ADJSP, so suppress vet stack checking
NOP SP
MOVQ AX, 0(SP)
MOVQ CX, 8(SP) ... MOVQ R14, MOVQ R14,96(SP)
MOVQ R15, 104(SP) #ifdef GOOS_darwin CMPB internal/CPU ·X86+const_offsetX86HasAVX(SB), $0
JE 2(PC)
VZEROUPPER
#endif
MOVUPS X0, 112(SP)
MOVUPS X1, 128(SP)
MOVUPS X2, 144(SP)
MOVUPS X3, 160(SP)
MOVUPS X4, 176(SP)
MOVUPS X5, 192(SP)
MOVUPS X6, 208(SP)
MOVUPS X7, 224(SP)
MOVUPS X8, 240(SP)
MOVUPS X9, 256(SP)
MOVUPS X10, 272(SP)
MOVUPS X11, 288(SP)
MOVUPS X12, 304(SP)
MOVUPS X13, 320(SP)
MOVUPS X14, 336(SP)
MOVUPS X15, 352(SP), CALL asyncPreempt2 MOVUPS (SB)352(SP), X15
MOVUPS 336(SP), X14 ... Restore the MOVQ value of all previously saved registers8(SP), CX
MOVQ 0(SP), AX
ADJSP $- 368.
POPFQ
POPQ BP
RET
Copy the code
All asyncPreempt does is save the scene, call asyncPreempt2, and restore the scene
//go:nosplit
func asyncPreempt2(a) {
gp := getg()
gp.asyncSafePoint = true
// preemptStop is set to true in GC stack scans
if gp.preemptStop {
mcall(preemptPark)
} else { // All preemption moves through this branch except for stack scanning
mcall(gopreempt_m)
}
gp.asyncSafePoint = false
}
Copy the code
AsyncPreempt2 has two branches, the next branch needs attention:
// preemptPark parks gp and puts it in _Gpreempted.
//
//go:systemstack
func preemptPark(gp *g){...// Transition from _Grunning to _Gscan|_Gpreempted. We can't
// be in _Grunning when we dropg because then we'd be running
// without an M, but the moment we're in _Gpreempted,
// something could claim this G before we've fully cleaned it
// up. Hence, we set the scan bit to lock down further
// transitions until we can dropg.
casGToPreemptScan(gp, _Grunning, _Gscan|_Gpreempted)
M = nil m.curg = nil
dropg()
// Change g's state to _Gpreempted
casfrom_Gscanstatus(gp, _Gscan|_Gpreempted, _Gpreempted)
schedule()
}
Copy the code
Branches 2:
func gopreempt_m(gp *g) {
if trace.enabled {
traceGoPreempt()
}
goschedImpl(gp)
}
Copy the code
func goschedImpl(gp *g) {
status := readgstatus(gp)
ifstatus&^_Gscan ! = _Grunning { dumpgstatus(gp) throw("bad g status")}// Change g's status to _Grunnable
casgstatus(gp, _Grunning, _Grunnable)
M = nil m.curg = nil
dropg()
lock(&sched.lock)
// put g at the end of the global queue
globrunqput(gp)
unlock(&sched.lock)
schedule()
}
Copy the code
Send a signal
Stack scan process
func markroot(gcw *gcWork, i uint32) {
// Note: if you add a case here, please also update heapdump.go:dumproots.
switch{...default:
// Get the g to scan
var gp *g
if work.baseStacks <= i && i < work.baseEnd {
gp = allgs[i-work.baseStacks]
} else {
throw("markroot: bad index")}...// scanstack must be done on the system stack in case
// we're trying to scan our own stack
// Switch to g0 for scanning
systemstack(func(a) {
// Suspend g to stop running
stopped := suspendG(gp)
if stopped.dead {
gp.gcscandone = true
return
}
if gp.gcscandone {
throw("g already scanned")}// Scan g's stack
scanstack(gp, gcw)
gp.gcscandone = true
// Restart gresumeG(stopped) ... }}})Copy the code
func suspendG(gp *g) suspendGState{...// Drive the goroutine to a preemption point.
stopped := false
var asyncM *m
var asyncGen uint32
var nextPreemptM int64
for i := 0; ; i++ {
switch s := readgstatus(gp); s {
/ / = = = (2) = = =
case _Gpreempted:
// We (or someone else) suspended the G. Claim
// ownership of it by transitioning it to
// _Gwaiting.
if! casGFromPreempted(gp, _Gpreempted, _Gwaiting) {break
}
// We stopped the G, so we have to ready it later.
stopped = true
s = _Gwaiting
fallthrough
/ / = = = (3) = = =
case _Grunnable, _Gsyscall, _Gwaiting:
// Claim goroutine by setting scan bit.
// This may race with execution or readying of gp.
// The scan bit keeps it from transition state.
if! castogscanstatus(gp, s, s|_Gscan) {break
}
// Clear the preemption request. It's safe to
// reset the stack guard because we hold the
// _Gscan bit and thus own the stack.
gp.preemptStop = false
gp.preempt = false
gp.stackguard0 = gp.stack.lo + _StackGuard
return suspendGState{g: gp, stopped: stopped}
/ / = = = = = =
case _Grunning:
// Optimization: if there is already a pending preemption request
// (from the previous loop iteration), don't bother with the atomics.
if gp.preemptStop && gp.preempt && gp.stackguard0 == stackPreempt && asyncM == gp.m && atomic.Load(&asyncM.preemptGen) == asyncGen {
break
}
// Temporarily block state transitions.
if! castogscanstatus(gp, _Grunning, _Gscanrunning) {break
}
// Request synchronous preemption.
// Set the preemption field
gp.preemptStop = true
gp.preempt = true
gp.stackguard0 = stackPreempt
// Prepare for asynchronous preemption.asyncM2 := gp.m asyncGen2 := atomic.Load(&asyncM2.preemptGen) needAsync := asyncM ! = asyncM2 || asyncGen ! = asyncGen2 asyncM = asyncM2 asyncGen = asyncGen2 casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning)// Send asynchronous preemption. We do this
// after CASing the G back to _Grunning
// because preemptM may be synchronous and we
// don't want to catch the G just spinning on
// its status.
if preemptMSupported && debug.asyncpreemptoff == 0 && needAsync {
// Rate limit preemptM calls. This is
// particularly important on Windows
// where preemptM is actually
// synchronous and the spin loop here
// can lead to live-lock.
now := nanotime()
// Limit preemption frequency
if now >= nextPreemptM {
nextPreemptM = now + yieldDelay/2
// Send preemption signalpreemptM(asyncM) } } } ... }}Copy the code
const sigPreempt = _SIGURG
func preemptM(mp *m){...if atomic.Cas(&mp.signalPending, 0.1) {
signalM(mp, sigPreempt)
}
...
}
Copy the code
func signalM(mp *m, sig int) {
pthread_kill(pthread(mp.procid), uint32(sig))
}
Copy the code
Set stop to true and fallthrough to continue to the next case block (suspendG) : Set preemptStop to false and return to suspendG, execute scanstack to start stack scan, and then resumeG to restore g
func resumeG(state suspendGState){...// Stopped was set to true earlier, so it will be entered here
if state.stopped {
// We stopped it, so we need to re-schedule it.
ready(gp, 0.true)}}Copy the code
func ready(gp *g, traceskip int, next bool) {
// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq
casgstatus(gp, _Gwaiting, _Grunnable)
// Next is true and puts the runnext of the current p
runqput(_g_.m.p.ptr(), gp, next)
wakep()
releasem(mp)
}
Copy the code
Other process
sysmon
-> retake
-> preemptone
func preemptone(_p_ *p) bool {
mp := _p_.m.ptr()
if mp == nil || mp == getg().m {
return false
}
// Get the executing g
gp := mp.curg
if gp == nil || gp == mp.g0 {
return false
}
gp.preempt = true
// Every call in a goroutine checks for stack overflow by
// comparing the current stack pointer to gp->stackguard0.
// Setting gp->stackguard0 to StackPreempt folds
// preemption into the normal stack overflow check.
gp.stackguard0 = stackPreempt
// Check whether the stack is preempted during expansion
// Request an async preemption of this P.
if preemptMSupported && debug.asyncpreemptoff == 0 {
_p_.preempt = true
preemptM(mp)
}
return true
}
Copy the code
func preemptM(mp *m) {
// On Darwin, don't try to preempt threads during exec.
// Issue #41702.
if GOOS == "darwin" || GOOS == "ios" {
execLock.rlock()
}
if atomic.Cas(&mp.signalPending, 0.1) {
if GOOS == "darwin" || GOOS == "ios" {
atomic.Xadd(&pendingPreemptSignals, 1)}// If multiple threads are preempting the same M, it may send many
// signals to the same M such that it hardly make progress, causing
// live-lock problem. Apparently this could happen on darwin. See
// issue #37741.
// Only send a signal if there isn't already one pending.
// Send preemption signal
signalM(mp, sigPreempt)
}
if GOOS == "darwin" || GOOS == "ios" {
execLock.runlock()
}
}
Copy the code
preemptall
-> preemptone
func preemptall(a) bool {
res := false
for _, _p_ := range allp {
if_p_.status ! = _Prunning {continue
}
// Preempt only g of running P
if preemptone(_p_) {
res = true}}return res
}
Copy the code
The following diagram illustrates the sending and processing of signals