当前位置: 首页 > article >正文

Linux——进程信号(1)(signal与sigaction)

进程信号初识

  • 信号
    • 信号概念类比理解
      • 一个简单的信号案例
    • 信号系统调用
      • (1) signal() 系统函数
      • (2) 示例代码解析
      • (3) 信号处理的核心问题
      • (4) 生活类比
      • (5) 补充知识点
      • (6) 总结
    • 信号处理(默认与忽略)
      • signal 函数
      • 示例代码
      • 默认与忽略宏定义解析
      • 信号处理机制
      • sigaction 函数(高级信号处理)
      • 示例代码
      • 总结

信号

信号概念类比理解

在Linux中,信号——Signal是系统或用户向进程发送的一种异步通知机制,用于通知进程发生了某种事件(如错误、中断或状态变化)。类比生活中的现象可以更直观地理解这一抽象概念:

(1)信号 ≈ 快递员的敲门(中断通知)
场景:你正在家专心工作,突然快递员敲门(信号到达)。

你需要决定:立即开门(处理信号)、忽略敲门(忽略信号),或者等忙完手头的事再开门(延迟处理)。

对应信号:SIGINT(Ctrl+C 终止进程)或 SIGUSR1(用户自定义信号)。(宏定义
在这里插入图片描述

关键点:信号是异步的——你无法预知敲门何时发生。

(2)默认行为 ≈ 条件反射
场景:有人突然从背后拍你肩膀,你会本能地转身(默认行为)。但你可以训练自己保持不动(覆盖默认行为)。

对应信号

  • SIGTERM:默认终止进程(像礼貌的“请离开”)。

  • SIGKILL:强制终止(像直接拔掉电源,无法忽略或捕获)。

关键点:进程可以捕获信号并自定义行为(如保存数据再退出)。

(3) 信号优先级 ≈ 紧急电话 vs 短信
场景:你同时接到电话(高优先级)和短信(低优先级)。电话会打断当前任务,而短信可以稍后处理。

对应信号

  • SIGSEGV(段错误)会立即终止进程(像火警警报)。

  • SIGCHLD(子进程退出)可能被延迟处理(像垃圾邮件提醒)。

关键点:某些信号会抢占进程的当前执行。

(4)信号阻塞 ≈ 开启勿扰模式
场景:开会时开启手机勿扰模式,期间所有来电被静音(信号被阻塞),但结束后会收到未接通知(信号递送)。

对应操作:sigprocmask() 可阻塞信号,解除阻塞后信号才会生效。

关键点:阻塞≠忽略,信号只是被暂缓处理。

(5)信号丢失 ≈ 重复按电梯按钮
场景:电梯按钮被多次按下,但电梯只会响应一次(同类信号可能合并)。

对应问题:标准信号(如 SIGINT)可能丢失,实时信号(如 SIGRTMIN)会排队。

关键点:非实时信号是不可靠的(多次发送≈一次)。

(6)父子进程信号 ≈ 家长与孩子的约定
场景:家长(父进程)和孩子(子进程)约定:孩子崩溃时打电话通知(发送 SIGCHLD)。

对应机制:父进程通过 wait() 回收子进程资源,避免僵尸进程。

关键点:信号可用于进程间通信(简单场景)。

一个简单的信号案例

// sig.cc
#include <iostream>
#include <unistd.h>
int main()
{
	while(true)
	{
		std::cout << "I am a process, I am waiting signal!" << std::endl;
		sleep(1);
	}
}

运行后发送信号终止:

$ g++ sig.cc -o sig
$ ./sig
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^C

为什么?

  • 在终端运行程序时,按下 Ctrl+C 会向进程发送 SIGINT(中断信号)。

  • SIGINT 的默认行为是终止进程(Terminate)。

  • 因此,进程收到 SIGINT 后立即终止,程序退出。

信号系统调用

(1) signal() 系统函数

功能:设置特定信号的处理方式(捕获、忽略或恢复默认行为)。

头文件:#include <signal.h>

函数原型:

typedef void (*sighandler_t)(int);  // 函数指针类型
sighandler_t signal(int signum, sighandler_t handler);

参数:

  • signum:信号编号(如 SIGINT 的编号是 2)。

  • handler:信号处理函数指针,收到信号时回调该函数。

示例:

void handler(int signum) {
    std::cout << "Received signal: " << signum << std::endl;
}
signal(SIGINT, handler);  // 捕获 SIGINT(Ctrl+C)

(2) 示例代码解析

代码:

复制
#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int signum) {
    std::cout << "PID: " << getpid() << ", Signal: " << signum << std::endl;
}

int main() {
    std::cout << "PID: " << getpid() << std::endl;
    signal(SIGINT, handler);  // 捕获 SIGINT
    while (true) {
        std::cout << "Waiting for signal..." << std::endl;
        sleep(1);
    }
}

运行结果:

$ ./sig
PID: 12345
Waiting for signal...
Waiting for signal...
^CPID: 12345, Signal: 2  # 按下 Ctrl+C
Waiting for signal...
^CPID: 12345, Signal: 2  # 再次按下 Ctrl+C

关键点:

进程不退出:因为自定义的 handler 仅打印信息,未调用 exit(),进程继续执行。

信号异步性:Ctrl+C 可打断 sleep(1),说明信号是异步的。

(3) 信号处理的核心问题

a.为什么进程不退出?
signal() 仅修改信号的处理方式,不自动终止进程。

若需退出,应在 handler 中调用 exit()。

b. 信号处理是“自己处理”
信号处理函数(handler)是用户自定义的,由进程自身执行。

类比:快递员(操作系统)敲门(发送信号),你(进程)决定是否开门或忽略。

c. 前台进程与后台进程
前台进程:直接接收终端信号(如 Ctrl+C 的 SIGINT)。

后台进程(命令后加 &):不接收终端信号,需通过 kill 发送信号。

$ ./sig &   # 后台运行
$ kill -SIGINT 12345  # 手动发送 SIGINT

d.信号与进程关系

  • 异步性:信号可能在任何时间点打断进程。

  • 可靠性:标准信号(如 SIGINT)可能丢失,实时信号(SIGRTMIN~SIGRTMAX)可排队。

(4) 生活类比

场景 Linux 信号机制


快递员敲门(电话通知)| 操作系统发送信号(如 SIGINT)


你决定是否开门 | 进程捕获信号并执行 handler


直接拒收快递 | 忽略信号(signal(signum, SIG_IGN))


强制拆除房门(消防员)| SIGKILL(不可捕获/忽略)


(5) 补充知识点

信号默认行为:

  • SIGINT(Ctrl+C):终止进程。

  • SIGQUIT(Ctrl+\):终止并生成核心转储。

  • SIGTERM:优雅终止(可捕获)。

  • SIGKILL:强制终止(不可捕获)。

信号阻塞与未决信号:

使用 sigprocmask() 阻塞信号,信号会被标记为未决(pending),直到解除阻塞。

类比:开启勿扰模式,未接来电稍后处理。

nohup 命令:

使进程忽略 SIGHUP(终端关闭时发送的信号),常用于后台长期运行:

$ nohup ./sig &

信号安全函数:

在 handler 中只能调用异步信号安全函数(如 write()),避免使用 cout/printf(非线程安全)。

(6) 总结

信号本质:异步事件通知机制,由操作系统发送,进程决定处理方式。

核心函数:signal() 或更健壮的 sigaction()。

关键特性

  • 异步性(可能打断任何代码)。

  • 用户自定义处理(捕获/忽略)。

  • 前台/后台进程差异。

  • 应用场景:进程优雅退出、定时任务(SIGALRM)、父子进程通信(SIGCHLD)。

附:常用信号列表


信号  编号  默认行为  触发方式


SIGINT  2   终止    Ctrl+C


SIGQUIT 3  终止+核心   Ctrl+\


SIGTERM 15  终止     kill


SIGKILL   9  强制终止    kill -9


SIGSTOP  19   暂停进程   Ctrl+Z


信号宏定义:
在这里插入图片描述

在这里插入图片描述

信号处理(默认与忽略)

signal 函数

signal 是用于设置信号处理方式的标准库函数,其原型如下:

#include <signal.h>
typedef void (*__sighandler_t)(int);
__sighandler_t signal(int signum, __sighandler_t handler);

参数:

  • SIG_IGN:忽略信号。
  • SIG_DFL:执行默认动作。
  • 自定义函数:用户定义的信号处理函数。

返回值:

  • 成功时返回之前的信号处理函数。
  • 失败时返回 SIG_ERR。

示例代码

忽略信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
 
