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

Linux 信号 (Signals)

一、概念

信号是操作系统内核向用户空间进程发送的一种异步通知机制。它用于通知进程某个特定事件发生了。

1. 信号的生命周期

一个信号的处理遵循以下流程:

  1. 产生 (Generation): 由内核、其他进程或硬件事件(如 SIGSEGV 由 MMU 产生)创建。

  2. 递送 (Delivery): 内核将信号传递给目标进程,并促使进程采取行动。

  3. 处理 (Handling): 目标进程收到信号后执行预定的操作。

2. 信号的来源
  • 硬件异常: 由 CPU 检测到,内核将其转换为信号发给相关进程。

    • SIGSEGV (Segmentation Fault): 非法内存访问。

    • SIGFPE (Floating-Point Exception): 算术错误,如除零。

    • SIGILL (Illegal Instruction): 执行了非法指令。

  • 终端相关: 来自键盘的中断。

    • SIGINT (Interrupt): Ctrl+C,通常终止进程。

    • SIGQUIT (Quit): Ctrl+\,终止并生成 core dump。

    • SIGTSTP (Terminal Stop): Ctrl+Z,暂停进程。

  • 软件事件

    • SIGCHLD: 子进程状态改变(停止、退出)。

    • SIGPIPE: 管道破裂(读端关闭后继续写)。

    • SIGALRM: 由 alarm() 或 setitimer() 设置的定时器超时。

    • SIGUSR1SIGUSR2: 用户自定义的信号。

  • 其他进程: 使用 kill() 系统调用发送。

3. 信号的处理方式

进程对信号的处理有三种选择:

  1. 默认动作 (Default Action): 大多数信号的默认动作是终止进程。有些是忽略(SIGCHLD)或生成 core dump(SIGQUIT)。

  2. 忽略信号 (Ignore): 告诉内核无需处理该信号(SIGKILL 和 SIGSTOP 不能被忽略或捕获)。

  3. 捕获信号 (Catch): 进程可以注册一个信号处理函数 (Signal Handler),当信号到来时,内核会中断进程的正常执行流,转而执行这个函数。

4. 重要系统调用和函数
  • signal(): 简单的注册信号处理函数(已过时,可移植性差)。

  • sigaction(): 现代、标准且功能更强大的方法,用于注册信号处理函数,并能精确控制信号行为。

  • kill(): 向指定进程发送信号。

  • alarm(): 设置一个实时定时器,超时后产生 SIGALRM

  • pause(): 挂起调用进程,直到任何信号到达。

  • 信号集操作函数: sigemptyset()sigaddset()sigprocmask() 等,用于管理信号掩码,阻塞或解除阻塞特定信号。

5. 信号的特点和注意事项
  • 异步性: 信号可能在进程执行的任何时刻到达。

  • 可靠性: 标准信号(1-31)是不可靠的,相同信号在处理期间再次到来可能会丢失。实时信号(34-64)是可靠的,支持排队。

  • 可重入性 (Reentrancy): 在信号处理函数中,只能调用异步信号安全 (async-signal-safe) 的函数(如 write()_exit())。严禁调用 malloc()printf() 等非安全函数,否则可能导致死锁或未定义行为。

  • 全局变量和 volatile: 信号处理函数和主程序之间通信的全局变量应声明为 volatile,防止编译器优化导致意外行为。

二、常见信号

信号可以分为以下几类:终止信号中断/暂停信号异常信号作业控制信号用户自定义信号

1. 终止信号 (Termination Signals)

这些信号默认会导致进程终止。

信号名默认动作描述 & 触发场景可否捕获/忽略核心转储
SIGHUP1Terminate挂起 (Hangup)。当控制终端关闭时发送给其关联的前台进程组。也常用于通知守护进程重新加载配置文件(如 nginx -s reload)。
SIGINT2Terminate中断 (Interrupt)。来自键盘的中断,通常由用户按下 Ctrl+C 产生。用于优雅地终止前台进程。
SIGQUIT3Core退出 (Quit)。来自键盘的退出,通常由用户按下 Ctrl+\ 产生。用于终止进程并生成核心转储 (core dump),便于后续调试。
SIGTERM15Terminate终止 (Terminate)。这是 kill 命令的默认信号。它要求进程正常终止,允许进程进行清理工作(关闭文件、释放资源等)。是优雅关闭进程的首选方式。
SIGKILL9Terminate杀死 (Kill)立即、强制终止进程。该信号不能被捕获、阻塞或忽略。进程会立刻死亡,没有机会进行任何清理。是确保进程终止的“杀手锏”,但应作为最后手段使用。

使用场景总结

  • 想优雅地结束一个进程:先尝试 kill <PID> 或 kill -TERM <PID>(发送 SIGTERM)。

  • 进程不响应 SIGTERM:尝试 kill -INT <PID>(发送 SIGINT)。

  • 需要强制结束一个“顽固”进程:使用 kill -9 <PID>(发送 SIGKILL)。

  • 想让进程退出并留下调试文件:使用 kill -QUIT <PID>(发送 SIGQUIT),需确保系统允许生成 core dump(ulimit -c unlimited)。

