gpt4 book ai didi

go - 如果修改信号处理程序中的ctx.rip和ctx.rsp会发生什么

转载 作者:行者123 更新时间:2023-12-01 22:27:12 25 4
gpt4 key购买 nike

众所周知,程序会被信号中断并进入内核空间,然后切换到用户空间信号处理程序。信号处理程序完成后,它将重新进入内核空间,然后切换回被中断的位置。

我最近正在阅读go 1.14中新实现的异步抢占,该抢占使用OS信号中断“非抢先”用户goroutine。我正在调试非常简单的程序:

package main

import (
"runtime"
"time"
)

func tightloop() {
for {
}
}

func main() {
runtime.GOMAXPROCS(1)
go tightloop()

time.Sleep(time.Millisecond)
println("OK")
runtime.Gosched()
}

在Go 1.14中,当抢占信号到达时,操作系统将中断 tightloop并进入预先配置的信号处理程序 runtime·sigtramp:
TEXT runtime·sigtramp(SB),NOSPLIT,$72
MOVQ DX, ctx-56(SP)
MOVQ SI, info-64(SP)
MOVQ DI, signum-72(SP)
MOVQ $runtime·sigtrampgo(SB), AX
CALL AX
RET

哪个 sigtrampgo最终称为 sighandler
//go:nosplit
//go:nowritebarrierrec
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
(...)
setg(g.m.gsignal)
(...)
sighandler(sig, info, ctx, g)
setg(g)
(...)
}

就我阅读的 sighandler函数而言,它调用 doSigPreempt并修改从系统内核传递的 ctx,并将 rip设置为 runtime.asyncPreempt的序言。
//go:nowritebarrierrec
func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
_g_ := getg()
c := &sigctxt{info, ctxt}

(...)
if sig == sigPreempt {
doSigPreempt(gp, c)
}
}
func doSigPreempt(gp *g, ctxt *sigctxt) {
if canPreempt {
// here modifies the rip and rsp
ctxt.pushCall(funcPC(asyncPreempt))
}

(...)
}

但是,我注意到asyncPreempt在以下情况下不会立即执行
信号处理程序已完成,相反:

返回(不输入结尾或序言)之后,调用
  • morestackmorestack_noctxt(不输入结尾或序言),后者调用sighandler并检查先占标志并进入调度循环,从而调度主goroutine完成异步抢占。
  • 执行newstack之前OK输出

  • 这是我在运行时插入的打印日志:
    mstart1 call schedule()
    enter schedule()
    park_m call schedule()
    enter schedule()
    mstart1 call schedule()
    enter schedule()
    mstart1 call schedule()
    enter schedule()
    park_m call schedule()
    enter schedule()
    park_m call schedule()
    enter schedule()
    park_m call schedule()
    enter schedule()
    mstart1 call schedule()
    enter schedule()
    park_m call schedule()
    enter schedule()
    rip: 17149264 eip: 824634034136
    before pushCall asyncPreempt
    after pushCall asyncPreempt
    rip: 17124704 eip: 824634034128 // rip points to asyncPreempt
    calling newstack: m0, g0 // how could newstack is called?
    newstack call gopreempt_m
    gopreempt_m call goschedImpl
    goschedImpl call schedule()
    enter schedule()
    OK
    gosched_m call goschedImpl
    goschedImpl call schedule()
    enter schedule()
    asyncPreempt2
    asyncPreempt2
    asyncPreempt2
    asyncPreempt2
    preemptPark
    gopreempt_m call goschedImpl
    goschedImpl call schedule()
    enter schedule()

    当我检查转储的汇编代码时,没有堆栈拆分检查
    既不是 asyncPreempt也不是 asyncPreempt

    很抱歉,我的问题是:
  • 运行时何时,谁以及如何在sigtramp之后调用morestack?我错过了什么?
  • 完成信号处理程序后,修改sighandler更改程序会跳转到修改后的ctx指令吗?

  • 非常感谢您阅读问题,并感谢go团队建立了如此出色的功能。

    最佳答案

    我已经弄明白了,非常感谢Ian的提示:

    https://groups.google.com/forum/#!topic/golang-nuts/BA7Dqp_zcwk

    根本原因似乎类似于“不确定性原理”。

    作为观察者,通过在println中添加asyncPreempt调用
    以及asyncPreempt2都会影响实际行为
    处理信号后。 println涉及堆栈拆分检查,
    调用morestack

    我花了一段时间才意识到morestack存储了它的调用者
    pc为g.m.morebuf.pc,因为getcallerpcnewstack总是从morestack返回pc,这并不说明
    太多信息。

    //go:nosplit
    func asyncPreempt2() {
    // println("asyncPreempt2 is called") // comment here omits calling morestack.
    gp := getg()
    gp.asyncSafePoint = true
    if gp.preemptStop {
    mcall(preemptPark)
    } else {
    mcall(gopreempt_m)
    }
    println("asyncPreempt2 finished")
    gp.asyncSafePoint = false
    }

    TLDR:信号处理程序之后,内核恢复asyncPreemptrip并直接切换到它,在runtime.asyncPreemptruntime.sigtramp之间什么也没有发生。

    关于go - 如果修改信号处理程序中的ctx.rip和ctx.rsp会发生什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59236641/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com