当前位置:首页 > 系统教程 > 正文

Linux进程信号详解 信号的保存与处理机制

Linux进程信号详解 信号的保存与处理机制

信号是Linux/Unix系统中进程间通信的一种异步通知机制,也是操作系统响应某些事件时对进程产生的中断。对于刚接触Linux编程的朋友来说,理解信号如何在内核中被保存、阻塞以及最终处理,是掌握系统编程的关键一步。本文将用最通俗的语言,带你彻底搞懂Linux信号信号保存信号处理的全过程。

1. 信号从哪里来?

信号可以来自多种场景:

  • 硬件异常:比如除零操作,CPU会触发异常,内核向当前进程发送SIGFPE(浮点异常)信号。
  • 软件条件:例如alarm定时器到期,内核向进程发送SIGALRM信号。
  • 用户输入:在终端按下Ctrl+C,内核向前台进程发送SIGINT信号。
  • 系统调用:通过killraise等函数主动发送信号。
Linux进程信号详解 信号的保存与处理机制 Linux信号 信号保存 信号处理 阻塞信号 第1张

2. 信号在内核中的保存:pending与blocked

每个进程在内核中都有两个关键的位图结构:未决信号集(pending)阻塞信号集(blocked),它们共同决定了信号的信号保存状态。

  • pending(未决):记录哪些信号已经产生但尚未被进程处理。当信号产生时,内核会将对应的pending位图中的比特位置1。当信号被递送(处理)后,该位被清0。
  • blocked(阻塞):记录哪些信号被进程屏蔽(阻塞)。进程可以告诉内核:“暂时不要递送这些信号给我”。被阻塞的信号可以多次产生,但在阻塞期间它们只能被保存在pending集中,不会立即处理。

小知识:阻塞信号集也称为“信号屏蔽字”,可以通过sigprocmask系统调用来修改。而pending集只能由内核更新,但进程可以通过sigpending函数读取。

3. 信号集操作函数(用户态视角)

为了便于操作这两个位图,Linux提供了一套信号集操作函数。它们不是直接修改内核位图,而是用于在用户空间构造sigset_t类型的变量,然后通过系统调用传递给内核。

    #include sigset_t set;sigemptyset(&set);          // 清空集合sigaddset(&set, SIGINT);    // 将SIGINT加入集合sigdelset(&set, SIGINT);    // 将SIGINT从集合移除sigismember(&set, SIGINT);  // 测试SIGINT是否在集合中// 将用户空间的set设置给内核的阻塞信号集sigprocmask(SIG_BLOCK, &set, NULL);  

通过sigprocmask,我们可以动态地阻塞解除阻塞某些信号,从而影响信号的信号处理时机。

4. 信号处理的三条路径

当一个信号要递送给进程时,内核会根据当前状态决定如何处理:

  1. 忽略:如果进程将该信号设置为SIG_IGN,则直接丢弃,pending对应位清零。
  2. 执行默认动作:如果设置为SIG_DFL,则执行系统预定义的动作(如终止、停止、继续等)。
  3. 捕捉并执行用户函数:如果进程通过signalsigaction注册了自定义处理函数,则当信号递送时,内核会暂停当前执行流,跳转到用户态执行该处理函数,返回后再恢复。

注意:只有未被阻塞的信号才有机会被递送。当信号递送时,pending位被清0,然后执行对应的处理动作。

5. 完整的信号处理流程(小白图解)

假设进程正在执行main函数中的代码,此时一个信号产生:

  1. 信号产生:内核将对应pending位置1。
  2. 检查阻塞:在进程从内核态返回用户态的前夕,内核检查pending和blocked位图。如果pending中某位为1且blocked中对应位为0,则决定递送该信号。
  3. 递送准备:如果该信号的处理动作是用户自定义函数,内核会修改用户态栈帧,使得从内核返回后首先执行该信号处理函数,然后再返回原代码继续执行。
  4. 执行处理:进程切换到用户态,执行信号处理函数,完成后通过特殊的系统调用(sigreturn)再次陷入内核,清理栈帧,最后返回原执行点。

如果信号在阻塞期间多次产生,对于普通信号(非实时信号),pending位图中只记录一次,即相同信号只被保存一次;而对于实时信号,则会排队保存多次。本文主要讨论标准Linux信号(1~31),它们都是非实时信号。

6. 关键点总结

  • 信号保存:pending位图保存了已产生但未处理的信号,blocked位图保存了被阻塞的信号。两者共同作用于信号的信号保存与递送。
  • 阻塞信号不同于忽略信号:阻塞只是延迟处理,忽略是直接丢弃。
  • 信号处理函数应在可重入函数中编写,避免使用不可重入函数(如printf、malloc等),因为信号处理可能随时打断主流程。
  • 常用函数signalsigaction用于注册处理函数;sigprocmask用于操作阻塞信号集;sigpending用于查看未决信号。

希望这篇文章能帮你理清Linux信号中信号保存信号处理的底层逻辑!