在 Rust 中捕获 Ctrl-C 信号的技巧
引言
本文介绍使用 libc 在 Rust 中处理 Ctrl-C
为代表的信号。是我最近从开源代码学到的技巧。
使用 libc 库访问底层操作系统接口
我们使用 libc 库中的 sigaction
函数来设置 Ctrl-C 信号的处理程序。sigaction
函数允许我们定义一个自定义的信号处理程序,并将其与特定的信号关联起来。
理解 sigaction 结构体
sigaction
函数接受一个 sigaction
结构体作为参数,该结构体定义了信号处理程序的行为。sigaction
结构体包含以下重要字段:
-
sa_sigaction
: 指向信号处理程序函数的指针。 -
sa_flags
: 用于配置信号处理程序的行为,如SA_SIGINFO
标志用于指示处理程序应该接收更多的信号信息。 -
sa_mask
: 在信号处理程序执行期间,哪些其他信号应该被阻塞。
在示例代码中,我们将 sa_sigaction
设置为一个名为 dummy_handler
的函数,该函数什么也不做,只是作为一个占位符。我们还将 sa_flags
设置为 SA_SIGINFO
,以便在信号处理程序中获取更多的信号信息。
参考:https://man7.org/linux/man-pages/man2/sigaction.2.html
实现 Ctrl-C 信号处理
请看代码,
1#[cfg(not(target_os = "windows"))]
2{
3 // Catch Ctrl-C with a dummy signal handler.
4 unsafe {
5 let mut new_action: libc::sigaction = core::mem::zeroed();
6 let mut old_action: libc::sigaction = core::mem::zeroed();
7
8 extern "C" fn dummy_handler(
9 _: libc::c_int, _: *const libc::siginfo_t, _: *const libc::c_void,
10 ) {
11 }
12
13 new_action.sa_sigaction = dummy_handler as libc::sighandler_t;
14 new_action.sa_flags = libc::SA_SIGINFO;
15
16 libc::sigaction(
17 libc::SIGINT,
18 &new_action as *const libc::sigaction,
19 &mut old_action as *mut libc::sigaction,
20 );
21 }
22}
解释如下:
-
#[cfg(not(target_os = "windows"))]
: 一个 Rust 属性宏,确保这段代码仅在非 Windows 操作系统上编译和执行。Windows 操作系统有自己的处理 Ctrl-C 信号的方式,我们稍后会讨论。 -
在大括号内的代码块中:
-
用
unsafe
块是因为需要直接调用 C 语言的libc
库函数。 -
声明了两个
libc::sigaction
结构体变量new_action
和old_action
。 -
定义了一个名为
dummy_handler
的 C 语言风格的函数。这个函数仅作为一个占位符。 -
将
dummy_handler
函数的地址赋值给new_action.sa_sigaction
。 -
将
new_action.sa_flags
设置为libc::SA_SIGINFO
。 -
调用
libc::sigaction
函数,将new_action
设置为SIGINT
信号(通常是 Ctrl-C)的新处理程序,并将旧的处理程序保存在old_action
中。
-
这段代码的目的是在非 Windows 操作系统上捕获 Ctrl-C 信号,并将其交由一个空的 dummy_handler
函数处理。这样做可以防止程序在收到 Ctrl-C 信号时退出,从而允许程序在收到该信号后执行其他操作。
跨平台兼容性
在上述代码中,我们用了 #[cfg(not(target_os = "windows"))]
属性宏来确保该代码仅在非 Windows 操作系统上编译和执行。这是因为 Windows 操作系统处理 Ctrl-C 信号的方式与 Unix 系统有所不同。
在 Windows 上,我们可以用 SetConsoleCtrlHandler
函数来捕获 Ctrl-C 信号。要用 Windows 特有的 API,而不是 libc 库。具体处理如下:
1#[cfg(target_os = "windows")]
2{
3 use winapi::shared::minwindef::DWORD;
4 use winapi::um::consoleapi::SetConsoleCtrlHandler;
5 use winapi::um::wincon::CTRL_C_EVENT;
6
7 extern "system" fn ctrl_c_handler(_: DWORD) -> winapi::ctypes::c_int {
8 1 // prevent default exit behavor
9 }
10
11 unsafe {
12 SetConsoleCtrlHandler(Some(ctrl_c_handler), 1);
13 }
14
15 ...
16}
完整代码
1use std::thread::sleep;
2
3fn hello() {
4 println!("Hello, world!");
5 sleep(std::time::Duration::from_secs(1));
6}
7
8#[cfg(not(target_os = "windows"))]
9fn main() {
10 // Catch Ctrl-C with a dummy signal handler.
11 unsafe {
12 let mut new_action: libc::sigaction = core::mem::zeroed();
13 let mut old_action: libc::sigaction = core::mem::zeroed();
14
15 extern "C" fn dummy_handler(
16 _: libc::c_int,
17 _: *const libc::siginfo_t,
18 _: *const libc::c_void,
19 ) {
20 // Perform cleanup or other actions here
21 println!("Caught Ctrl-C signal!");
22 // Exit the program
23 std::process::exit(0);
24 }
25
26 new_action.sa_sigaction = dummy_handler as libc::sighandler_t;
27 new_action.sa_flags = libc::SA_SIGINFO;
28
29 libc::sigaction(
30 libc::SIGINT,
31 &new_action as *const libc::sigaction,
32 &mut old_action as *mut libc::sigaction,
33 );
34
35 loop {
36 hello()
37 }
38 }
39}
40
41#[cfg(target_os = "windows")]
42fn main() {
43 use winapi::shared::minwindef::DWORD;
44 use winapi::um::consoleapi::SetConsoleCtrlHandler;
45 use winapi::um::wincon::CTRL_C_EVENT;
46
47 extern "system" fn ctrl_c_handler(_: DWORD) -> winapi::ctypes::c_int {
48 // Perform cleanup or other actions here
49 println!("Caught Ctrl-C signal!");
50 // Exit the program
51 std::process::exit(0);
52 0
53 }
54
55 unsafe {
56 SetConsoleCtrlHandler(Some(ctrl_c_handler), 1);
57 }
58
59 loop {
60 hello()
61 }
62}