信号是Linux操作系统中一种重要的进程间通信(IPC)机制,它不同于管道、消息队列等需要显式读写数据的通信方式,而是通过内核传递的异步通知,实现进程间的简单事件告知或行为控制,作为Linux IPC体系中最轻量级的通信手段之一,信号在系统管理、进程同步、异常处理等领域发挥着不可替代的作用。

信号的基本概念与特性
信号本质上是一个整数编号,Linux内核通过向目标进程发送特定信号,通知进程某个事件的发生,系统支持多种信号,每种信号都有预定义的含义和默认处理行为,根据POSIX标准,Linux信号可分为两类:传统信号(非实时信号)和实时信号。
传统信号编号为1-31,如SIGINT(2,键盘中断)、SIGTERM(15,终止进程)、SIGKILL(9,强制终止)等,这类信号不可靠,可能因信号处理未完成而丢失,且不支持排队——同一信号多次发送,进程可能只接收一次,实时信号编号为34-64(SIGRTMIN至SIGRTMAX),解决了传统信号的缺陷:支持信号排队,确保每个发送的信号都被处理,且可携带附加数据(通过sigqueue函数实现)。
信号的默认处理方式包括三种:进程终止(如SIGINT)、进程暂停(如SIGSTOP)、忽略(如SIGCHLD,子进程终止时父进程默认忽略),进程可通过自定义处理函数(捕获信号)或明确忽略信号(SIG_IGN)来覆盖默认行为,但SIGKILL和SIGSTOP两种信号不可被捕获或忽略,确保内核始终拥有强制控制进程的能力。
信号的发送与接收
信号的发送可通过多种方式实现,既可由内核触发(如硬件异常、系统调用),也可由其他进程主动发送。
内核触发的信号
内核在特定情况下会自动向进程发送信号,常见场景包括:

- 硬件异常:如除零操作触发SIGFPE(8),非法内存访问触发SIGSEGV(11);
- 系统调用:如alarm函数设置的定时器到期触发SIGALRM(14),进程执行exec函数时原文件描述符未关闭触发SIGCHLD;
- 终端事件:用户输入Ctrl+C发送SIGINT,Ctrl+Z发送SIGTSTP(20,暂停进程)。
进程主动发送信号
进程可通过系统调用向其他进程或自身发送信号:
- kill函数:最基础的信号发送函数,原型为
int kill(pid_t pid, int sig),pid参数指定目标进程:pid>0时发送给指定进程;pid=0时发送给同进程组的所有进程;pid<0时发送给进程组ID为|pid|的所有进程;pid=-1时发送给有权限发送的所有进程(除init进程外),sig参数为信号编号,若为0则不发送信号,仅用于检查进程是否存在。 - raise函数:用于进程向自身发送信号,相当于
kill(getpid(), sig),常用于测试信号处理逻辑。 - sigqueue函数:实时信号专用函数,支持携带数据和标识符,原型为
int sigqueue(pid_t pid, int sig, const union sigval value),其中union sigval可包含整数指针或浮点数,实现信号的附加信息传递,适用于需要简单数据交互的场景。
信号的接收由内核隐式处理:当内核确认目标进程可接收某信号(未阻塞且处理函数就绪)时,会在进程从内核态返回用户态的时机“注入”信号,触发相应的处理逻辑。
信号处理机制
信号处理的核心是“信号处理函数”,它定义了进程收到信号后的具体行为,Linux通过信号集、阻塞集和未决集等机制,确保信号处理的可控性和安全性。
信号处理函数的设置
进程可通过两种函数设置信号处理行为:
- signal函数:简单易用的旧接口,原型为
void (*signal(int sig, void (*handler)(int)))(int),handler参数可指定SIG_DFL(默认处理)、SIG_IGN(忽略)或自定义处理函数(需符合void handler(int sig)签名),但该函数存在竞态条件(信号可能在设置过程中到达),且行为不可控(如被其他信号处理函数覆盖),已不推荐使用。 - sigaction函数:现代推荐的标准接口,原型为
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact),通过struct sigaction结构体,可精细控制信号处理行为:- sa_handler:指定处理函数(同signal函数);
- sa_mask:信号集,用于在处理当前信号时临时屏蔽的信号(避免嵌套处理导致竞态);
- sa_flags:控制标志,如SA_RESTART(自动重启被信号中断的系统调用)、SA_NOCLDSTOP(子进程暂停时不发送SIGCHLD)等。
信号的阻塞与未决
每个进程维护两个信号集:

- 阻塞信号集(Blocked Set):记录被暂时屏蔽的信号,进程不会处理这些信号,即使它们已被发送,通过sigprocmask函数可修改阻塞集:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset),how参数可为SIG_BLOCK(添加阻塞)、SIG_UNBLOCK(解除阻塞)、SIG_SETMASK(替换阻塞集)。 - 未决信号集(Pending Set):记录已发送但尚未被处理的信号(包括被阻塞的信号),内核会实时更新未决集,当信号被解除阻塞且处理函数就绪时,内核会立即触发处理,可通过sigpending函数查看当前未决信号:
int sigpending(sigset_t *set)。
信号的“未决-阻塞”机制解决了信号处理的竞态问题:在处理共享资源时,进程可临时阻塞关键信号,完成操作后再解除阻塞,确保信号不会干扰临界区的执行。
信号的应用场景与注意事项
典型应用场景
- 进程控制:如使用SIGTERM通知进程优雅退出(先清理资源再终止),避免SIGKILL强制终止导致的数据损坏;使用SIGUSR1/SIGUSR2自定义进程间同步(如生产者-模型中通知数据就绪)。
- 异常处理:通过捕获SIGSEGV、SIGFPE等信号,实现进程的异常恢复(如记录错误堆栈后安全退出)。
- 系统管理:kill命令通过发送SIGTERM或SIGKILL控制进程,systemd等系统管理服务利用信号实现服务启停状态同步。
使用注意事项
- 异步安全性:信号处理函数必须使用异步安全函数(如write、_exit),避免调用不可重入函数(如malloc、printf),否则可能导致数据竞争或程序崩溃。
- 信号屏蔽:在处理共享资源时,务必通过sa_mask临时屏蔽相关信号,避免嵌套处理引发逻辑错误。
- 信号丢失风险:传统信号不支持排队,高频发送可能导致信号丢失;若需可靠通信,应优先使用实时信号或消息队列等IPC机制。
信号作为Linux IPC中的轻量级异步通信机制,以其高效、简单的特性,在进程通知、异常处理、系统管理等领域广泛应用,尽管存在信息量有限、传统信号不可靠等局限,但通过合理的信号处理机制(如sigaction、信号集管理)和实时信号的补充,信号仍能高效满足特定场景的通信需求,在实际应用中,需结合信号的特点和注意事项,与其他IPC机制(如管道、共享内存)协同使用,以构建健壮、高效的进程间通信体系。



















