Linux程序退出处理

在编程Linux软件的时候,我们会考虑这么一件事情,当程序退出执行(即用Ctrl+C或者其他方式)时,该如何做程序的手尾工作,保存数据呢?避免直接强制退出造成丢失数据等后果。

那么我们就会用到信号这个东西

1
2
3
4
5
6
7
8
9
10
11
12
13
struct sigaction sa;  // 创建一个局部变量 sa,存放“信号处理方案”

memset(&sa, 0, sizeof(sa)); // 先清零,避免结构体里有脏数据
sa.sa_handler = onSignalStop; // 收到指定信号后调用onSignalStop
sigemptyset(&sa.sa_mask); // 处理这个信号的时候,不额外屏蔽别的新信号
sigaction(SIGINT, &sa, NULL); // 将SIGINT(Ctrl+C)绑定到这套方案
sigaction(SIGTERM, &sa, NULL); // 将请求程序退出绑定到这套方案

static void onSignalStop(int signum)
{
(void)signum;
runtimeRequestStop();
}

这里的 int signum,表示“当前收到的是哪个信号”。

SIGTERM 是“操作系统或别的进程发给这个程序的终止请求信号”。

比如:

1
kill <pid>

默认发的就是 SIGTERM

你的程序这里:

1
sigaction(SIGTERM, &sa, NULL);

意思是:
如果别人给我发 SIGTERM,我不要立刻死掉,而是先执行 onSignalStop(),做收尾退出。

exit() 会不会触发 SIGTERM
不会,正常情况下不会。

顺便补一个常见对比:

  • SIGINT
    通常是 Ctrl+C
  • SIGTERM
    通常是 kill pid
  • SIGKILL
    强制杀死,程序来不及清理,不能捕获

比如这个函数:

1
2
3
4
5
static void onSignalStop(int signum)
{
(void)signum;
runtimeRequestStop();
}

当系统调用这个函数时,会把信号编号传进来:

  • SIGINT 对应一种编号
  • SIGTERM 对应另一种编号

所以 signum 的作用就是告诉你:
“这次到底是哪个信号触发了这个处理函数”。

比如如果想区分处理,就可以写成:

1
2
3
4
5
6
7
8
9
10
static void onSignalStop(int signum)
{
if (signum == SIGINT) {
// Ctrl+C
} else if (signum == SIGTERM) {
// kill 默认发送的终止信号
}

runtimeRequestStop();
}这份代码里:
1
(void)signum;

意思是:
“我知道这个参数传进来了,但目前我不用它”。

这样写主要是为了消除编译器“未使用参数”的警告。

所以一句话总结:

  • signum = 信号编号
  • 用来表示当前收到的是什么信号
  • 这份代码暂时没用它,只是统一收到退出类信号后都执行 runtimeRequestStop()