Golang 捕获信号

43

我希望在Go中实现一个“进程包装器”。基本上它所要做的就是启动一个进程(比如一个节点服务器)并监控它(捕获如SIGKILL,SIGTERM等信号)。

我认为实现的方式是使用syscall.Exec在一个Go协程中启动节点服务器:

func launchCmd(path string, args []string) {
  err := syscall.Exec(path, args, os.Environ())
  if err != nil {
    panic(err)
  }
}

我想捕获由 syscall 执行的每一个可能的信号。我对Go不是很熟悉,希望能得到帮助。


1
请参见:See also - kostix
3个回答

71

在Go语言中,有三种执行程序的方法:

  1. 使用 syscall 包的 syscall.Execsyscall.ForkExecsyscall.StartProcess
  2. 使用 os 包的 os.StartProcess
  3. 使用 os/exec 包的 exec.Command

syscall.StartProcess 是底层的方法。它返回一个 uintptr 句柄。

os.StartProcess 返回一个方便的 os.Process 结构体,你可以调用其上的 Signal 方法。os/exec 则提供了可用于管道的 io.ReaderWriter 接口。这两个方法内部都使用了 syscall

读取从其他进程发送来的信号似乎有点棘手。如果可能的话,syscall 应该能够做到。但在更高层的包中并没有明显的解决方案。

要接收信号,你可以使用 signal.Notify 方法,像这样:

sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
    syscall.SIGHUP,
    syscall.SIGINT,
    syscall.SIGTERM,
    syscall.SIGQUIT)
go func() {
    s := <-sigc
    // ... do something ...
}()

你只需要更改你想要监听的信号。如果不指定信号,它会捕获所有可捕获的信号。

你可以使用syscall.KillProcess.Signal来映射信号。你可以从Process.Pid或从syscall.StartProcess的结果中获取pid。


谢谢,我会尝试的。这个想法是让这个包装器被upstart监控,它只会用来跟踪发生了什么。 - rmonjo
好的,我可以捕获输入到程序中的信号(例如,如果我按^C),但无法获取由系统调用执行的程序生成的信号。有什么想法吗? - rmonjo
@rmonjo 你是在尝试捕获从你的程序发送的信号还是发送到你的程序的信号? - Luke
我想在syscall中捕获从我执行的程序发送的信号。我尝试了你的解决方案,当我使用syscall.Exec时,似乎该程序被分叉并且go退出。然而,使用os/exec,我可以捕获诸如子进程退出之类的信号。 - rmonjo
@rmonjo signal.Notify 可以捕获发送到程序的信号。从哪里捕获可能有点棘手。稍微优化一下答案。 - Luke

39
你可以使用signal.Notify
import (
"os"
"os/signal"
"syscall"
)

func main() {
    signalChannel := make(chan os.Signal, 2)
    signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
    go func() {
        sig := <-signalChannel
        switch sig {
        case os.Interrupt:
            //handle SIGINT
        case syscall.SIGTERM:
            //handle SIGTERM
        }
    }()
    // ...
}

4
嗨,巴巴,为什么要创建容量为2的缓冲信道? - Samir Kape

1
func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSEGV)
    for {
        s := <-c
        switch s {
        case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
            return
        case syscall.SIGHUP:
        case syscall.SIGSEGV:
        default:
            return
        }
    }
}


虽然这可能回答了问题,但我认为加上一点解释会更有用(也适用于未来的读者!)。你为什么捕获哪些信号?在哪种情况下做什么? - FObersteiner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接