信号处理的安全全局状态

Safe global state for signal handling

我正在研究 Rust 和各种 UNIX 库。我现在的一个用例是我想对 POSIX 信号做出反应。为了保持合理,我想在信号处理上创建一个抽象,这样我的程序的其余部分就不必担心它们了。

让我们调用抽象SignalHandler:

struct SignalHandler {
    pub signals: Arc<Vec<libc::c_int>>,
}

我希望这个 signals 向量充满接收到的所有信号。我的真实状态比较复杂,但让我们以这个向量为例。

我希望 API 表现如下:

// ← No signals are being captured
let Some(h) = SignalHandler::try_create();
// ← Signals are added to h.signals

// Only one signal handler can be active at a time per process
assert_eq!(None, SignalHandler::try_create());

// ← Signals are added to h.signals
drop(h);
// ← No signals are being captured

问题是注册信号处理程序(例如使用 nix crate)需要指向 C 函数的指针:

use nix::sys::signal;
let action = signal::SigAction::new(handle_signal, signal::SockFlag::empty(), signal::SigSet::empty());
signal::sigaction(signal::SIGINT, &action);

我无法将 signals 向量传递给 handle_signal 函数,因为它需要 C ABI,因此不能是闭包。我想以某种方式给出一个指向该函数的 Weak<_> 指针。这可能意味着使用全局状态。

所以问题是:我应该为全局状态使用什么数据结构,它可以是 "unset"(即没有 signals 向量)或原子地 "set" 到一些可变状态我在 try_create?

中初始化

对于这种类型的全局状态,我建议使用 lazy_static crate。您可以使用宏来定义延迟计算的可变全局引用。您也许可以使用全局 Option<T> 变量。

虽然这是这种情况的一个问题。你会 运行 遇到的一个大问题是很难只在信号处理程序内部做你想做的事情。由于信号处理程序必须是可重入的,因此任何类型的锁以及任何内存分配都不能使用(除非使用的内存分配器也是可重入的)。这意味着 Arc<Mutex<Vec<T>>> 类型或类似的东西将不起作用。不过,您可能已经知道并正在以某种方式处理它。

根据您的需要,我可能会向您指出 chan_signal crate,它是对信号的抽象,它使用线程和 sigwait 系统调用来接收信号。

希望对您有所帮助,另一个值得关注的有趣资源是 signalfd 函数,它创建一个文件描述符来对信号进行排队。 nix crate 也绑定了它。