英文:
How can I list available operating system signals by name in a cross-platform way in Go?
问题
假设我正在使用Go语言实现kill
程序。我可以从命令行接受数字信号和进程ID,并将它们发送给syscall.Kill
函数,没有问题。
然而,我不知道如何实现信号的字符串形式分发,例如kill -INT 12345
。
真正的用例是一个更大程序的一部分,它提示用户发送kill信号;而不是kill
命令的替代品。
问题:
如何在任何支持的平台上将有效的信号名称转换为信号编号,在运行时(或者至少不需要编写针对每个平台的代码在编译时运行)?
我尝试过的方法:
- 维护一个静态映射表,将信号名称映射到信号编号。这种方法在跨平台上不起作用(例如,在Mac OSX上,
kill -l
返回的信号列表与现代Linux和旧版Linux上返回的信号列表不同)。要使此解决方案在一般情况下起作用,唯一的方法是为每个操作系统创建映射表,这要求我了解每个操作系统的行为,并随着它们添加新的信号支持而保持更新。 - 调用GNU
kill
工具并捕获其信号列表。这种方法不够优雅,有点自相矛盾,而且需要:a)能够找到kill
工具,b)具备执行子进程的能力/权限,c)能够预测/解析kill
工具的输出。 - 使用各种
Signal
类型的String
方法。这只返回包含信号编号的字符串,例如os.Signal(4).String() == "signal 4"
,这并不实用。 - 调用私有函数
runtime.signame
,它正好做我想要的事情。使用go://linkname
技巧可以实现,但我假设这种做法是不被赞同的,有其原因。
想法/尚未尝试的事情:
- 以某种方式使用CGo。对于一个否则不需要低级别/本地集成的项目,我宁愿不涉足CGo领域。如果这是唯一的选择,我会尝试,但我不知道从何开始。
- 使用模板和代码生成,在编译时基于外部源构建信号列表。出于与CGo相同的原因,这不是首选方法。
- 反射和解析以
SIG
开头的syscall
成员。据我所知,这是不可能的,因为名称在编译时被删除;难道对于像信号名称这样基本的东西,有地方它们没有被编译掉吗?
英文:
Let's say I'm implementing the kill
program in Go. I can accept numeric signals and PIDs from the commandline and send them to syscall.Kill
no problem.
However, I don't know how to implement the "string" form of signal dispatch, e.g. kill -INT 12345
.
The real use case is a part of a larger program that prompts the user to send kill signals; not a replacement for kill
.
Question:
How can I convert valid signal names to signal numbers on any supported platform, at runtime (or at least without writing per-platform code to be run at compile time)?
What I've tried:
- Keep a static map of signal names to numbers. This doesn't work in a cross-platform way (for example, different signal lists are returned by
kill -l
on Mac OSX versus a modern Linux versus an older Linux, for example). The only way to make this solution work in general would be to make maps for every OS, which would require me to know the behavior of every OS, and keep up to date as they add new signal support. - Shell out to the GNU
kill
tool and capture the signal lists from it. This is inelegant and kind of a paradox, and also requires a) being able to findkill
, b) having the ability/permission to exec subprocesses, and c) being able to predict/parse the output ofkill
-the-binary. - Use the various
Signal
types'String
method. This just returns strings containing the signal number, e.g.os.Signal(4).String() == "signal 4"
, which is not useful. - Call the private function
runtime.signame
, which does exactly what I want.go://linkname
hacks will work, but I'm assuming that this sort of thing is frowned-upon for a reason.
Ideas/Things I Haven't Tried:
- Use CGo somehow. I'd rather not venture into CGO territory for a project that is otherwise not low-level/needful of native integration at all. If that's the only option, I will, but have no idea where to start.
- Use templating and code generation to build lists of signals based on external sources at compile time. This is not preferable for the same reasons as CGo.
- Reflect and parse the members of
syscall
that start withSIG
somehow. I am told that this is not possible because names are compiled away; is it possible that, for something as fundamental as signal names, there's someplace they're not compiled away?
答案1
得分: 3
d455e41 提交于2019年3月添加了这个功能,作为 sys/unix.SignalNum()
,因此至少从Go 1.13开始可用。更多详细信息请参阅GitHub问题#28027。
根据golang.org/x/sys/unix
包的文档:
> func SignalNum(s string) syscall.Signal
>
> SignalNum返回名称为s的信号的syscall.Signal,如果找不到具有该名称的信号,则返回0。信号名称应以"SIG"开头。
为了回答一个类似的问题,即“如何列出所有可用信号的名称(在给定的类Unix平台上)”,我们可以使用反函数sys/unix.SignalName()
:
import "golang.org/x/sys/unix"
// 有关为什么在范围0,255内循环的详细信息,请参阅https://github.com/golang/go/issues/28027#issuecomment-427377759。
for i := syscall.Signal(0); i < syscall.Signal(255); i++ {
name := unix.SignalName(i)
// 信号编号不保证连续。
if name != "" {
fmt.Println(name)
}
}
英文:
Commit 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.
From the documentation of the golang.org/x/sys/unix
package:
> 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".
To answer a similar question, "how can I list the names of all available signals (on a given Unix-like platform)", we can use the inverse function 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)
}
}
答案2
得分: 1
更新 在我发布下面的答案后的一段时间内,Golang的stdlib获得了这个功能。@marco.m发布了一个描述如何使用该功能的答案,并被接受;以下方法不推荐使用,除非您使用的Go版本早于提供适合此任务的正确工具的可用性。
由于没有发布答案,我将发布一个不太理想的解决方案,我能够通过“突破”Go标准库中的一个私有信号枚举函数来使用。
signame
内部函数可以通过信号编号在Unix和Windows上获取信号名称。要调用它,您必须使用 linkname
/汇编器解决方法。基本上,您可以在项目中创建一个名为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”。
**此行为未记录,可能会更改,并且在某些平台上可能不成立。**如果这能够公开,那将是很好的。
英文:
Update some time after I posted the below answer, Golang's stdlib acquired this functionality. An answer describing how to use that functionality was posted by @marco.m and accepted; the below is not recommended unless the version of Go you are using pre-dates the availability of the right tool for the job.
Since no answers were posted, I'll post the less-than-ideal solution I was able to use by "breaking into" a private signal-enumeration function inside Go's standard library.
The 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. Basically, make a file in your project called empty.s
or similar, with no contents, and then a function declaration like so:
//go:linkname signame runtime.signame
func signame(sig uint32) string
Then, you can get a list of all signals known by the operating system by calling signame
on an increasing number until it doesn't return a value, like so:
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++
}
After that runs, signalmap
will have keys for every signal that can be sent on the current operating system. It will have an empty string where Go doesn't think the OS has a name for the signal (the kill(1) may name some signals that Go won't return names for, I've found, but it's usually the higher-numbered/nonstandard ones), or a string name, e.g. "INT" where a name can be found.
This behavior is undocumented, subject to change, and may not hold true on some platforms. It would be nice if this were made public, though.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论