Linux信号处理全解析

在 Linux 系统编程中,信号(Signal) 是一种异步通知机制,用于告知进程发生了某种事件。理解常见的信号及其默认行为对于编写健壮的应用程序至关重要。


目录

一、信号的分类与作用

[1. SIGHUP(信号编号:1)](#1. SIGHUP(信号编号:1))

[2. SIGINT(信号编号:2)](#2. SIGINT(信号编号:2))

[3. SIGQUIT(信号编号:3)](#3. SIGQUIT(信号编号:3))

[4. SIGILL(信号编号:4)](#4. SIGILL(信号编号:4))

[5. SIGABRT(信号编号:6)](#5. SIGABRT(信号编号:6))

[6. SIGFPE(信号编号:8)](#6. SIGFPE(信号编号:8))

[7. SIGKILL(信号编号:9)](#7. SIGKILL(信号编号:9))

[8. SIGUSR1 和 SIGUSR2(信号编号:10 和 12)](#8. SIGUSR1 和 SIGUSR2(信号编号:10 和 12))

[9. SIGSEGV(信号编号:11)](#9. SIGSEGV(信号编号:11))

[10. SIGPIPE(信号编号:13)](#10. SIGPIPE(信号编号:13))

[11. SIGALRM(信号编号:14)](#11. SIGALRM(信号编号:14))

[12. SIGTERM(信号编号:15)](#12. SIGTERM(信号编号:15))

[13. SIGCHLD(信号编号:17)](#13. SIGCHLD(信号编号:17))

[14. SIGCONT(信号编号:18)](#14. SIGCONT(信号编号:18))

[15. SIGSTOP(信号编号:19)](#15. SIGSTOP(信号编号:19))

[16. SIGTSTP(信号编号:20)](#16. SIGTSTP(信号编号:20))

二、信号的处理方式

示例代码:自定义信号处理函数

[使用 sigaction() 更精确地设置信号处理函数](#使用 sigaction() 更精确地设置信号处理函数)

三、信号的阻塞与未决

示例代码:演示信号阻塞

四、总结知识点图解(知识树状图)

五、课后练习建议


一、信号的分类与作用

Linux 定义了多种信号,每个信号都有一个编号和默认的行为。下面列出了一些常用的信号及其用途:

1. SIGHUP(信号编号:1)

  • 默认行为:终止
  • 用途:当控制终端关闭或网络连接断开时发送给进程。通常用于通知守护进程重新加载配置文件。

2. SIGINT(信号编号:2)

  • 默认行为:终止
  • 用途:由用户通过键盘输入中断字符(通常是 Ctrl+C)产生,要求进程停止执行。

3. SIGQUIT(信号编号:3)

  • 默认行为:核心转储后终止
  • 用途 :类似于 SIGINT,但要求进程生成核心转储文件以便调试。

4. SIGILL(信号编号:4)

  • 默认行为:核心转储后终止
  • 用途:非法指令错误,例如尝试执行无效的操作码。

5. SIGABRT(信号编号:6)

  • 默认行为:核心转储后终止
  • 用途 :由调用 abort() 函数触发,用于异常终止程序并生成核心转储文件。

6. SIGFPE(信号编号:8)

  • 默认行为:核心转储后终止
  • 用途:浮点运算错误,如除以零或溢出。

7. SIGKILL(信号编号:9)

  • 默认行为:强制终止
  • 用途:无法被捕获或忽略,用于立即终止进程,通常作为最后手段。

8. SIGUSR1SIGUSR2(信号编号:10 和 12)

  • 默认行为:终止
  • 用途:用户定义的信号,可用于应用程序内部通信或特殊处理逻辑。

9. SIGSEGV(信号编号:11)

  • 默认行为:核心转储后终止
  • 用途:无效内存引用(段错误),常用于调试内存访问问题。

10. SIGPIPE(信号编号:13)

  • 默认行为:终止
  • 用途:写入管道或 socket 连接已关闭时触发。

11. SIGALRM(信号编号:14)

  • 默认行为:终止
  • 用途 :由 alarm() 函数设置的定时器超时时触发,常用于实现定时任务。

12. SIGTERM(信号编号:15)

  • 默认行为:终止
  • 用途 :请求进程正常终止,可以被捕获和处理,比 SIGKILL 更友好。

13. SIGCHLD(信号编号:17)

  • 默认行为:忽略
  • 用途:子进程状态改变时发送给父进程,用于回收僵尸进程。

14. SIGCONT(信号编号:18)

  • 默认行为:继续执行
  • 用途:如果进程被暂停,则恢复其执行。

15. SIGSTOP(信号编号:19)

  • 默认行为:暂停
  • 用途:不可捕获或忽略,用于暂停进程执行。

16. SIGTSTP(信号编号:20)

  • 默认行为:暂停
  • 用途 :类似 SIGSTOP,但可被捕获或忽略,通常由终端输入 Ctrl+Z 触发。

二、信号的处理方式

对于每一个信号,可以采取以下三种处理方式之一:

  1. 默认动作:根据信号类型采取相应操作(如终止进程、忽略信号等)。
  2. 忽略信号 :使用 signal()sigaction() 注册忽略处理函数。
  3. 自定义处理函数:为信号指定一个处理函数,当信号到达时会调用该函数。

示例代码:自定义信号处理函数

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    // 设置 SIGINT 的处理函数
    if (signal(SIGINT, handle_signal) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    printf("Press Ctrl+C to test...\n");
    while (1) {
        pause();  // 挂起等待信号
    }

    return 0;
}

使用 sigaction() 更精确地设置信号处理函数

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("Press Ctrl+C to test...\n");
    while (1) {
        pause();
    }

    return 0;
}

三、信号的阻塞与未决

有时候我们需要暂时阻止某些信号的传递,即使信号已经发送也不会立即处理,直到解除阻塞为止。此时这些信号处于"未决"状态。

示例代码:演示信号阻塞

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void handle_signal(int sig) {
    printf("Caught signal %d\n", sig);
}

int main() {
    sigset_t newmask, oldmask;

    // 设置 SIGINT 的处理函数
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);

    // 阻塞 SIGINT
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }

    printf("Blocked SIGINT, press Ctrl+C...\n");
    sleep(10);  // 在这期间按 Ctrl+C 不会触发 handler

    // 解除阻塞
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
    printf("Unblocked SIGINT\n");

    pause();  // 现在按 Ctrl+C 会触发 handler

    return 0;
}

四、总结知识点图解(知识树状图)

复制代码
常见信号及其用途
│
├── SIGHUP
│   ├── 编号:1
│   └── 用途:通知守护进程重新加载配置
│
├── SIGINT
│   ├── 编号:2
│   └── 用途:用户请求中断(Ctrl+C)
│
├── SIGQUIT
│   ├── 编号:3
│   └── 用途:用户请求退出并生成 core 文件(Ctrl+\)
│
├── SIGILL
│   ├── 编号:4
│   └── 用途:非法指令错误
│
├── SIGABRT
│   ├── 编号:6
│   └── 用途:调用 abort() 时生成 core 文件
│
├── SIGFPE
│   ├── 编号:8
│   └── 用途:浮点运算错误
│
├── SIGKILL
│   ├── 编号:9
│   └── 用途:强制终止进程
│
├── SIGUSR1 / SIGUSR2
│   ├── 编号:10 / 12
│   └── 用途:用户定义信号
│
├── SIGSEGV
│   ├── 编号:11
│   └── 用途:无效内存引用
│
├── SIGALRM
│   ├── 编号:14
│   └── 用途:定时器超时
│
├── SIGTERM
│   ├── 编号:15
│   └── 用途:请求进程正常终止
│
├── SIGCHLD
│   ├── 编号:17
│   └── 用途:子进程状态变化
│
├── SIGCONT
│   ├── 编号:18
│   └── 用途:继续暂停的进程
│
├── SIGSTOP
│   ├── 编号:19
│   └── 用途:暂停进程
│
└── SIGTSTP
    ├── 编号:20
    └── 用途:终端停止命令(Ctrl+Z)

五、课后练习建议

  1. 编写程序注册不同信号的处理函数,并通过 kill 命令发送信号测试。
  2. 实现一个简单的计时器,使用 alarm()SIGALRM 处理函数实现定时任务。
  3. 修改上面的例子,让程序在接收到 SIGINT 后不退出,而是打印一条消息并继续运行。
  4. 使用 sigaction() 替代 signal(),体验更精细的信号处理控制。
  5. 探索如何使用 sigprocmask()sigsuspend() 实现更复杂的信号同步逻辑。
bash 复制代码
gcc your_signal_program.c -o your_signal_program
./your_signal_program &
kill -<signal_number> <PID>