欢迎光临
我们一直在努力

Linux 进程间通信(IPC)之信号

文章目录

    • 一、详解 IPC 通信特点,进程与线程的核心对比
      • 1.1 进程间通信(IPC)的核心概念与特点
      • 1.2 进程与线程的核心对比
    • 二、Linux 信号 API 与典型使用场景
      • 2.1 信号的基础认知
        • 2.1.1 信号的本质与默认动作
        • 2.1.2 信号的分类
      • 2.2 核心信号 API 详解
        • 2.2.1 信号处理函数注册
          • (1)signal ():简易注册函数
          • (2)sigaction ():健壮注册函数(推荐)
        • 2.2.2 信号发送函数
          • (1)kill ():向指定进程发送信号
          • (2)raise ():向当前进程发送信号
          • (3)abort ():强制终止当前进程
          • (4)alarm ():设置定时器(触发 SIGALRM)
        • 2.2.3 信号集操作函数
        • 2.2.4 信号屏蔽与未决信号函数
          • (1)sigprocmask ():设置进程信号屏蔽字
          • (2)sigpending ():获取未决信号集
        • 2.2.5 信号等待函数
          • (1)pause ():挂起进程等待信号
          • (2)sigsuspend ():原子操作等待信号
      • 2.3 信号的典型使用场景
    • 三、信号实战示例与深度解析
      • 3.1 示例 1:基础信号捕获(signal ())
        • 代码解析:
        • 运行步骤:
        • 输出结果:
      • 3.2 示例 2:健壮信号处理(sigaction ())
        • 代码解析:
        • 运行步骤:
        • 输出结果:
      • 3.3 示例 3:父子进程间信号通信
        • 代码解析:
        • 运行步骤:
        • 输出结果:
      • 3.4 示例 4:信号屏蔽与未决信号
        • 代码解析:
        • 运行步骤:
        • 输出结果(5 秒内按 Ctrl+C):
      • 3.5 示例 5:超时控制(alarm () + pause ())
        • 代码解析:
        • 测试结果:
    • 四、总结

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

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

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

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

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

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

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

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

  • 数据传输能力

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

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

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

  • 可靠性

    :信号分为

    不可靠信号

    (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 信号的典型使用场景

  • 进程控制:终止 / 暂停 / 恢复进程(如kill -9 PID发送 SIGKILL 终止进程、kill -STOP PID暂停进程);
  • 异常处理:捕获程序运行异常(如 SIGSEGV 段错误、SIGFPE 浮点错误),执行资源清理、日志记录;
  • 超时控制:通过alarm()+SIGALRM实现函数 / 网络请求超时终止;
  • 异步通知:进程间异步传递事件(如子进程退出触发 SIGCHLD,父进程捕获后回收僵尸进程);
  • 终端交互:响应终端操作(如Ctrl+C触发 SIGINT,终止前台进程);
  • 实时事件通知:实时信号(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;
    }

    代码解析:
  • 头文件:signal.h(信号 API)、unistd.h(sleep()/getpid())、stdlib.h(exit());
  • 处理函数:sigint_handler接收信号编号,执行清理逻辑后退出;
  • 信号注册:signal(SIGINT, sigint_handler)替换 SIGINT 的默认动作;
  • 主循环: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;
    }

    代码解析:
  • 高级处理函数:sigusr1_handler接收 3 个参数,siginfo_t *info包含信号详细信息;
  • 结构体初始化:memset清空struct sigaction,避免野值;
  • 屏蔽集配置:处理 SIGUSR1 时,屏蔽 SIGINT(此时按 Ctrl+C 无反应);
  • 标志位: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;
    }

    代码解析:
  • fork () 创建子进程:子进程返回 0,父进程返回子进程 PID;
  • 子进程逻辑:注册 SIGUSR2 处理函数,向父进程发送 SIGUSR1,等待回复;
  • 父进程逻辑:注册 SIGUSR1 处理函数,收到信号后回复 SIGUSR2,通过waitpid()回收子进程;
  • 全局变量:存储父子 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;
    }

    代码解析:
  • print_pending_signals():遍历 1-31 号信号,打印未决信号编号;
  • sigprocmask(SIG_BLOCK):屏蔽 SIGINT,此时按 Ctrl+C 信号不会被处理;
  • sigpending():获取未决信号集,验证 SIGINT 是否未决;
  • 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;
    }

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

      time_consuming_func(6)
      [主进程] 设置5秒超时定时器…
      [函数] 开始执行,预计耗时6秒…

      [超时] 函数执行超过5秒,强制终止!

    • 测试 2(正常)

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

    四、总结

  • 信号本质:内核向进程发送的异步软中断,是最轻量的 IPC 机制,仅传递事件标识,无复杂数据传输能力;
  • 核心 API
    • 注册函数:优先使用sigaction()(健壮、可配置),避免signal()(兼容性差);
    • 发送函数:kill()(跨进程)、alarm()(定时器)是最常用的信号发送方式;
    • 控制函数:sigprocmask()(屏蔽信号)、sigpending()(查询未决信号)用于精细控制信号生命周期;
  • 典型场景:进程控制、异常处理、超时控制、异步通知是信号最核心的使用场景,尤其适合需要 “异步事件触发” 的场景。
  • 信号是 Linux 进程通信的基础,掌握信号的使用不仅能解决进程间异步通知问题,也是理解 Linux 内核进程调度、异常处理机制的关键。在实际开发中,需注意信号的可靠性(优先使用实时信号)、屏蔽集的配置(避免关键信号被中断),以及与其他 IPC 机制(如共享内存、锁)的配合使用。

    赞(0)
    未经允许不得转载:171主机测评 » Linux 进程间通信(IPC)之信号
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址