按 Ctrl+C 程序就退出了,这背后是操作系统给进程发了一个叫 SIGINT 的信号。信号是操作系统发给进程的软件中断,C 语言可以接管它们,写自定义处理函数。下面看信号的来龙去脉。

信号是什么

信号是操作系统发给进程的通知,比如:用户按了 Ctrl+C、别的进程发来消息、程序自己越界访问内存。每种信号都有个整数编号,比如 SIGINT 的值通常是 2。

默认行为

每个信号默认都有个处理动作,常见几类:

  • 忽略(Ign):什么都不做直接返回。
  • 终止(Term):把进程干掉。
  • 继续/停止(Cont/Stop):恢复或挂起进程。

比如下面这个无限打印的程序,按 Ctrl+C 发 SIGINT,默认动作就是终止进程:

#include <stdio.h>

int main() {
    while (1) {
        printf("hello world\n");
    }
    return 0;
}

输出会无限刷 hello world,直到 Ctrl+C 把它终止。

自定义信号处理函数

除了 SIGKILL 这类极少数,大多数信号都可以用自己的函数接管。处理函数必须返回 void,接收一个 int 参数(信号编号)。用 signal() 注册:

signal(type, signalHandler);

一个经典的例子,捕获 Ctrl+C:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

/* 信号处理函数:打印信号编号 */
void signalHandler(int sig) {
    printf("Caught signal: %d\n", sig);
    exit(sig);   /* 处理完退出 */
}

int main() {
    signal(SIGINT, signalHandler);   /* 接管 Ctrl+C */

    while (1) {
        printf("Hello World!\n");
        /* 模拟工作,等一会 */
    }
    return 0;
}

运行时程序一直打印 Hello World!,按 Ctrl+C:

Hello World!
Hello World!
...
Ctrl+C
Caught signal: 2

原本该直接死掉的进程,被我们接管后走自定义逻辑,这就是优雅退出、清理资源的起点。

主动产生信号:raise()

不用等用户按键,程序自己也能发信号。raise() 给当前进程发信号:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void signalHandler(int sig) {
    printf("Interrupt handled: %d\n", sig);
    exit(sig);
}

int main() {
    signal(SIGINT, signalHandler);

    raise(SIGINT);   /* 自己给自己发 SIGINT */
    return 0;
}

输出:

Interrupt handled: 2

给别的进程发信号:kill()

kill() 可以把信号发给指定进程(用进程 ID):

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void handle_signal(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    signal(SIGINT, handle_signal);

    pid_t pid = getpid();   /* 当前进程 ID */
    kill(pid, SIGINT);      /* 发给自己 */
    return 0;
}

输出:

Received signal: 2

kill 名字吓人,其实只是发信号,信号内容是啥、进程怎么反应,由接收方决定。

常见信号速查

信号含义默认行为
SIGABRT程序异常中止(abort)终止
SIGFPE浮点异常(如除零、溢出)终止
SIGILL非法指令终止
SIGINT中断(Ctrl+C)终止
SIGSEGV段错误(非法访问内存)终止
SIGTERM请求终止进程终止
SIGKILL强制杀死进程终止(不可捕获)
SIGQUIT退出(会生成核心转储)终止 + core
SIGCHLD子进程结束/停止父进程处理
SIGCONT继续被停止的进程恢复运行
SIGTSTP终端停止(Ctrl+Z)停止

信号机制的几个内部点

  • 投递:操作系统把信号存进进程的待处理队列,进程准备好才处理,不会丢。
  • 屏蔽:关键代码段可以屏蔽某些信号,防止被打断。
  • 信号栈:信号一来,系统保存当前上下文、用专门的栈跑处理函数,跑完再恢复。

信号是 C 语言和操作系统之间的握手方式。学会捕获、处理、主动发送,写守护进程、优雅关闭、清理资源就都有了基础。