2. 异常信号 (Exception Signals)

这些信号由 CPU 检测到异常后,由内核发送给进程。

信号名默认动作描述 & 触发场景可否捕获/忽略核心转储
SIGILL4Core非法指令 (Illegal Instruction)。进程试图执行一条非法、格式错误、特权级别不对的CPU指令。通常由损坏的可执行文件或尝试执行数据段导致。
SIGTRAP5Core陷阱跟踪 (Trace Trap)。由断点指令或其他陷阱指令触发,主要用于调试器(如 gdb)实现单步执行和断点。
SIGABRT6Core中止 (Abort)。由进程自己调用 abort() 函数产生。表示进程检测到了致命错误并主动调用中止。C库中的 assert() 失败也会触发此信号。
SIGBUS7Core总线错误 (Bus Error)。无效的内存访问,但不同于 SIGSEGV。例如,访问了物理地址不存在违反了内存对齐限制的内存(如某些架构上要求4字节对齐,但你访问了一个奇地址)。
SIGFPE8Core浮点异常 (Floating-Point Exception)。发生了致命的算术运算错误,如除以零、溢出、非法操作(如对负数开平方)。
SIGSEGV11Core段错误 (Segmentation Fault)最常见的异常信号。表示进程尝试访问未被分配给它的虚拟内存地址,或试图以不允许的方式(如写只读内存)访问内存。通常是指针错误(空指针、野指针、缓冲区溢出)的标志。

3. 作业控制信号 (Job Control Signals)

这些信号用于 Shell 管理前台和后台作业。

信号名默认动作描述 & 触发场景可否捕获/忽略
SIGSTOP17,19,23Stop停止 (Stop)暂停进程的执行。该信号不能被捕获、阻塞或忽略。是 Ctrl+Z 的另一种实现。
SIGTSTP18,20,24Stop终端停止 (Terminal Stop)。来自终端的停止信号,通常由用户按下 Ctrl+Z 产生。请求进程暂停运行(转入后台),允许它稍后恢复(用 fg 或 bg 命令)。
SIGCONT19,18,25Continue继续 (Continue)。让一个被 SIGSTOP 或 SIGTSTP 暂停的进程恢复执行。即使被忽略,它也能唤醒进程。

4.I/O 相关信号

信号核心用途处理建议
SIGPIPE处理连接断开后的写入错误忽略它 (SIG_IGN),在代码中检查 EPIPE 错误。
SIGIO异步通知文件描述符就绪在某些设备驱动中有用,但在网络编程中优先选择 epoll
SIGURG处理带外数据 (OOB)为需要紧急命令的应用程序设置处理函数。
SIGCHLD回收子进程资源,避免僵尸进程必须在使用 fork() 的服务器中设置处理函数,并使用 waitpid() 循环。

5. 其他重要信号

信号名默认动作描述 & 触发场景可否捕获/忽略
SIGCHLD17,20,18Ignore子进程状态改变 (Child Status Changed)。当子进程停止恢复终止时,内核会向其父进程发送此信号。父进程可以在此信号处理函数中调用 wait() 或 waitpid() 来回收子进程资源,防止僵尸进程的产生。
SIGPIPE13Terminate管道破裂 (Broken Pipe)。当进程向一个读端已关闭的管道、Socket 或 FIFO 写入数据时,内核会发送此信号。常见于网络编程中,对端关闭了连接,本方仍在写入。
SIGALRM14Terminate闹钟信号 (Alarm Clock)。由 alarm() 或 setitimer() 设置的定时器超时后触发。常用于实现超时机制。
SIGUSR110,30,16Terminate用户自定义信号 1 (User-Defined Signal 1)
SIGUSR212,31,17Terminate用户自定义信号 2 (User-Defined Signal 2)SIGUSR1 和 SIGUSR2 没有预定义的含义,完全由应用程序自行定义其行为。常用于进程间自定义通信,例如通知守护进程切换日志文件、报告状态等。

三、示例代码

1. SIGPIPE - 必须处理的信号

场景:你的网络服务器客户端断开连接后,服务器试图回复一个“再见”消息,导致 send() 调用触发 SIGPIPE,进程默认被终止。