int main() {
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT, SIG_IGN); // 忽略 SIGINT 信号
    while (true) {
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

运行效果:
输入 Ctrl+C(发送 SIGINT 信号)时,程序无反应,继续运行。

执行默认动作

#include <iostream>
#include <unistd.h>
#include <signal.h>
 
int main() {
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT, SIG_DFL); // 执行默认动作
    while (true) {
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

运行效果:
输入 Ctrl+C 时,程序退出(默认动作是终止进程)。

默认与忽略宏定义解析

在信号处理中,SIG_DFL 和 SIG_IGN 是两个重要的宏:

#define SIG_DFL ((__sighandler_t) 0) /* 默认动作 */
#define SIG_IGN ((__sighandler_t) 1) /* 忽略信号 */

SIG_DFL:将信号处理函数设置为 0,表示执行默认动作。
SIG_IGN:将信号处理函数设置为 1,表示忽略信号。

这两个宏实际上是将整数 0 和 1 强制转换为函数指针类型 __sighandler_t。

信号处理机制

(1) 信号处理流程
a.内核检测到信号(如用户输入 Ctrl+C)。

b.内核根据进程的信号处理方式执行相应动作:

  • 忽略信号:直接丢弃。
  • 默认动作:执行系统预定义的行为(如终止进程)。
  • 自定义捕捉:切换到用户态,调用用户定义的信号处理函数。

(2) 注意事项
a.信号处理函数应尽可能简单,避免调用不可重入函数(如 printf、malloc 等)。

b.信号处理函数执行期间,系统会阻塞其他同类型信号(防止重入)。

sigaction 函数(高级信号处理)

signal 函数功能有限,推荐使用 sigaction 函数进行更精细的信号处理。

sigaction 函数原型

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:

  • signum:信号编号。
  • act:指向 sigaction 结构体的指针,定义新处理方式。
  • oldact:指向 sigaction 结构体的指针,保存旧处理方式(可为 NULL)。

sigaction 结构体:

struct sigaction {
    void (*sa_handler)(int);       // 信号处理函数或 SIG_IGN/SIG_DFL
    void (*sa_sigaction)(int, siginfo_t *, void *); // 替代信号处理函数(可选)
    sigset_t sa_mask;             // 信号处理期间阻塞的信号集
    int sa_flags;                 // 选项标志(如 SA_RESTART)
};

示例代码

#include <iostream>
#include <unistd.h>
#include <signal.h>
 
void handler(int signumber) {
    std::cout << "捕获信号: " << signumber << std::endl;
}
 
int main() {
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
 
    sigaction(SIGINT, &act, nullptr);
 
    while (true) {
        std::cout << "等待信号..." << std::endl;
        sleep(1);
    }
}

补充注释:
struct sigaction act:

  • 定义一个 sigaction 结构体变量,用于描述信号处理方式。

act.sa_handler = handler:

  • 将 sa_handler 成员设置为自定义的信号处理函数 handler。
  • 当 SIGINT 信号被捕获时,会调用 handler 函数。

sigemptyset(&act.sa_mask):

  • 初始化 sa_mask 成员为一个空集。
  • sa_mask 指定在信号处理函数执行期间需要阻塞的信号集。
  • 空集表示不阻塞任何信号。

act.sa_flags = 0:

  • 设置 sa_flags 成员为 0,表示不使用任何特殊选项。
  • 常见的选项包括 SA_RESTART(自动重启被信号中断的系统调用)。

总结

信号处理方式:

  • 忽略信号(SIG_IGN)。
  • 执行默认动作(SIG_DFL)。
  • 自定义捕捉信号(信号处理函数)。
  • signal 函数:简单易用,但功能有限。
  • sigaction 函数:功能强大,推荐用于复杂场景。

注意事项:

  • 信号处理函数应简单、快速。
  • 避免调用不可重入函数。(不可重入函数是指那些在执行过程中如果再次被调用,可能会导致数据不一致、程序崩溃或未定义行为的函数。这些函数通常依赖于全局状态、静态变量或不可中断的资源。

http://www.kler.cn/a/612144.html

相关文章:

  • java程序员实用英语学习总结
  • linux scp复制多层级文件夹到另一服务器免密及脚本配置
  • 【深度学习与实战】2.1、线性回归模型与梯度下降法先导
  • [250324] Kafka 4.0.0 版本发布:告别 ZooKeeper,拥抱 KRaft!| Wine 10.4 发布!
  • Apache Shiro 全面指南:从入门到高级应用
  • 网络安全可以去哪些单位工作
  • Windows 图形显示驱动开发-WDDM 2.7功能-MCDM KM 驱动程序实现指南(三)
  • Anaconda 安装NCL (Linux系统)
  • ArcGIS字段计算器的详细使用案例
  • 机器学习核心评估指标解析:AUC-ROC、RMSE、轮廓系数与PR AUC详解
  • 深度解析 | Android 12系统级禁用SIM卡功能实现与Framework层定制
  • 城电科技 | 光伏植物墙 一款会发电发光自动浇水的植物墙
  • STM32八股【2】-----ARM架构
  • OpenHarmony子系统开发 - init启动引导组件(七)
  • 在Windows docker desktop 中安装Dify
  • Android Studio编译问题
  • 单元测试mock
  • SSE SseEmitter.completeWithError(e) 触发的处理逻辑
  • Android 地区选择器或者其他选择器
  • WHAT - 程序员英语之美式发音学习系列(二)