Linux 信号处理概览

在Linux系统中,信号(Signal)是进程间通信的关键机制,通常用于通知进程发生特定事件。例如,用户按下Ctrl+C会发送SIGINT信号来终止进程,或程序访问非法内存时触发SIGSEGV信号。信号处理是进程响应这些事件的核心机制。


一、信号分类 ⚡

Linux支持多种信号(可通过kill -l查看)。常见信号及其默认行为如下:

  • 终止进程

    • SIGTERM(优雅终止)

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

    • SIGINT(Ctrl+C)

  • 核心转储

    • SIGSEGV(段错误)

    • SIGFPE(算术错误)

  • 挂起/继续

    • SIGHUP(终端断开)

    • SIGSTOP(暂停进程,不可捕获)

  • 其他

    • SIGALRM(定时器)

    • SIGCHLD(子进程状态变化)


二、信号处理函数 📜

进程可通过注册信号处理函数自定义信号行为,常用的API如下:

  1. signal() 函数 🛠️

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

  • 功能 :为信号signum绑定处理函数handler

  • 示例

    复制代码
    void handler(int sig) {
      printf("Received SIGINT\n");
      exit(0);
    }
    signal(SIGINT, handler); // 捕获Ctrl+C
  • 缺点:行为因系统而异,缺乏灵活性(如无法控制信号屏蔽)。

  1. sigaction() 函数(推荐) 👍

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

  • 参数

    • act:指定新行为,通过struct sigaction配置:

      • sa_handler:处理函数(或SIG_IGN/SIG_DFL)。

      • sa_mask:在处理信号时阻塞其他信号。

      • sa_flags:控制行为(如SA_RESTART自动重启被中断的系统调用)。

    • oldact:保存旧的处理配置。

  • 示例

    复制代码
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask); // 不阻塞其他信号
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);

三、编写信号处理函数的注意事项 ⚠️

  1. 可重入性 :避免使用非异步安全函数(如printfmalloc),可能引发竞态条件。

    • 使用volatile变量原子操作传递状态到主程序。
  2. 执行时间短:避免长时间操作,尽快返回。

  3. 避免修改全局状态:如需修改,需通过信号屏蔽等手段保证原子性。


四、信号阻塞与解除 🛑

通过信号掩码控制进程对信号的响应:

  • sigprocmask():设置/修改进程的信号掩码。

  • sigpending():检查未决(被阻塞)的信号。

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞SIGINT


五、实时信号(RT Signals) ⏱️

  • 范围SIGRTMINSIGRTMAX(如32-64)。

  • 特点 :支持排队(避免丢失)、可携带额外数据(通过sigqueue()发送)。

  • 使用

    复制代码
    union sigval value;
    value.sival_int = 123;
    sigqueue(pid, SIGRTMIN, value); // 发送附带数据的信号

六、发送信号的常用方法 📬

  • 命令kill -信号 PID(如kill -9 1234)。

  • 系统调用

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

    • raise():向自身发送信号。

    • alarm():设置定时器(触发SIGALRM)。


七、多线程中的信号处理 🔄

  • 信号处理函数是进程级:所有线程共享同一处理函数。

  • 信号掩码是线程级:各线程可独立设置阻塞的信号。

  • 建议

    • 主线程统一处理信号(通过sigwait()同步等待)。

    • 避免在多线程中使用异步信号处理。


八、典型应用场景 🏗️

  1. 优雅退出 :捕获SIGTERM释放资源。

  2. 定时任务 :通过SIGALRM触发定时操作。

  3. 错误调试 :处理SIGSEGV记录错误信息。


示例:使用sigaction处理SIGINT 📝

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

volatile sig_atomic_t flag = 0;

void handler(int sig) {
    flag = 1; // 安全设置标志位
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);

    while (!flag) {
        sleep(1);
        printf("Running...\n");
    }
    printf("Exiting gracefully\n");
    return 0;
}

总结 🏁

Linux信号处理是控制进程行为的重要机制。通过合理使用signal()sigaction()注册处理函数,结合信号阻塞、实时信号等特性,可以实现灵活的事件响应。需要特别注意处理函数的可重入性,避免竞态条件,确保程序健壮性。

相关推荐
巨可爱熊16 分钟前
高并发内存池(定长内存池基础)
linux·运维·服务器·c++·算法
yangang1852 小时前
linuxbash原理
linux·运维·服务器
小小毛桃2 小时前
在Ubuntu系统中运行Windows程序
linux·windows·ubuntu
码农新猿类3 小时前
服务器本地搭建
linux·网络·c++
小度爱学习3 小时前
linux中的执行命令格式及命令帮助
linux·运维·chrome
良许Linux3 小时前
嵌入式算吃青春饭么?
linux
良许Linux4 小时前
马上要毕业去工作了,做嵌入式软件开发工程师,但是完全不会编程怎么办?
linux
良许Linux4 小时前
学stm32,有什么学习方法?
linux
良许Linux4 小时前
为啥有好多人说 Arduino 是玩具?
linux
独行soc4 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf