具体的信号含义可以看这个介绍:Unix信号 https://zh.wikipedia.org/wiki/Unix信号。需要注意的是,抢占信号在这里是 _SigNotify + _SigIgn 如下:
{_SigNotify + _SigIgn, "SIGURG: urgent condition on socket"}下面我们看一下 setsig 函数,这个函数是在 runtime/os_linux.go文件里面:
setsig
func setsig(i uint32, fn uintptr) { var sa sigactiont sa.sa_flags = _SA_SIGINFO | _SA_ONSTACK | _SA_RESTORER | _SA_RESTART sigfillset(&sa.sa_mask) ... if fn == funcPC(sighandler) { // CGO 相关 if iscgo { fn = funcPC(cgoSigtramp) } else { // 替换为调用 sigtramp fn = funcPC(sigtramp) } } sa.sa_handler = fn sigaction(i, &sa, nil) }这里需要注意的是,当 fn 等于 sighandler 的时候,调用的函数会被替换成 sigtramp。sigaction 函数在 Linux 下会调用系统调用函数 sys_signal 以及 sys_rt_sigaction 实现安装信号。
执行抢占信号到了这里是信号发生的时候进行信号的处理,原本应该是在发送抢占信号之后,但是这里我先顺着安装信号往下先讲了。大家可以跳到发送抢占信号后再回来。
上面分析可以看到当 fn 等于 sighandler 的时候,调用的函数会被替换成 sigtramp,sigtramp是汇编实现,下面我们看看。
src/runtime/sys_linux_amd64.s:
TEXT runtime·sigtramp<ABIInternal>(SB),NOSPLIT,$72 ... // We don't save mxcsr or the x87 control word because sigtrampgo doesn't // modify them. MOVQ DX, ctx-56(SP) MOVQ SI, info-64(SP) MOVQ DI, signum-72(SP) MOVQ $runtime·sigtrampgo(SB), AX CALL AX ... RET这里会被调用说明信号已经发送响应了,runtime·sigtramp会进行信号的处理。runtime·sigtramp会继续调用 runtime·sigtrampgo 。
这个函数在 runtime/signal_unix.go文件中:
sigtrampgo&sighandler
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) { if sigfwdgo(sig, info, ctx) { return } c := &sigctxt{info, ctx} g := sigFetchG(c) ... sighandler(sig, info, ctx, g) setg(g) if setStack { restoreGsignalStack(&gsignalStack) } } func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) { _g_ := getg() c := &sigctxt{info, ctxt} ... // 如果是一个抢占信号 if sig == sigPreempt && debug.asyncpreemptoff == 0 { // 处理抢占信号 doSigPreempt(gp, c) } ... }sighandler 方法里面做了很多其他信号的处理工作,我们只关心抢占部分的代码,这里最终会通过 doSigPreempt 方法执行抢占。
这个函数在 runtime/signal_unix.go文件中:
doSigPreempt
func doSigPreempt(gp *g, ctxt *sigctxt) { // 检查此 G 是否要被抢占并且可以安全地抢占 if wantAsyncPreempt(gp) { // 检查是否能安全的进行抢占 if ok, newpc := isAsyncSafePoint(gp, ctxt.sigpc(), ctxt.sigsp(), ctxt.siglr()); ok { // 修改寄存器,并执行抢占调用 ctxt.pushCall(funcPC(asyncPreempt), newpc) } } // 更新一下抢占相关字段 atomic.Xadd(&gp.m.preemptGen, 1) atomic.Store(&gp.m.signalPending, 0) }函数会处理抢占信号,获取当前的 SP 和 PC 寄存器并调用 ctxt.pushCall修改寄存器,并调用 runtime/preempt.go 的 asyncPreempt 函数。
// 保存用户态寄存器后调用asyncPreempt2 func asyncPreempt()asyncPreempt 的汇编代码在 src/runtime/preempt_amd64.s中,该函数会保存用户态寄存器后调用 runtime/preempt.go 的 asyncPreempt2 函数中: