​ 信号(Signal)是 Linux 系统中最基础、最核心的进程间通信(IPC)机制之一,本质是内核向进程发送的软中断,用于实现异步事件通知。本文将从 IPC 通信特点与进程 / 线程对比入手,深入讲解信号的核心 API、使用场景,并通过多个可运行的实战示例,帮助你彻底掌握 Linux 信号的使用方式。

一、详解 IPC 通信特点,进程与线程的核心对比

要理解信号的设计初衷和使用场景,首先需要明确进程间通信的本质,以及进程与线程在通信维度的核心差异。

1.1 进程间通信(IPC)的核心概念与特点

进程间通信(Inter-Process Communication,IPC)是指两个或多个独立进程之间交换数据、传递信息、同步行为的机制。Linux 系统中,IPC 机制包括信号、管道、消息队列、共享内存、套接字等,而信号是其中最轻量、最异步的一种。

IPC 的核心特点可总结为以下 6 点:

  1. 进程独立性:每个进程拥有独立的虚拟地址空间,用户态下无法直接访问其他进程的内存,因此所有 IPC 机制都需要内核介入(内核作为中间层完成数据转发或事件通知)。

  2. 通信异步性:信号是典型的异步 IPC 机制 —— 进程无需主动轮询事件,信号到达时内核会中断进程的正常执行流程,触发预设的处理逻辑;而管道、共享内存等则偏向同步通信(进程需主动读写)。

  3. 数据传输能力

    :不同 IPC 机制的传输能力差异显著:

    • 信号:仅传递 “事件标识”(整数),无法携带自定义数据(实时信号可携带少量数据,但非核心能力);
    • 管道 / 套接字:可传输流式数据,但有内核拷贝开销;
    • 共享内存:可传输大量数据,无内核拷贝,但需手动处理同步互斥。
  4. 同步与互斥:IPC 需解决多进程竞态问题(如同时读写共享资源),但信号本身不提供同步互斥能力,需配合锁、信号量等机制使用。

  5. 开销与效率:信号的内核开销极低(仅传递一个整数),但功能简单;共享内存效率最高,但开发复杂度高;管道 / 套接字居中。

  6. 可靠性

    :信号分为

    不可靠信号

    (1-31 号,非实时信号)和

    可靠信号

    (34-64 号,实时信号):

    • 不可靠信号:不排队,若同一信号多次触发且未处理,可能丢失;
    • 可靠信号:排队机制,确保所有信号都能被递达,不会丢失。

1.2 进程与线程的核心对比

进程和线程是操作系统中两个核心的执行单元,二者的设计目标不同,导致通信方式和特性差异巨大,具体对比如下表:

表格

维度进程通信线程通信
地址空间完全独立,必须通过 IPC 机制通信共享进程地址空间,可直接访问全局变量
通信方式信号、管道、共享内存、套接字等全局变量、互斥锁、条件变量、信号量等
通信开销高(需内核介入,有上下文切换)低(用户态即可完成,无内核拷贝)
数据安全性天然隔离,数据泄露风险低共享数据易出现竞态,需同步机制保护
异步通知信号是主要实现方式可通过条件变量、信号量模拟异步通知
错误影响范围单个进程崩溃不影响其他进程线程崩溃可能导致整个进程退出
信号作用范围信号默认发给进程,所有线程共享处理函数可通过pthread_kill发送给指定线程

关键补充

  • 进程是 “资源分配的基本单位”,线程是 “CPU 调度的基本单位”;
  • 线程间通信无需内核介入(直接访问共享内存),但需处理同步问题;
  • 信号是少数能跨进程、跨线程的通用通知机制,也是进程异常处理的核心手段(如段错误触发 SIGSEGV 信号)。

二、Linux 信号 API 与典型使用场景

信号的生命周期分为四个阶段:产生(生成)→ 注册(未决)→ 递送(触发)→ 处理。Linux 提供了一套完整的 API 来操作信号的全生命周期,下面逐一讲解核心 API 及使用场景。

2.1 信号的基础认知

2.1.1 信号的本质与默认动作

信号是内核向进程发送的 “异步事件通知”,常见的信号来源包括:

  • 用户操作:如Ctrl+C触发 SIGINT(2 号)、Ctrl+\触发 SIGQUIT(3 号);
  • 硬件异常:如段错误触发 SIGSEGV(11 号)、浮点错误触发 SIGFPE(8 号);
  • 进程主动发送:如kill命令、kill()函数发送信号;
  • 内核事件:如子进程退出触发 SIGCHLD(17 号)、定时器到期触发 SIGALRM(14 号)。

每个信号都有预设的默认动作,核心类型包括:

  • Term:终止进程(如 SIGINT、SIGTERM(15 号));
  • Core:终止进程并生成 core dump 文件(如 SIGSEGV、SIGABRT(6 号));
  • Ign:忽略信号(如 SIGCHLD 默认忽略);
  • Stop:暂停进程(如 SIGSTOP(19 号),不可捕获 / 忽略);
  • Cont:恢复暂停的进程(如 SIGCONT(18 号))。
2.1.2 信号的分类
类型编号范围特点典型信号
不可靠信号1-31不排队、可能丢失、无参数SIGINT、SIGTERM
可靠信号34-64排队、不丢失、可带参数SIGRTMIN+0~30

2.2 核心信号 API 详解

2.2.1 信号处理函数注册

信号的核心操作是 “自定义处理逻辑”,Linux 提供了两个核心函数:signal()(简单但不健壮)和sigaction()(推荐、POSIX 标准)。

(1)signal ():简易注册函数

函数原型:

#include <signal.h>
// 定义信号处理函数类型
typedef void (*sighandler_t)(int);
// 注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);

参数说明:

  • signum:要处理的信号编号(如SIGINT=2);

  • handler
    

    :处理逻辑,可选值:

    • SIG_IGN:忽略该信号;
    • SIG_DFL:恢复默认动作;
    • 自定义函数指针:参数为信号编号,无返回值。

返回值:成功返回旧的处理函数指针,失败返回SIG_ERR

缺点:

  • 跨平台实现不一致(不同系统处理逻辑有差异);
  • 信号处理函数执行后,部分系统会恢复为默认动作;
  • 无法设置信号屏蔽集、无法获取信号详细信息。
(2)sigaction ():健壮注册函数(推荐)

函数原型:

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

核心结构体struct sigaction

struct sigaction {
    // 普通处理函数(同signal())
    void (*sa_handler)(int);
    // 高级处理函数(带信号详细信息)
    void (*sa_sigaction)(int, siginfo_t *, void *);
    // 处理信号时屏蔽的信号集
    sigset_t sa_mask;
    // 标志位(控制信号处理行为)
    int sa_flags;
    // 已废弃,无需关注
    void (*sa_restorer)(void);
};

参数说明:

  • act:新的信号处理配置(NULL 表示不修改);

  • oldact:输出旧的配置(NULL 表示不获取);

  • sa_flags
    

    :核心标志位,常用值:

    • SA_RESTART:系统调用被信号中断后自动重启;
    • SA_SIGINFO:启用sa_sigaction(高级处理函数);
    • SA_NODEFER:处理信号时不屏蔽自身(默认会屏蔽)。

返回值:成功返回 0,失败返回 - 1。

优点:跨平台兼容、可配置屏蔽集、可获取信号详细信息(如发送方 PID)。

2.2.2 信号发送函数

用于主动向进程 / 进程组发送信号,核心函数包括kill()raise()abort()alarm()

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

函数原型:

#include <signal.h>
int kill(pid_t pid, int sig);

参数说明:

  • pid
    

    :目标进程 / 进程组 ID:

    • pid > 0:发送给 PID 为pid的进程;
    • pid = 0:发送给当前进程组的所有进程;
    • pid = -1:发送给所有有权限的进程;
    • pid < -1:发送给进程组 ID 为-pid的所有进程;
  • sig:要发送的信号(sig=0时仅检测进程是否存在,不发送信号)。

返回值:成功返回 0,失败返回 - 1。

(2)raise ():向当前进程发送信号

等价于kill(getpid(), sig),原型:

#include <signal.h>
int raise(int sig);
(3)abort ():强制终止当前进程

向当前进程发送SIGABRT(6 号)信号,强制终止并生成 core dump 文件(无法忽略),原型:

#include <stdlib.h>
void abort(void);
(4)alarm ():设置定时器(触发 SIGALRM)

设置秒级定时器,到期后内核向当前进程发送SIGALRM(14 号)信号,原型:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

参数:seconds=0表示取消定时器;

返回值:之前未到期的定时器剩余秒数。

2.2.3 信号集操作函数

信号集(sigset_t)是表示多个信号的集合,用于屏蔽信号、查询未决信号,核心操作函数:

#include <signal.h>
// 清空信号集
int sigemptyset(sigset_t *set);
// 填充所有信号到集合
int sigfillset(sigset_t *set);
// 添加指定信号到集合
int sigaddset(sigset_t *set, int signum);
// 从集合删除指定信号
int sigdelset(sigset_t *set, int signum);
// 判断信号是否在集合中(1=是,0=否)
int sigismember(const sigset_t *set, int signum);
2.2.4 信号屏蔽与未决信号函数
(1)sigprocmask ():设置进程信号屏蔽字

屏蔽指定信号(使其进入 “未决” 状态,暂时不处理),原型:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数how

  • SIG_BLOCK:将set中的信号添加到屏蔽集;
  • SIG_UNBLOCK:从屏蔽集删除set中的信号;
  • SIG_SETMASK:用set替换当前屏蔽集。
(2)sigpending ():获取未决信号集

查询当前被屏蔽、尚未处理的信号,原型:

#include <signal.h>
int sigpending(sigset_t *set);
2.2.5 信号等待函数
(1)pause ():挂起进程等待信号

暂停进程执行,直到收到一个信号并处理,原型:

#include <unistd.h>
int pause(void);

返回值:始终返回 - 1,errno=EINTR(被信号中断)。

(2)sigsuspend ():原子操作等待信号

解决pause()的竞态问题(先屏蔽信号再挂起),原型:

#include <signal.h>
int sigsuspend(const sigset_t *mask);

2.3 信号的典型使用场景

  1. 进程控制:终止 / 暂停 / 恢复进程(如kill -9 PID发送 SIGKILL 终止进程、kill -STOP PID暂停进程);
  2. 异常处理:捕获程序运行异常(如 SIGSEGV 段错误、SIGFPE 浮点错误),执行资源清理、日志记录;
  3. 超时控制:通过alarm()+SIGALRM实现函数 / 网络请求超时终止;
  4. 异步通知:进程间异步传递事件(如子进程退出触发 SIGCHLD,父进程捕获后回收僵尸进程);
  5. 终端交互:响应终端操作(如Ctrl+C触发 SIGINT,终止前台进程);
  6. 实时事件通知:实时信号(34+)用于工业控制、嵌入式场景,确保通知不丢失。

三、信号实战示例与深度解析

下面通过 5 个递进式示例,从基础到综合,讲解信号的实际使用方式,所有代码均可直接编译运行。

3.1 示例 1:基础信号捕获(signal ())

功能:捕获Ctrl+C触发的 SIGINT 信号,修改默认 “终止进程” 动作,改为自定义清理后退出。

完整代码(sigint_demo.c):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// 自定义SIGINT信号处理函数
void sigint_handler(int signum) {
    // signum为触发的信号编号(SIGINT=2)
    printf("\n[处理函数] 捕获到信号:%d(SIGINT)\n", signum);
    // 模拟资源清理:关闭文件、释放内存等
    printf("[处理函数] 执行清理操作:关闭日志文件、释放网络连接...\n");
    // 主动退出进程
    exit(EXIT_SUCCESS);
}

int main() {
    // 注册SIGINT信号的处理函数
    if (signal(SIGINT, sigint_handler) == SIG_ERR) {
        perror("signal注册失败"); // 打印错误原因
        exit(EXIT_FAILURE);
    }

    // 打印进程PID,方便调试
    printf("程序启动,PID:%d\n", getpid());
    printf("按下Ctrl+C触发SIGINT信号...\n");

    // 无限循环等待信号(避免进程退出)
    while (1) {
        printf("程序正常运行中...\n");
        sleep(1); // 每秒打印一次,避免CPU占用过高
    }

    return 0;
}
代码解析:
  1. 头文件signal.h(信号 API)、unistd.hsleep()/getpid())、stdlib.hexit());
  2. 处理函数sigint_handler接收信号编号,执行清理逻辑后退出;
  3. 信号注册signal(SIGINT, sigint_handler)替换 SIGINT 的默认动作;
  4. 主循环while(1)保持进程运行,sleep(1)降低 CPU 占用。
运行步骤:
# 编译
gcc sigint_demo.c -o sigint_demo
# 运行
./sigint_demo
输出结果:
程序启动,PID:12345
按下Ctrl+C触发SIGINT信号...
程序正常运行中...
程序正常运行中...
^C(按下Ctrl+C)
[处理函数] 捕获到信号:2(SIGINT)
[处理函数] 执行清理操作:关闭日志文件、释放网络连接...

3.2 示例 2:健壮信号处理(sigaction ())

功能:使用sigaction()注册 SIGUSR1 信号,配置屏蔽集、获取信号详细信息(发送方 PID)。

完整代码(sigaction_demo.c):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

// 高级信号处理函数(带详细信息)
void sigusr1_handler(int signum, siginfo_t *info, void *context) {
    printf("\n=== 捕获到SIGUSR1信号 ===\n");
    printf("信号编号:%d\n", signum);          // 信号编号(SIGUSR1=10)
    printf("发送进程PID:%d\n", info->si_pid); // 发送方进程PID
    printf("信号携带值:%d\n", info->si_value.sival_int); // 信号携带的自定义值
    printf("=========================\n");
}

int main() {
    struct sigaction act;
    // 清空结构体(避免随机内存值导致异常)
    memset(&act, 0, sizeof(act));

    // 配置高级处理函数
    act.sa_sigaction = sigusr1_handler;
    // 标志位:启用sa_sigaction + 系统调用重启
    act.sa_flags = SA_SIGINFO | SA_RESTART;

    // 初始化屏蔽集:处理SIGUSR1时屏蔽SIGINT(Ctrl+C)
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGINT);

    // 注册SIGUSR1信号
    if (sigaction(SIGUSR1, &act, NULL) == -1) {
        perror("sigaction注册失败");
        exit(EXIT_FAILURE);
    }

    printf("程序启动,PID:%d\n", getpid());
    printf("测试方式:新开终端执行 kill -USR1 %d\n", getpid());

    // 循环等待信号
    while (1) {
        printf("程序运行中...\n");
        sleep(2);
    }

    return 0;
}
代码解析:
  1. 高级处理函数sigusr1_handler接收 3 个参数,siginfo_t *info包含信号详细信息;
  2. 结构体初始化memset清空struct sigaction,避免野值;
  3. 屏蔽集配置:处理 SIGUSR1 时,屏蔽 SIGINT(此时按 Ctrl+C 无反应);
  4. 标志位SA_SIGINFO启用高级处理函数,SA_RESTART保证系统调用不被中断。
运行步骤:
# 编译
gcc sigaction_demo.c -o sigaction_demo
# 运行
./sigaction_demo
# 新开终端发送信号
kill -USR1 12346(替换为实际PID)
输出结果:
程序启动,PID:12346
测试方式:新开终端执行 kill -USR1 12346
程序运行中...
程序运行中...

=== 捕获到SIGUSR1信号 ===
信号编号:10
发送进程PID:12347(发送方终端的bash PID)
信号携带值:0
=========================
程序运行中...

3.3 示例 3:父子进程间信号通信

功能:父进程创建子进程,子进程向父进程发送 SIGUSR1,父进程回复 SIGUSR2,子进程收到后退出。

完整代码(parent_child_sig.c):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

// 全局变量:存储父子进程PID
pid_t child_pid = 0;
pid_t parent_pid = 0;

// 父进程处理SIGUSR1的函数
void parent_sigusr1_handler(int signum) {
    printf("[父进程 %d] 收到子进程 %d 的SIGUSR1信号\n", parent_pid, child_pid);
    // 向子进程发送SIGUSR2信号
    if (kill(child_pid, SIGUSR2) == -1) {
        perror("kill发送SIGUSR2失败");
        exit(EXIT_FAILURE);
    }
    printf("[父进程 %d] 已向子进程 %d 发送SIGUSR2信号\n", parent_pid, child_pid);
}

// 子进程处理SIGUSR2的函数
void child_sigusr2_handler(int signum) {
    printf("[子进程 %d] 收到父进程 %d 的SIGUSR2信号,准备退出\n", getpid(), parent_pid);
    exit(EXIT_SUCCESS);
}

int main() {
    parent_pid = getpid();
    printf("[父进程 %d] 启动,准备创建子进程\n", parent_pid);

    // 创建子进程
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork失败");
        exit(EXIT_FAILURE);
    }

    if (child_pid == 0) {
        // 子进程逻辑
        printf("[子进程 %d] 启动,父进程PID:%d\n", getpid(), parent_pid);

        // 注册SIGUSR2处理函数
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_handler = child_sigusr2_handler;
        act.sa_flags = SA_RESTART;
        if (sigaction(SIGUSR2, &act, NULL) == -1) {
            perror("子进程sigaction注册失败");
            exit(EXIT_FAILURE);
        }

        // 向父进程发送SIGUSR1信号
        printf("[子进程 %d] 向父进程 %d 发送SIGUSR1信号\n", getpid(), parent_pid);
        if (kill(parent_pid, SIGUSR1) == -1) {
            perror("kill发送SIGUSR1失败");
            exit(EXIT_FAILURE);
        }

        // 等待父进程回复
        while (1) {
            printf("[子进程 %d] 等待父进程回复...\n", getpid());
            sleep(1);
        }
    } else {
        // 父进程逻辑
        // 注册SIGUSR1处理函数
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_handler = parent_sigusr1_handler;
        act.sa_flags = SA_RESTART;
        if (sigaction(SIGUSR1, &act, NULL) == -1) {
            perror("父进程sigaction注册失败");
            exit(EXIT_FAILURE);
        }

        // 等待子进程退出(避免僵尸进程)
        printf("[父进程 %d] 等待子进程退出...\n", parent_pid);
        waitpid(child_pid, NULL, 0);
        printf("[父进程 %d] 子进程已退出,父进程结束\n", parent_pid);
    }

    return 0;
}
代码解析:
  1. fork () 创建子进程:子进程返回 0,父进程返回子进程 PID;
  2. 子进程逻辑:注册 SIGUSR2 处理函数,向父进程发送 SIGUSR1,等待回复;
  3. 父进程逻辑:注册 SIGUSR1 处理函数,收到信号后回复 SIGUSR2,通过waitpid()回收子进程;
  4. 全局变量:存储父子 PID,方便互相发送信号。
运行步骤:
gcc parent_child_sig.c -o parent_child_sig
./parent_child_sig
输出结果:
[父进程 12348] 启动,准备创建子进程
[父进程 12348] 等待子进程退出...
[子进程 12349] 启动,父进程PID:12348
[子进程 12349] 向父进程 12348 发送SIGUSR1信号
[父进程 12348] 收到子进程 12349 的SIGUSR1信号
[父进程 12348] 已向子进程 12349 发送SIGUSR2信号
[子进程 12349] 收到父进程 12348 的SIGUSR2信号,准备退出
[父进程 12348] 子进程已退出,父进程结束

3.4 示例 4:信号屏蔽与未决信号

功能:屏蔽 SIGINT 信号,触发后查看未决信号,解除屏蔽后处理。

完整代码(sig_mask_pending.c):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// SIGINT处理函数
void sigint_handler(int signum) {
    printf("\n[处理函数] 捕获到SIGINT信号(%d)\n", signum);
}

// 打印未决信号集
void print_pending_signals() {
    sigset_t pending_set;
    // 获取未决信号集
    if (sigpending(&pending_set) == -1) {
        perror("sigpending失败");
        exit(EXIT_FAILURE);
    }

    printf("当前未决信号:");
    // 遍历1-31号信号,检查是否未决
    for (int i = 1; i <= 31; i++) {
        if (sigismember(&pending_set, i)) {
            printf("%d ", i);
        }
    }
    printf("\n");
}

int main() {
    sigset_t mask_set;

    // 注册SIGINT处理函数
    if (signal(SIGINT, sigint_handler) == SIG_ERR) {
        perror("signal注册失败");
        exit(EXIT_FAILURE);
    }

    // 初始化屏蔽集,添加SIGINT
    sigemptyset(&mask_set);
    sigaddset(&mask_set, SIGINT);

    // 屏蔽SIGINT信号
    printf("[主进程] 屏蔽SIGINT信号...\n");
    if (sigprocmask(SIG_BLOCK, &mask_set, NULL) == -1) {
        perror("sigprocmask屏蔽失败");
        exit(EXIT_FAILURE);
    }

    printf("[主进程] PID:%d,5秒内按下Ctrl+C触发SIGINT...\n", getpid());
    sleep(5); // 5秒内按Ctrl+C,信号进入未决状态

    // 查看未决信号
    print_pending_signals();

    // 解除SIGINT屏蔽
    printf("[主进程] 解除SIGINT屏蔽,信号将被处理...\n");
    if (sigprocmask(SIG_UNBLOCK, &mask_set, NULL) == -1) {
        perror("sigprocmask解除失败");
        exit(EXIT_FAILURE);
    }

    // 再次查看未决信号(已处理)
    sleep(1);
    print_pending_signals();

    printf("[主进程] 程序正常退出\n");
    return 0;
}
代码解析:
  1. print_pending_signals():遍历 1-31 号信号,打印未决信号编号;
  2. sigprocmask(SIG_BLOCK):屏蔽 SIGINT,此时按 Ctrl+C 信号不会被处理;
  3. sigpending():获取未决信号集,验证 SIGINT 是否未决;
  4. sigprocmask(SIG_UNBLOCK):解除屏蔽,内核立即递送信号,触发处理函数。
