首页 > 解决方案 > 为什么即使使用 nohup 也会捕获挂断信号?

问题描述

package main

import (
    "os"
    "os/signal"

    log "github.com/sirupsen/logrus"
    "golang.org/x/sys/unix"
)

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, unix.SIGHUP)

    go func() {
        s := <-sigs
        log.Info("OS signal: " + s.String())
    }()

    DoSomething()
}

我编译了上面的 Go 代码并使用以下命令执行:

nohup ./build_linux > /dev/null 2>&1 &

但是当我退出终端时,该过程仍然会捕获 HANGUP 信号。

似乎signal.Notify具有更高的优先级,并且nohup命令无效。

发生了什么,为什么nohup不阻止向进程发送挂断信号?

标签: linuxgosignals

解决方案


TL;博士

首先检查signal.Ignored()

if !signal.Ignored(unix.SIGHUP) {
    signal.Notify(sigs, unix.SIGHUP)
}

tkausl 的评论有正确的答案:运行:

nohup ./build_linux

从命令行启动./build_linux程序并SIGHUP设置为SIG_IGN(所有这些信号名称都是通用的 Linux 名称,而不是 Go 包名称)。如果您为 安装自己的处理程序SIGHUP,这将覆盖 的当前设置SIGHUP

通常,在 Unix/Linux 程序中,正确的模式是在安装信号捕获函数之前(或作为其一部分)测试信号当前是否被忽略。如果信号忽略,则将其恢复为被忽略。

为了使这个过程完全可靠,最有效的正确模式是:

  • 推迟信号(可能是所有信号);
  • 安装任何所需的处理程序,该处理程序返回信号的当前处置;
  • 如果当前处置是SIG_IGN,将处置返回到SIG_IGN
  • 释放信号。

持有和释放是通过 Unix/Linuxsigprocmaskpthread_sigmask系统调用完成的。使用哪个取决于您是否使用线程。Go 当然使用线程;参见,例如,这个从 2013 年开始 Cgo 运行时启动的补丁(修复问题 #6811)。

自 Go 1.11 以来signal.Ignored,您可以直接使用它,因为 Go 运行时已经在启动时完成了所有适当的保持/设置和测试/恢复序列,并缓存了结果。绝对应该使用它SIGHUP来遵守nohup约定。人们通常也应该将其用于SIGINT其他键盘信号,并且几乎没有理由将其用于所有信号。1


1 Jenkins,或者至少是 Jenkins 的某个或多个版本,显然(错误地)在运行测试套件时将所有信号设置为在启动时被忽略。


推荐阅读