如何在Go中以跨平台方式按名称列出可用的操作系统信号?

时间:2017-03-04 16:43:11

标签: unix go signals cross-platform

让我们说我在Go中实施kill程序。我可以从命令行接受数字信号和PID,并将它们发送到syscall.Kill没问题。

但是,我不知道如何实现"字符串"信号调度的形式,例如kill -INT 12345

真实用例是一个较大程序的一部分,它会提示用户发送终止信号;不能代替kill

问题:

如何在运行时将有效信号名称转换为任何支持平台上的信号编号(或者至少不编写在编译时运行的每个平台代码)?

我尝试过的事情:

  • 将信号名称的静态地图保存到数字中。这不是以跨平台的方式工作(例如,在Mac OSX上由kill -l返回不同的信号列表,而不是现代Linux而不是旧Linux。使这个解决方案工作的唯一方法是为每个操作系统制作地图,这需要我了解每个操作系统的行为,并在添加新的信号支持时保持最新。
  • 转出GNU kill工具并从中捕获信号列表。这是不优雅的,有点悖论,还需要a)能够找到kill,b)具有执行子进程的能力/权限,以及c)能够预测/解析{{1}的输出}} - 的二元
  • 使用各种kill类型' Signal方法。这只返回包含信号编号的字符串,例如String,这没用。
  • 调用私有函数runtime.signame,这正是我想要的。 os.Signal(4).String() == "signal 4"黑客行为会有效,但我认为这种事情是不可理喻的。

我避开的想法/事情

  • 以某种方式使用CGo。我宁愿不冒险进入CGO领域,因为这个项目根本不是低级/需要原生集成的项目。如果这是唯一的选择,我会,但不知道从哪里开始。
  • 使用模板和代码生成在编译时基于外部源构建信号列表。出于与CGo相同的原因,这不是优选的。
  • 以某种方式反映并解析以go://linkname开头的syscall成员。我被告知这是不可能的,因为名字被编译掉了;是否有可能,对于像信号名称这样基本的东西,他们在 的某个地方被编译掉了?

2 个答案:

答案 0 :(得分:0)

由于没有发布任何答案,我将发布一个不太理想的解决方案,我可以使用"闯入" Go标准库中的私有信号枚举功能。

signame内部函数可以在Unix和Windows上按编号获取信号名称。要调用它,您必须使用linkname/assembler workaround。基本上,在你的项目中创建一个名为empty.s或类似的文件,没有内容,然后是一个像这样的函数声明:

//go:linkname signame runtime.signame
func signame(sig uint32) string

然后,您可以通过调用signame增加的数字来获取操作系统已知的所有信号的列表,直到它没有返回值,如下所示:

signum := uint32(0)
signalmap = make(map[uint32]string)
for len(signame(signum)) > 0 {
    words := strings.Fields(signame(signum))
    if words[0] == "signal"  || ! strings.HasPrefix(words[0], "SIG") {
        signalmap[signum] = ""
    } else {
        // Remove leading SIG and trailing colon.
        signalmap[signum] = strings.TrimRight(words[0][3:], ":")
    }
    signum++
}

运行之后,signalmap将为每个可在当前操作系统上发送的信号提供密钥。它将有一个空字符串,其中Go并不认为操作系统有一个信号名称(kill(1)可能会命名一些Go赢得的信号,我找不到它的名字,我发现了,但它通常是较高编号/非标准的),或字符串名称,例如" INT"哪里可以找到名字。

此行为未记录,可能会发生变化,并且在某些平台上可能不成立。如果将其公之于众,那将会很好。

答案 1 :(得分:0)

Commit d455e41在2019年3月以sys/unix.SignalNum()的形式添加了此功能,因此至少从Go 1.13开始可用。更多详细信息,请参见GitHub第#28027

来自golang.org/x/sys/unix软件包的documentation

func SignalNum(s string) syscall.Signal

SignalNum返回名为s的信号的syscall.Signal;如果找不到具有该名称的信号,则返回0。信号名称应以“ SIG”开头。

要回答类似的问题,“如何列出所有可用信号的名称(在给定的类Unix平台上)”,我们可以使用反函数sys/unix.SignalName()

    import "golang.org/x/sys/unix"

    // See https://github.com/golang/go/issues/28027#issuecomment-427377759
    // for why looping in range 0,255 is enough.
    for i := syscall.Signal(0); i < syscall.Signal(255); i++ {
        name := unix.SignalName(i)
        // Signal numbers are not guaranteed to be contiguous.
        if name != "" {
            fmt.Println(name)
        }
    }