运行步骤:
gcc sig_mask_pending.c -o sig_mask_pending
./sig_mask_pending
输出结果(5 秒内按 Ctrl+C):
[主进程] 屏蔽SIGINT信号...
[主进程] PID:12350,5秒内按下Ctrl+C触发SIGINT...
^C(按下Ctrl+C,无立即反应)
当前未决信号:2 
[主进程] 解除SIGINT屏蔽,信号将被处理...
[处理函数] 捕获到SIGINT信号(2)
当前未决信号:
[主进程] 程序正常退出

3.5 示例 5:超时控制(alarm () + pause ())

功能:实现函数执行超时控制,5 秒内未完成则触发 SIGALRM 终止程序。

完整代码(sig_alarm_demo.c):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// 超时处理函数
void alarm_handler(int signum) {
    printf("\n[超时] 函数执行超过5秒,强制终止!\n");
    exit(EXIT_FAILURE);
}

// 模拟耗时函数
void time_consuming_func(int seconds) {
    printf("[函数] 开始执行,预计耗时%d秒...\n", seconds);
    sleep(seconds); // 模拟耗时操作
    printf("[函数] 执行完成!\n");
}

int main() {
    // 注册SIGALRM处理函数
    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal注册失败");
        exit(EXIT_FAILURE);
    }

    // 设置5秒定时器
    printf("[主进程] 设置5秒超时定时器...\n");
    alarm(5);

    // 调用耗时函数(测试:3秒=正常,6秒=超时)
    time_consuming_func(6); // 改为3秒测试正常情况

    // 取消定时器(若函数提前完成)
    alarm(0);
    printf("[主进程] 程序正常结束\n");

    return 0;
}
代码解析:
  1. alarm(5):设置 5 秒定时器,到期发送 SIGALRM;
  2. time_consuming_func:模拟耗时操作(sleep 指定秒数);
  3. 超时逻辑:若函数执行 > 5 秒,SIGALRM 触发,执行alarm_handler终止程序;
  4. 正常逻辑:若函数执行 < 5 秒,调用alarm(0)取消定时器,程序正常结束。
测试结果:
  • 测试 1(超时)

    time_consuming_func(6)
    
    [主进程] 设置5秒超时定时器...
    [函数] 开始执行,预计耗时6秒...
    
    [超时] 函数执行超过5秒,强制终止!
    
  • 测试 2(正常)

    time_consuming_func(3)
    
    [主进程] 设置5秒超时定时器...
    [函数] 开始执行,预计耗时3秒...
    [函数] 执行完成!
    [主进程] 程序正常结束
    

四、总结

  1. 信号本质:内核向进程发送的异步软中断,是最轻量的 IPC 机制,仅传递事件标识,无复杂数据传输能力;
  2. 核心 API
    • 注册函数:优先使用sigaction()(健壮、可配置),避免signal()(兼容性差);
    • 发送函数:kill()(跨进程)、alarm()(定时器)是最常用的信号发送方式;
    • 控制函数:sigprocmask()(屏蔽信号)、sigpending()(查询未决信号)用于精细控制信号生命周期;
  3. 典型场景:进程控制、异常处理、超时控制、异步通知是信号最核心的使用场景,尤其适合需要 “异步事件触发” 的场景。

信号是 Linux 进程通信的基础,掌握信号的使用不仅能解决进程间异步通知问题,也是理解 Linux 内核进程调度、异常处理机制的关键。在实际开发中,需注意信号的可靠性(优先使用实时信号)、屏蔽集的配置(避免关键信号被中断),以及与其他 IPC 机制(如共享内存、锁)的配合使用。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