解决方案

  • 最佳实践:忽略它。直接忽略 SIGPIPE 信号,让 write() 或 send() 函数返回错误码 -1 并设置 errno 为 EPIPE。这样你可以在代码中逻辑地处理该错误,而不是让进程突然死亡。

    c

    // 在程序初始化时忽略 SIGPIPE
    #include <signal.h>
    signal(SIGPIPE, SIG_IGN);
  • 之后,你的 I/O 调用会安全地失败:

    c

    ssize_t bytes_sent = send(socket_fd, buffer, len, MSG_NOSIGNAL); // 也可以使用 MSG_NOSIGNAL 标志避免产生信号
    if (bytes_sent == -1) {if (errno == EPIPE) {// 对端已经关闭,安全地关闭本地的socket并清理资源close(socket_fd);}// 处理其他错误...
    }
2. SIGIO / SIGPOLL - 异步 I/O 通知

场景:你想实现一个高性能的应用程序,在没有使用 select/poll/epoll 的情况下,希望内核在文件描述符就绪时主动通知你。

解决方案

  1. 为信号安装一个处理函数。

  2. 使用 fcntl 设置文件描述符的所有者和异步标志。

    c

    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>void io_handler(int sig, siginfo_t *info, void *context) {int fd = info->si_fd; // 哪个fd就绪了?int band = info->si_band; // 什么事件?(POLLIN, POLLOUT, etc.)// ... 处理I/O ...
    }int main() {struct sigaction sa;sa.sa_sigaction = io_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_SIGINFO; // 为了获取详细的siginfo_tsigaction(SIGIO, &sa, NULL);int fd = open("/dev/some_device", O_RDONLY);// 设置当前进程为接收SIGIO信号的进程fcntl(fd, F_SETOWN, getpid());// 启用该fd的异步模式int flags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, flags | O_ASYNC);// ... 主循环可以做其他事,等待信号中断 ...
    }

注意:在现代高性能网络中,SIGIO 已经很少用于 socket,因为 epoll 的性能和可扩展性远优于信号驱动的 I/O 模型。但它仍然在某些设备驱动(如串口、自定义硬件)中非常有用。

3. SIGURG - 处理带外数据

场景:需要通过网络发送一个紧急命令(例如,远程控制程序的“紧急停止”)。

解决方案

  1. 捕获 SIGURG 信号。

  2. 在信号处理函数中,使用 recv() 和 MSG_OOB 标志来读取带外数据。

    c

    // 发送端
    send(sockfd, "!", 1, MSG_OOB);// 接收端
    void urg_handler(int sig) {char oob_data;recv(sockfd, &oob_data, 1, MSG_OOB);// 处理紧急命令
    }
    // ... 注册信号处理函数 ...
    signal(SIGURG, urg_handler);
    // 必须设置socket是它的进程所有者才能接收SIGURG
    fcntl(sockfd, F_SETOWN, getpid());
4. SIGCHLD - 管理子进程

场景:一个并发服务器使用 fork() 为每个客户端创建一个新的子进程。子进程退出后,如果不回收会成为僵尸进程。

解决方案

c

void child_handler(int sig) {int saved_errno = errno; // 保存errno,因为waitpid可能会修改它while (waitpid(-1, NULL, WNOHANG) > 0) { // 使用循环和WNOHANG回收所有已退出的子进程continue;}errno = saved_errno;
}int main() {// 设置SIGCHLD处理函数struct sigaction sa;sa.sa_handler = child_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // SA_NOCLDSTOP: 子进程停止时不发出信号sigaction(SIGCHLD, &sa, NULL);while(1) {// ... accept 连接 ...pid_t pid = fork();if (pid == 0) {// 子进程处理逻辑exit(0);}// 父进程继续循环}
}

http://www.dtcms.com/a/342629.html

相关文章:

  • 鱼眼相机去畸变的算法原理(一)
  • WEB服务器(静态/动态网站搭建)
  • 循环神经网络实战:用 LSTM 做中文情感分析(二)
  • Mokker AI:一键更换照片背景的AI神器
  • 鸿蒙生态开发全栈指南
  • mac的m3芯片安装mysql
  • 统计全为1的正方形子矩阵-二维dp
  • 机器学习中的两大核心算法:k 均值聚类与集成学习
  • c# 和 c++ 怎样结合
  • 基于springboot的美术馆管理系统
  • 迁移docker容器的mysql数据库到本地
  • CQRS 的优缺点
  • 【图像算法 - 20】慧眼识病:基于深度学习与OpenCV的植物叶子疾病智能识别系统
  • uniapp跨域怎么解决
  • uniapp 获取手机状态栏的高度
  • 2025-08-21 Python进阶1——控制流语句
  • K 均值聚类:从概念到实践的无监督学习之旅
  • 面试后的跟进策略:如何提高录用几率并留下专业印象
  • 暂停更新的高速下载网盘,作者可能不再维护
  • Oracle: cannot decrease column length because some value is too big
  • .NET Core MongoDB 查询数据异常及解决
  • 分布式集群压测+grafana+influxdb+Prometheus详细步骤
  • 详细说明http协议特别是conten-length和chunk编码,并且用linux的命令行演示整个过程
  • Python读取和设置PNG图片的像素值
  • 软件漏洞扫描的测试内容(二)
  • DzzOffice V2.3.7 核心功能升级与关键问题修复,体验全面优化!
  • 计算机网络-1——第一阶段
  • 【苹果软件】Prism Mac 9.4苹果系统免费安装包英文版 Graphpad Prism for Mac 9.4软件免费下载与详细图文教程!!
  • UGUI源码剖析(12):实战演练——从零构建一个健壮的Gradient顶点特效
  • 虚幻基础:目标值之间的过渡