跳到主要内容 Linux 信号机制详解:从除零到 SIGPIPE | 极客日志
C++
Linux 信号机制详解:从除零到 SIGPIPE Linux 信号是操作系统用于进程间通信及通知事件的机制。介绍信号的产生、保存与执行流程,涵盖默认、忽略及自定义处理方式。详细讲解 kill、raise、abort 等系统调用接口。分析整数除零、野指针等异常产生的信号原理,涉及硬件异常与操作系统交互。最后介绍进程定时器 alarm 及核心转储 Core Dump 的作用,展示利用 gdb 定位错误的流程。
月光旅人 发布于 2026/2/25 更新于 2026/4/18 1 浏览Linux 信号机制详解
信号是操作系统中用于进程间通信的一种机制,通常用于通知进程发生了某种事件。信号分为三部分:信号产生、信号保存、信号执行。
我们可以通过生活中的例子来理解。例如点外卖时,下单为信号产生,等待送达为信号保存,取餐为信号执行。在操作系统中,进程必须能够识别并处理信号。即使信号没有产生,进程也要具备处理能力;收到信号后,可能不会立即处理,而是推迟到合适的时候;同时进程具有临时保存信号的能力。
认识信号
进程通过以下方式认识信号:
能够识别信号(如红灯、绿灯)。
知道该信号的处理方式。
信号的处理能力属于进程的内置功能。操作系统需明确进程可以识别哪些信号,以及收到信号后的默认处理动作。当进程收到具体信号时,可能不会立即处理,以便优先处理更重要的任务。
#include <iostream>
#include <unistd.h>
int main () {
while (1 ) {
std::cout << "this is a process" << std::endl;
sleep (1 );
}
return 0 ;
}
运行上述代码,可以通过 Ctrl+C 终止进程。这是因为按下 Ctrl+C 被解释为收到了 2 号信号 (SIGINT)。
Linux 中的信号都有编号和宏定义名称,定义在 signal.h 中。编号 34 以上的是实时信号,本文主要讨论编号 34 以下的普通信号。
信号处理方式
默认动作行为 :操作系统设计的收到相应信号后的动作。
忽略信号 :不处理该信号。
自定义动作 :按照自己的方式执行。
signal 系统调用
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
#include <signal.h>
void (*signal (int signum, void (*handler)(int )))(int );
signum:信号编号(如 SIGINT、SIGTERM)。
handler:信号处理函数(系统默认动作、忽略、自定义动作)。
系统默认动作 #include <iostream>
#include <unistd.h>
#include <signal.h>
int main () {
while (1 ) {
signal (SIGINT, SIG_DFL);
std::cout << "this is a process" << std::endl;
sleep (1 );
}
return 0 ;
}
忽略信号 #include <iostream>
#include <unistd.h>
#include <signal.h>
int main () {
while (1 ) {
signal (SIGINT, SIG_IGN);
std::cout << "this is a process" << std::endl;
sleep (1 );
}
return 0 ;
}
此时按下 Ctrl+C,进程忽略该信号,不会终止。
自定义动作 #include <iostream>
#include <unistd.h>
#include <signal.h>
void myhandler (int signum) {
std::cout << "这是一个 2 号信号" << std::endl;
}
int main () {
while (1 ) {
signal (SIGINT, myhandler);
std::cout << "this is a process" << std::endl;
sleep (1 );
}
return 0 ;
}
收到 2 号信号后,执行自定义打印语句,不终止进程。
键盘数据与中断 键盘数据输入给内核涉及 I/O 部分。单核 CPU 中,进程需要等待用户输入时会被阻塞,让其他就绪进程先运行。当用户输入数据后,数据放在内核缓冲区。I/O 控制器发送中断请求,内核根据中断向量表查找对应的中断处理方式,将数据放入缓冲区。
Ctrl+C 等组合键被操作系统识别为特定信号(如 2 号信号),而非普通字符数据。
对于前 31 号信号,只有 9 号 (SIGKILL) 和 19 号 (SIGSTOP) 不可被自定义,其余均可自定义。
#include <iostream>
#include <unistd.h>
#include <signal.h>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
for (int i = 1 ; i <= 31 ; i++) {
signal (i, myhandler);
}
while (1 ) {
signal (SIGINT, myhandler);
std::cout << "this is a process , pid : " << getpid () << std::endl;
sleep (1 );
}
return 0 ;
}
验证结果显示,除 9 号和 19 号外,其余信号可被自定义捕获。
产生信号的系统调用
kill #include <signal.h>
int kill (pid_t pid, int sig) ;
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
void Usage (std::string str) {
std::cout << str << " : " << "Signum Pid" << std::endl;
}
int main (int argc, char *argv[]) {
if (argc != 3 ) {
Usage (argv[0 ]);
exit (1 );
}
kill (atoi (argv[2 ]), atoi (argv[1 ]));
return 0 ;
}
raise #include <signal.h>
int raise (int sig) ;
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
signal (2 , myhandler);
int count = 1 ;
while (1 ) {
std::cout << "this is a process , pid : " << getpid () << std::endl;
if (count % 2 == 0 ) {
raise (2 );
}
count++;
sleep (1 );
}
return 0 ;
}
abort abort() 用于使当前进程异常终止,生成 SIGABRT。
#include <stdlib.h>
void abort (void ) ;
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <stdlib.h>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
signal (SIGABRT, myhandler);
printf ("before abort\n" );
abort ();
printf ("after abort\n" );
return 0 ;
}
由异常产生信号
整数除零 整数除 0 会触发硬件异常,对应 SIGFPE (8 号信号)。
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main () {
int a = 10 ;
std::cout << "div before" << std::endl;
a = a / 0 ;
std::cout << "div after" << std::endl;
return 0 ;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
signal (SIGFPE, myhandler);
int a = 10 ;
std::cout << "div before" << std::endl;
a = a / 0 ;
std::cout << "div after" << std::endl;
return 0 ;
}
若自定义了信号处理,程序会一直打印,因为每次调度时 CPU 的 psw 寄存器溢出标记位仍为 1,操作系统会再次发送信号。
野指针异常 野指针访问非法内存会触发页故障,导致硬件异常,对应 SIGSEGV (11 号信号)。
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main () {
std::cout << "point error before" << std::endl;
int *p = nullptr ;
*p = 10 ;
std::cout << "point error after" << std::endl;
return 0 ;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
signal (SIGSEGV, myhandler);
std::cout << "point error before" << std::endl;
int *p = nullptr ;
*p = 10 ;
std::cout << "point error after" << std::endl;
return 0 ;
}
SIGPIPE 管道写端写入数据而读端关闭时,触发 SIGPIPE 信号。
进程定时器机制 alarm 函数用于设置进程定时器,到达指定时间后发送 SIGALRM 信号。
#include <unistd.h>
unsigned int alarm (unsigned int seconds) ;
特点:一个进程同一时间只能有一个 alarm,新的 alarm 会覆盖旧的。
#include <iostream>
#include <unistd.h>
#include <signal.h>
void myhandler (int signum) {
std::cout << "这是一个" << signum << "号信号" << std::endl;
}
int main () {
signal (SIGALRM, myhandler);
alarm (5 );
while (1 ) {
std::cout << "wait ......." << std::endl;
sleep (1 );
}
}
Core Dump Core Dump 是进程异常终止时将内存信息转储到文件的过程。
当进程正常终止时,status 参数中低 7 位表示终止信号,第 7 位表示 core dump 标志位。
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main () {
pid_t id = fork();
if (id == 0 ) {
while (1 ) {
std::cout << "this is child process , pid : " << getpid () << std::endl;
sleep (1 );
}
exit (1 );
}
int status;
int rid = waitpid (id, &status, 0 );
if (rid == id) {
printf ("child process quit, rid : %d, exit code : %d, exit signal : %d, core dump:%d\n" , rid, (status >> 8 ) & 0xFF , status & 0x7F , (status >> 7 ) & 0x1 );
}
return 0 ;
}
如果发送信号的行为包含 Core,当前目录会出现 core.pid 文件。利用 gdb 调试程序可以快速定位错误位置。
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main () {
int a = 10 ;
a /= 0 ;
std::cout << "a : " << a << std::endl;
return 0 ;
}
通过核心转储和 gdb 调试程序可以找到出错的行数及内容。