1. 信号的概念
信号是一种软件中断,通常是异步发生的,用来通知进程某个事件已经发生。每个信号都有一个唯一的编号和一个宏定义名称,这些宏定义可以在 signal.h 中找到。
使用 kill -l 命令可以查看信号编号。
2. 信号的分类
在 Linux 中,信号被分为标准信号(也称为传统或不可靠信号)和实时信号。它们的主要区别在于编号范围、处理方式以及特性。
Linux 进程信号是进程间通信的异步通知机制。文章涵盖信号概念、分类、处理、产生、保存及捕捉流程。重点解析了标准信号与实时信号区别,SIGKILL 等常见信号含义,以及通过 sigaction 和 sigset_t 进行信号屏蔽与捕获的实现细节。阐述了内核如何通过 PCB 位图管理信号状态,帮助开发者掌握 Linux 系统编程中的信号处理核心知识。

信号是一种软件中断,通常是异步发生的,用来通知进程某个事件已经发生。每个信号都有一个唯一的编号和一个宏定义名称,这些宏定义可以在 signal.h 中找到。
使用 kill -l 命令可以查看信号编号。
在 Linux 中,信号被分为标准信号(也称为传统或不可靠信号)和实时信号。它们的主要区别在于编号范围、处理方式以及特性。
这些信号是早期 Unix 系统定义的,编号通常从 1 到 31。以下是一些常见的标准信号:
SIGHUP (1): 终端挂起或控制进程结束。SIGINT (2): 中断信号,通常是 Ctrl+C 产生的。SIGQUIT (3): 退出信号,产生核心转储。SIGILL (4): 非法指令。SIGTRAP (5): 跟踪陷阱(由调试器使用)。SIGABRT (6): 调用 abort() 函数生成的信号。SIGBUS (7): 总线错误。SIGFPE (8): 浮点异常。SIGKILL (9): 强制终止信号(不可被捕获、阻塞或忽略)。SIGSEGV (11): 段违例。SIGPIPE (13): 管道破裂。SIGALRM (14): 定时器到期。SIGTERM (15): 终止请求。实时信号是在 POSIX.1b 标准中引入的,用于提供更可靠的信号机制。它们的编号范围从 SIGRTMIN 到 SIGRTMAX。实时信号的特点包括但不限于:不会丢失、支持排队、有序性、可携带数据。
本章只讨论编号 31 以下的信号,不讨论实时信号。
在 Linux 中,信号处理有三种方式:
方式一:执行该信号的默认处理动作
使用命令 man 7 signal 查看信号在什么条件下产生,默认的处理动作是什么。
方式二:忽略此信号
可以通过设置信号处理器来实现。可以将信号处理器设置为 SIG_IGN 来忽略某些信号。但是,不能忽略像 SIGKILL 和 SIGSTOP 这样的不可捕获信号。
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main(){
signal(SIGINT, SIG_IGN); // 将 2 号信号忽略
while(true){
std::cout << "PID:" << getpid() << " I am waiting a signal." << std::endl;
sleep(1);
}
return 0;
}
方式三:设置自定义处理方式
实例代码如下:
#include <iostream>
#include <unistd.h>
#include <signal.h>
void Handler(int signo){
std::cout << "PID:" << getpid() << " Get a signal:" << signo << std::endl;
}
int main(){
signal(SIGINT, Handler); // 对 2 号信号设置自定义处理动作
while(true){
std::cout << "PID:" << getpid() << " I am waiting a signal." << std::endl;
sleep(1);
}
return 0;
}
对于信号默认处理动作,也可以使用信号处理器来实现:signal(SIGINT, SIG_DFL)。
对于信号的处理我们可以分别通过软件和硬件这两个视角来理解。
操作系统给进程发送信号就是将进程 PCB 中记录信号的位图对应位置的信号比特位由 0 置 1,然后进程在合适的时候发现自己收到了信号,执行对应处理动作。
在 Linux 系统中,信号是一种异步通知机制。以下是几种产生信号的常见方法:
Ctrl+C:发送 SIGINT 给前台进程。Ctrl+\:发送 SIGQUIT 给前台进程。Ctrl+Z:发送 SIGTSTP 给前台进程。使用 kill 命令可以向指定的进程发送信号。例如:
kill -9 <pid> # 发送 SIGKILL 信号
kill -15 <pid> # 发送 SIGTERM 信号
kill() 函数:可以直接从程序内部发送信号给其他进程。#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
raise 函数:用于向调用它的进程自身发送信号。int raise(int sig);
abort 函数:用于异常终止程序。void abort(void);
alarm 定时器:用于设置一个定时器,可以在指定的时间后产生一个 SIGALRM 信号。#include <unistd.h>
unsigned int alarm(unsigned int seconds);
硬件异常被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以 0 的指令,CPU 的运算单元会产生异常,内核将这个异常解释为 SIGFPE 信号发送给进程。再比如当前进程访问了非法内存地址,MMU 会产生异常,内核将这个异常解释为 SIGSEGV 信号发送给进程。
每个信号都有两个标志位分别表示阻塞 (block) 和未决 (pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,表示接收到信号,直到信号递达才清除该标志。
未决和阻塞标志可以用相同的数据类型 sigset_t 来存储,sigset_t 称为信号集,这个类型可以表示每个信号的'有效'或'无效'状态。
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
#include <signal.h>
int sigpending(sigset_t *set);
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
参数 how 指示如何更改:
SIG_BLOCK: 添加信号到屏蔽字。SIG_UNBLOCK: 从屏蔽字中移除信号。SIG_SETMASK: 设置当前屏蔽字。如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
SIGQUIT 信号的处理函数 sighandler。main 函数,这时发生中断或异常切换到内核态。main 函数之前检查到有信号 SIGQUIT 递达。main 函数的上下文继续执行,而是执行 sighandler 函数。sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。main 函数的上下文继续执行了。#include <signal.h>
int sigaction(int signo, const struct sigaction* act, struct sigaction* oact);
signo:指定要操作的信号编号。act:指向 sigaction 结构体的指针,根据 act 修改该信号的处理动作。oact:指向 sigaction 结构体的指针,通过 oact 传出该信号原来的处理动作。sigaction 结构体:
struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void*);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
代码示例如下:
#include <iostream>
#include <signal.h>
void handler(int signo){
std::cout << "get a signal:" << signo << std::endl;
}
int main(){
struct sigaction act, oact;
act.sa_handler = handler;
::sigaction(2, &act, &oact);
while(true){
::pause();
}
return 0;
}
相比于 signal 函数来说,sigaction 函数提供了对信号处理更精确的控制,更为安全和灵活。
我们从信号定义、分类、处理谈到信号产生、信号保存最后到信号捕捉,关键在于信号处理的理解、相关的信号处理函数、信号保存的三张表——pending 表、block 表和 handler 表以及信号捕捉的理解与运用。总之,Linux 进程信号是一种强大且灵活的进程间通信机制。通过合理地使用信号,可以实现进程间的异步通知、同步和通信等功能。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online