signal 是 C 标准库中一个非常重要的函数,它允许程序异步地响应和处理特定的“信号”(Signals),信号是 Unix/Linux/Windows 等操作系统中进程间通信的一种机制,用于通知进程发生了某个特定的事件。

什么是信号?
你可以把信号想象成操作系统发给进程的“中断”或“通知”,这些通知通常由以下事件触发:
- 硬件异常:比如非法访问内存(
SIGSEGV)、除以零(SIGFPE)。 - 软件事件:比如用户按下
Ctrl+C(SIGINT)、Ctrl+\(SIGQUIT)、Ctrl+Z(SIGTSTP)。 - 系统调用:
kill命令可以发送任意信号给一个进程。
当进程接收到一个信号时,它会中断当前的正常执行流程,转而去执行一个预先设定好的“信号处理函数”,处理完毕后再返回到之前被中断的地方继续执行。
signal 函数的原型
signal 函数在 <signal.h> 头文件中声明。
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
参数解释:

int signum:指定要捕获的信号编号,通常使用<signal.h>中定义的宏来表示,SIGINT、SIGSEGV等。sighandler_t handler:一个指向信号处理函数的指针,它指定了当signum信号发生时,应该执行哪个函数,这个类型sighandler_t本质上是一个函数指针类型,定义为void (*)(int),表示它指向一个接受一个int参数(信号编号)并返回void的函数。
返回值:
- 成功时:返回该信号之前的信号处理函数的指针。
- 失败时:返回
SIG_ERR(这是一个宏),errno会被设置为相应的错误码。
handler 参数的三种可能性
signal 函数的第二个参数 handler 可以有以下三种值:
a) SIG_DFL (Default Action - 默认处理)
告诉操作系统对信号采取默认处理方式,每个信号都有一个默认行为,常见的有:
SIGINT(中断,通常是Ctrl+C):终止进程。SIGQUIT(退出,通常是Ctrl+\):终止进程,并生成核心转储文件。SIGSEGV(段错误):终止进程,并生成核心转储文件。SIGTERM(终止请求):终止进程。SIGTSTP(终端停止,通常是Ctrl+Z):暂停进程。
b) SIG_IGN (Ignore - 忽略)
告诉操作系统完全忽略这个信号,这个信号将被丢弃,不会产生任何效果。

