You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenBSD下Go实现SIGINT处理器并支持SIGTSTP返回shell

解决OpenBSD下Go程序SIGTSTP无法挂起的问题

问题场景

在OpenBSD/amd64环境(go1.20.1)下,当Go程序通过signal.Notify监听SIGINT和SIGTERM信号时:

  • 按下Ctrl-C(发送SIGINT),程序能正常以返回码1终止
  • 按下Ctrl-Z(发送SIGTSTP),程序无法被挂起,无法返回shell
    但未添加信号处理的程序,按下Ctrl-Z能正常挂起并返回shell。

异常代码示例

package main

import (
        "os"
        "os/signal"
        "syscall"
        "time"
)

func main() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
        go func() {
                <-c
                os.Exit(1)
        }()

        for {
                time.Sleep(time.Second)
        }
}

正常对比代码

package main

import (
    "time"
)

func main() {
    for {
        time.Sleep(time.Second)
    }
}

运行正常代码按下Ctrl-Z的输出:

^Z[1] + Suspended            ./test 
me@mymachine:~$ 

原因分析

当调用signal.Notify注册特定信号时,Go runtime会接管进程的信号处理机制。在OpenBSD环境下,这种接管会导致未注册的SIGTSTP信号的默认挂起动作被阻断,无法触发进程挂起。

解决方案

方案1:显式处理SIGTSTP,恢复默认挂起动作

监听SIGTSTP信号,收到时先恢复该信号的默认处理逻辑,再重新发送信号触发挂起,挂起恢复后重新监听:

package main

import (
        "os"
        "os/signal"
        "syscall"
        "time"
)

func main() {
        c := make(chan os.Signal, 1)
        // 同时监听SIGINT、SIGTERM和SIGTSTP
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGTSTP)
        go func() {
                for sig := range c {
                        switch sig {
                        case syscall.SIGINT, syscall.SIGTERM:
                                os.Exit(1)
                        case syscall.SIGTSTP:
                                // 停止监听SIGTSTP,恢复系统默认处理
                                signal.Stop(c, syscall.SIGTSTP)
                                // 给自己发送SIGTSTP,触发挂起
                                syscall.Kill(syscall.Getpid(), syscall.SIGTSTP)
                                // 进程恢复后,重新监听SIGTSTP
                                signal.Notify(c, syscall.SIGTSTP)
                        }
                }
        }()

        for {
                time.Sleep(time.Second)
        }
}

方案2:使用signal.NotifyContext处理终止信号

signal.NotifyContext仅会处理指定的终止信号,不会干扰其他信号的默认处理逻辑,SIGTSTP的挂起动作会正常生效:

package main

import (
        "context"
        "os"
        "os/signal"
        "syscall"
        "time"
)

func main() {
        // 创建监听SIGINT、SIGTERM的上下文
        ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
        defer stop()

        // 监听上下文取消信号,触发退出
        go func() {
                <-ctx.Done()
                os.Exit(1)
        }()

        // 主循环,同时监听上下文取消
        for {
                select {
                case <-ctx.Done():
                        return
                default:
                        time.Sleep(time.Second)
                }
        }
}

验证效果

运行修改后的程序:

  1. 按下Ctrl-Z,程序会被挂起,返回shell,输出类似正常代码的挂起提示
  2. 输入fg命令可恢复程序运行
  3. 按下Ctrl-C,程序仍能正常以返回码1终止

内容的提问来源于stack exchange,提问作者user92403

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 02:35:10