如何在 Go 中以跨平台的方式按名称列出可用的操作系统信号?
How can I list available operating system signals by name in a cross-platform way in Go?
假设我正在用 Go 实现 kill
程序。我可以从命令行接受数字信号和 PID,并将它们发送到 syscall.Kill
没问题。
但是,我不知道如何实现 "string" 形式的信号调度,例如kill -INT 12345
.
真正的用例是提示用户发送终止信号的较大程序的一部分;不能替代 kill
.
问题:
如何在 运行 时(或至少不在编译时将每个平台的代码编写为 运行)在任何支持的平台上将有效信号名称转换为信号编号?
我试过的:
- 保留信号名称到数字的静态映射。这不适用于跨平台方式(例如,
kill -l
在 Mac OSX 上返回不同的信号列表,而现代 Linux 与较旧的 Linux,例如)。使这个解决方案在一般情况下起作用的唯一方法是为每个 OS 制作地图,这需要我了解每个 OS 的行为,并在它们添加新信号支持时保持最新.
- Shell 到 GNU
kill
工具并从中捕获信号列表。这是不优雅的,有点自相矛盾,还需要 a) 能够找到 kill
,b) 具有 ability/permission 来执行子进程,以及 c) 能够 predict/parse 输出kill
-the-binary.
- 使用各种
Signal
类型的 String
方法。这只是 returns 包含信号编号的字符串,例如os.Signal(4).String() == "signal 4"
,没用。
- 调用私有函数
runtime.signame
,这正是我想要的。 go://linkname
hacks 会奏效,但我假设这种事情是有原因的。
Ideas/Things我没试过:
- 以某种方式使用 CGo。我宁愿不冒险进入 CGO 领域,否则根本不是 low-level/needful 本机集成的项目。如果这是唯一的选择,我会的,但不知道从哪里开始。
- 使用模板和代码生成在编译时根据外部源构建信号列表。出于与 CGo 相同的原因,这不是可取的。
- 以某种方式反映并解析
syscall
中以SIG
开头的成员。有人告诉我这是不可能的,因为名称被编译掉了;有没有可能,对于像信号名称这样基本的东西,它们在某个地方没有被编译掉?
由于 post 没有答案,我将 post 我能够通过 "breaking into" Go 中的私有信号枚举函数使用的不太理想的解决方案标准库。
signame
internal function can get a signal name by number on Unix and Windows. To call it, you have to use the linkname
/assembler workaround。基本上,在你的项目中创建一个名为 empty.s
或类似的文件,没有内容,然后是一个像这样的函数声明:
//go:linkname signame runtime.signame
func signame(sig uint32) string
然后,您可以通过对递增的数字调用 signame
直到它没有 return 一个值来获取操作系统已知的所有信号的列表,如下所示:
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 认为 OS 没有信号名称(kill(1) 可能会命名一些 Go 不会 return 命名的信号,我'我已经找到了,但它通常是 higher-numbered/nonstandard 个)或字符串名称,例如"INT" 哪里可以找到名字。
此行为未记录在案,可能会发生变化,并且可能不适用于某些平台。不过,如果能做到这一点就好了public。
提交 d455e41 added this feature in March 2019 as sys/unix.SignalNum()
and is thus available at least since Go 1.13. More details in GitHub issue #28027。
来自 golang.org/x/sys/unix
包的 documentation:
func SignalNum(s string) syscall.Signal
SignalNum returns the syscall.Signal for signal named s, or 0 if a signal with such name is not found. The signal name should start with "SIG".
要回答类似的问题,"how can I list the names of all available signals (on a given Unix-like platform)",我们可以使用反函数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)
}
}
假设我正在用 Go 实现 kill
程序。我可以从命令行接受数字信号和 PID,并将它们发送到 syscall.Kill
没问题。
但是,我不知道如何实现 "string" 形式的信号调度,例如kill -INT 12345
.
真正的用例是提示用户发送终止信号的较大程序的一部分;不能替代 kill
.
问题:
如何在 运行 时(或至少不在编译时将每个平台的代码编写为 运行)在任何支持的平台上将有效信号名称转换为信号编号?
我试过的:
- 保留信号名称到数字的静态映射。这不适用于跨平台方式(例如,
kill -l
在 Mac OSX 上返回不同的信号列表,而现代 Linux 与较旧的 Linux,例如)。使这个解决方案在一般情况下起作用的唯一方法是为每个 OS 制作地图,这需要我了解每个 OS 的行为,并在它们添加新信号支持时保持最新. - Shell 到 GNU
kill
工具并从中捕获信号列表。这是不优雅的,有点自相矛盾,还需要 a) 能够找到kill
,b) 具有 ability/permission 来执行子进程,以及 c) 能够 predict/parse 输出kill
-the-binary. - 使用各种
Signal
类型的String
方法。这只是 returns 包含信号编号的字符串,例如os.Signal(4).String() == "signal 4"
,没用。 - 调用私有函数
runtime.signame
,这正是我想要的。go://linkname
hacks 会奏效,但我假设这种事情是有原因的。
Ideas/Things我没试过:
- 以某种方式使用 CGo。我宁愿不冒险进入 CGO 领域,否则根本不是 low-level/needful 本机集成的项目。如果这是唯一的选择,我会的,但不知道从哪里开始。
- 使用模板和代码生成在编译时根据外部源构建信号列表。出于与 CGo 相同的原因,这不是可取的。
- 以某种方式反映并解析
syscall
中以SIG
开头的成员。有人告诉我这是不可能的,因为名称被编译掉了;有没有可能,对于像信号名称这样基本的东西,它们在某个地方没有被编译掉?
由于 post 没有答案,我将 post 我能够通过 "breaking into" Go 中的私有信号枚举函数使用的不太理想的解决方案标准库。
signame
internal function can get a signal name by number on Unix and Windows. To call it, you have to use the linkname
/assembler workaround。基本上,在你的项目中创建一个名为 empty.s
或类似的文件,没有内容,然后是一个像这样的函数声明:
//go:linkname signame runtime.signame
func signame(sig uint32) string
然后,您可以通过对递增的数字调用 signame
直到它没有 return 一个值来获取操作系统已知的所有信号的列表,如下所示:
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 认为 OS 没有信号名称(kill(1) 可能会命名一些 Go 不会 return 命名的信号,我'我已经找到了,但它通常是 higher-numbered/nonstandard 个)或字符串名称,例如"INT" 哪里可以找到名字。
此行为未记录在案,可能会发生变化,并且可能不适用于某些平台。不过,如果能做到这一点就好了public。
提交 d455e41 added this feature in March 2019 as sys/unix.SignalNum()
and is thus available at least since Go 1.13. More details in GitHub issue #28027。
来自 golang.org/x/sys/unix
包的 documentation:
func SignalNum(s string) syscall.Signal
SignalNum returns the syscall.Signal for signal named s, or 0 if a signal with such name is not found. The signal name should start with "SIG".
要回答类似的问题,"how can I list the names of all available signals (on a given Unix-like platform)",我们可以使用反函数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)
}
}