一个经典例子:忽略 SIGINT (Ctrl+C)
#include <stdio.h>
#include <signal.h>
#include <unistd.h> // for sleep()
int main() {
printf("程序正在运行... 尝试按下 Ctrl+C 看看会发生什么,\n");
printf("现在我将忽略 SIGINT 信号,\n");
// 忽略 SIGINT 信号
if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
perror("signal");
return 1;
}
// 程序将在这里无限循环,无法被 Ctrl+C 中断
while(1) {
sleep(1);
printf("程序仍在运行...\n");
}
return 0;
}
编译并运行这个程序,你会发现按下 Ctrl+C 并不能终止它,直到你使用 kill 命令或者关闭终端。
c) 自定义处理函数 (Custom Handler)
提供一个指向你自己编写的函数的指针,当信号发生时,这个函数会被调用。
函数原型要求:
void my_handler(int signum);
它必须接受一个 int 参数(即收到的信号编号),并且返回 void。
一个经典例子:捕获 SIGINT
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义的信号处理函数
void my_handler(int signum) {
printf("\n收到信号 %d! 正在优雅地退出...\n", signum);
// 在这里可以进行清理工作,比如关闭文件、释放内存等
exit(0); // 主动退出程序
}
int main() {
printf("程序正在运行... 按下 Ctrl+C 来触发信号处理函数,\n");
// 注册 my_handler 函数来处理 SIGINT 信号
if (signal(SIGINT, my_handler) == SIG_ERR) {
perror("signal");
return 1;
}
while(1) {
sleep(1);
printf("程序正在运行...\n");
}
return 0;
}
在这个例子中,当你按下 Ctrl+C 时,my_handler 函数会被调用,打印一条消息并退出程序。
信号处理的注意事项(非常重要)
直接使用 signal 函数有一些固有的问题,这些问题在现代 C 编程中通常被更可靠的 sigaction 函数所解决。
a) 可重入性
信号处理函数应该尽可能简单,并且应该是可重入的,这意味着在信号处理函数执行期间,如果再次接收到同一个信号,它仍然可以安全地被调用。
避免在信号处理函数中做的事情:
- 调用标准 I/O 库函数(如
printf,scanf,malloc等),因为它们通常使用全局状态,不安全。 - 调用不可重入的函数。
- 修改非
volatile sig_atomic_t类型的全局变量。
推荐做法:
在信号处理函数中只设置一个标志位(最好是 volatile sig_atomic_t 类型),然后在主程序循环中检查这个标志位来执行复杂的操作。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdatomic.h> // C11 原子类型
// 使用 volatile sig_atomic_t 确保原子操作
volatile sig_atomic_t keep_running = 1;
void my_handler(int signum) {
printf("\n收到信号 %d,准备退出...\n", signum);
keep_running = 0; // 安全地修改标志位
}
int main() {
signal(SIGINT, my_handler);
while (keep_running) {
sleep(1);
printf("程序正在运行...\n");
}
printf("程序正常退出,\n");
return 0;
}
这个例子比上一个更安全、更健壮。
b) 信号的丢失
当一个信号被处理时,如果该信号再次发生,可能会被“丢失”。sigaction 函数提供了 SA_RESTART 选项,可以尝试在被信号中断的系统调用(如 read, write)自动重新启动,但这不能解决信号本身丢失的问题。
c) signal 的行为在不同系统间可能不一致
早期的 signal 函数实现存在差异,在处理完一个信号后,系统是否会自动将信号处理函数重置为 SIG_DFL(这被称为 " unreliable " 的信号)是不确定的。
signal vs. sigaction
为了解决 signal 函数的上述问题,POSIX 标准引入了 sigaction 函数,它提供了更强大、更可靠的控制。
| 特性 | signal |
sigaction |
|---|---|---|
| 可靠性 | 不可靠,行为可能因系统而异 | 可靠,行为在 POSIX 系统中标准 |
| 功能 | 基本功能:设置处理函数 | 功能强大:设置处理函数、查询当前设置、控制信号集 |
| 可重入性 | 无法保证 | 可以指定 SA_RESTART 标志来尝试重启系统调用 |
| 信号掩码 | 无法在处理期间自动屏蔽其他信号 | 可以指定一个信号集,在处理函数执行期间自动屏蔽这些信号 |
| 推荐度 | 简单示例,不推荐用于生产环境 | 强烈推荐用于任何严肃的程序 |
sigaction 的基本用法:
#include <signal.h>
#include <stdio.h>
void my_handler(int signum) {
printf("收到信号 %d\n", signum);
}
int main() {
struct sigaction sa;
sa.sa_handler = my_handler; // 设置处理函数
sigemptyset(&sa.sa_mask); // 初始化信号集,不屏蔽任何信号
sa.sa_flags = 0; // 默认标志
// 使用 sigaction 注册 SIGINT 的处理函数
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
while(1) {
pause(); // 暂停进程,等待信号
}
return 0;
}
常见信号列表
| 信号名 | 默认动作 | 说明 |
|---|---|---|
SIGINT |
Term |
中断信号 (Ctrl+C) |
SIGQUIT |
Core |
退出信号 (Ctrl+),会产生 core dump |
SIGTERM |
Term |
终止信号,程序可捕获并优雅退出 |
SIGSEGV |
Core |
段错误,访问非法内存地址 |
SIGFPE |
Core |
浮点异常,如除以零 |
SIGKILL |
Term |
无法捕获的终止信号,强制杀死进程 |
SIGSTOP |
Stop |
无法捕获的停止信号,暂停进程 |
SIGTSTP |
Stop |
终端停止信号 (Ctrl+Z) |
SIGCONT |
Cont |
继续执行一个被暂停的进程 |
SIGALRM |
Term |
由 alarm() 函数设置的定时器到期时发送 |
SIGUSR1 |
Term |
用户自定义信号 1 |
SIGUSR2 |
Term |
用户自定义信号 2 |
signal是什么? 它是 C 语言中用于捕获和处理异步信号的机制。- 如何使用? 调用
signal(signum, handler),handler可以是SIG_DFL(默认)、SIG_IGN(忽略)或一个自定义函数。 - 自定义函数要注意什么? 必须简单、可重入,最好只设置一个标志位,在主程序中处理复杂逻辑,避免调用标准 I/O 和不可重入函数。
- 现代实践是什么? 虽然在简单示例中
signal可以工作,但对于健壮的程序,强烈推荐使用功能更强大、更可靠的sigaction函数。 - 信号的生命周期:从操作系统产生 -> 内核传递给进程 -> 进程执行处理函数 -> 处理完毕后返回,这个过程是异步的。
