跳到主要内容Linux 信号机制详解:从除零到 SIGPIPE | 极客日志C++
Linux 信号机制详解:从除零到 SIGPIPE
Linux 信号是操作系统用于进程间通信及通知事件的机制,包含产生、保存、执行三个阶段。详细讲解了信号的处理方式(默认、忽略、自定义)、系统调用接口(kill、raise、abort),以及由硬件异常产生的信号(如除零触发 SIGFPE、野指针触发 SIGSEGV)。此外还介绍了管道读写导致的 SIGPIPE、定时器信号 SIGALRM 以及核心转储(Core Dump)的概念与应用,帮助开发者理解信号底层原理及调试方法。
小熊软糖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 中找到,例如 #define SIGINT 2。编号 34 以上的是实时信号,本文主要讨论编号 34 以下的普通信号。
进程收到信号后的处理方式有三种:
- 默认动作行为(SIG_DFL)。
- 忽略信号(SIG_IGN)。
- 自定义动作。
对于 2 号信号,默认行为是终止进程。
验证信号处理
系统调用 signal 用于设置信号处理函数。
void (*signal(int signum, void (*handler)(int)))(int);
参数说明:
signum:信号编号。
handler:信号处理函数(系统默认动作、忽略、自定义动作)。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 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 <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;
}
自定义动作
#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;
}
键盘数据与中断机制
键盘输入数据给内核的过程涉及 I/O 中断机制。当 CPU 等待用户输入时,进程阻塞,其他进程运行。用户输入后,数据放入内核缓冲区。I/O 控制器发送中断请求,内核根据中断向量表查找处理地址,将数据放入缓冲区供进程使用。
Ctrl+C 被操作系统识别为特定组合键,映射为 2 号信号。
前 31 号信号中,只有 9 号(SIGKILL)和 19 号(SIGSTOP)不可被自定义,其余均可自定义。
产生信号的系统调用
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>
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 <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;
}
由异常产生信号
整数除零
整数除零会触发硬件异常,对应 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;
}
若自定义了处理行为,程序会持续打印,因为每次调度都会检测到 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。
unsigned int alarm(unsigned int seconds);
特点:一个进程同一时间只能有一个 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);
}
return 0;
}
Core Dump
Core Dump 是进程异常终止时将内存信息转储到文件(core.pid),用于调试。
#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 调试工具可快速定位错误行数和内容。