Linux信号处理详解:从基本概念到高级应用

发布于:2025-03-23 ⋅ 阅读:(20) ⋅ 点赞:(0)

个人主页:chian-ocean

文章专栏-Linux

前言:

在Linux系统中,信号(Signal)是操作系统用来通知进程发生某些事件的一种机制。信号是一种软件中断机制,可以被进程用来响应特定的事件,如终止进程、暂停进程、重新加载配置等。信号机制是Unix及其衍生系统的核心功能之一

在这里插入图片描述

什么是信号

生活中的信号也可以理解为一种通过特定方式传递信息、指令或警告的方式。在日常生活中,信号无处不在,帮助我们理解周围环境并做出反应。以下是一些常见的生活中的信号:

  • 交通灯:交通信号灯(红灯、绿灯、黄灯)是最常见的生活信号之一。通过颜色的变化来指示司机和行人是“停车”、“前进”还是“等待”。

  • 语言表达:人们通过语言交流时,语气、词汇、语法等本身就是信号。例如,愤怒的语气、温柔的话语或请求,都传递了不同的情感或意图。

  • 手机通知:手机的振动或铃声是用来提示有来电、短信、邮件等通知的信号。

  • 天气变化:天气的变化可以是自然界给我们发出的信号。例如,乌云密布通常预示着即将下雨,气温变化可能暗示季节的变化。

Linux中常见的信号

  • SIGINT (2):中断信号,通常由用户通过Ctrl+C触发。默认行为是终止进程。(退出码也是异常退出)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • SIGKILL (9):强制终止进程信号,进程无法捕获或忽略该信号,收到后立即终止进程。

在这里插入图片描述

信号的概念

信号是操作系统用来与进程交互的一种方式,通过信号,操作系统能够控制进程的执行流,处理错误或执行特定任务。

  • 查看信号:
kill -l

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

信号的处理方式

进程可以根据不同需求对信号进行以下三种处理:

  1. 忽略信号:进程可以选择忽略某个信号。例如,使用 signal(SIGINT, SIG_IGN) 忽略 SIGINT 信号,进程将不会因按下 Ctrl+C 而被终止。

  2. 自定义处理信号:进程可以为信号指定一个自定义的信号处理函数。进程接收到信号时,操作系统会调用该函数来处理信号。例如,可以通过 signal(SIGTERM, my_handler) 设置当进程接收到 SIGTERM 信号时,调用 my_handler() 函数进行处理。

  3. 默认处理:如果进程没有特别指定对某个信号的处理方式,操作系统会采用默认行为。例如,默认情况下 SIGINT 信号会终止进程,SIGSEGV 会让进程崩溃。

signal函数

sighandler_t signal(int signum, sighandler_t handler);

typedef void (*sighandler_t)(int);//函数指针类型 参数是int 返回值是void

参数说明

  • signum:要捕获的信号编号。它是一个整数值,表示特定的信号。例如,SIGINT 用来捕获中断信号(Ctrl+C),SIGTERM 用来请求终止进程等。

  • handler:信号处理函数。当进程接收到指定的信号时,操作系统会调用这个函数。这个函数应该符合 sighandler_t 类型(通常是 void handler(int sig) 类型的函数)。

信号的产生

通过终端按键产⽣信号

  • Ctrl + C : 本质是2号信号。
  • Ctrl + Z : 本质是3号信号。
#include <iostream>    
#include <sys/types.h>   
#include <unistd.h>      
#include <signal.h>      
#include <cstdlib>       

// 信号处理函数
void myheander(int signo)
{
    std::cout << "signo: " << signo << std::endl;  // 输出捕获到的信号编号
    sleep(3);  // 模拟处理信号的延时操作
    exit(1);  // 退出程序,返回1表示异常终止
}

int main()
{
    while(true)  // 无限循环,模拟程序持续运行
    {
        signal(SIGINT, myheander);  // 设置 SIGINT 信号的处理函数为 myheander
        std::cout << "The process is running ...   Pid: " << getpid() << std::endl;  // 输出进程的PID
        sleep(1);  // 每秒输出一次当前的进程信息
    }
    return 0;  // 退出程序,理论上不会执行到这行代码
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分析

  • 在左侧的终端中,程序输出 "The process is running ..." 和对应的进程ID(例如,Pid: 26956)。当用户按下 Ctrl+C(产生 SIGINT 信号)时,程序捕获到信号并打印 "signo: 2"
  • 右侧的终端展示了用户通过 kill -2 27041 命令向进程ID为 27041 的进程发送 SIGINT 信号。
    • kill 命令用于向进程发送信号。-2SIGINT 信号的数字表示(等同于按 Ctrl+C),27041 是目标进程的进程ID。

同理Ctrl + Z一样子可以得到证明。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过kill在终端杀死进程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 在终端中用 kill杀死进程

通过函数产生信号

kill

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

int kill(pid_t pid , int sig);

参数

  • pid :目标进程的PID(进程ID)。可以指定一个特定进程,或者发送信号给进程组或所有进程:

    • 如果 pid 是一个正数,表示发送信号给 PID 对应的进程。
    • 如果 pid 是 0,表示向与当前进程同属一组的所有进程发送信号。
    • 如果 pid 是 -1,表示向所有进程发送信号。
  • sig:要发送的信号。可以使用预定义的信号常量,如 SIGINTSIGKILL

#include <iostream>  
#include <sys/types.h>  
#include <unistd.h>     
#include <signal.h>      
#include <cstdlib>      


int main()
{
    int cnt = 5;  // 定义一个计数器,控制循环次数
    while(cnt--)  // 循环5次
    {
        signal(SIGQUIT, myheander);  // 设置 SIGQUIT 信号的处理函数为 myheander
        std::cout << "The process is running ...   Pid: " << getpid() << std::endl;  // 输出当前进程的PID
        sleep(1);  // 每秒输出一次当前进程的信息
    }

    kill(getpid(), SIGKILL);  // 发送 SIGKILL 信号终止当前进程
    return 0;  // 程序结束
}
  • 信号处理

    • 当程序收到 SIGQUIT 信号时,调用 myheander 函数,打印信号编号并退出程序。
  • 循环

    • 程序通过一个循环执行5次,每次输出进程信息并设置信号处理。
  • 强制退出

    • 在循环结束后,程序通过 kill(getpid(), SIGKILL) 发送 SIGKILL 信号来强制结束当前进程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 通过9号信号直接杀死进程。

  • kill 命令用于向进程发送信号。-2SIGINT 信号的数字表示(等同于按 Ctrl+C),27041 是目标进程的进程ID。

raise

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • sig:指定要发送的信号,可以是任何有效的信号常量
#include <iostream>  
#include <sys/types.h>  
#include <unistd.h>     
#include <signal.h>      
#include <cstdlib>    

int main()
{
    int cnt = 5;  // 定义一个计数器,循环5次
    while(cnt--)  // 每次循环减少计数器
    {
        signal(SIGQUIT, myheander);  // 设置 SIGQUIT 信号的处理函数为 myheander
        std::cout << "The process is running ...   Pid: " << getpid() << std::endl;  // 输出当前进程的PID
        sleep(1);  // 每秒输出一次进程信息
    }

    raise(SIGKILL);  // 发送 SIGKILL 信号,强制终止当前进程
    return 0;  // 程序结束(实际上不会执行到这里,因为进程会在发送 SIGKILL 后被终止)
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 效果是和kill是一样的。
  • raise:用于在进程内部模拟或触发信号,通常用于程序自身的信号处理或测试。raise 向调用它的进程发送信号,并由该进程的信号处理函数处理该信号。
  • kill:用于进程间的通信,可以用于发送信号给任何进程或进程组。kill 常用于终止进程、暂停进程等操作。

abort

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include <iostream>  
#include <sys/types.h>  
#include <unistd.h>     
#include <signal.h>      
#include <cstdlib>    

int main()
{
    int cnt = 5;  // 定义计数器,循环执行5次
    while(cnt--)  // 计数器递减,每次循环执行
    {
        signal(SIGQUIT, myheander);  // 设置 SIGQUIT 信号的处理函数为 myheander
        std::cout << "The process is running ...   Pid: " << getpid() << std::endl;  // 输出当前进程的PID
        sleep(1);  // 每隔1秒输出一次进程状态
    }

    abort();  // 强制终止当前进程,并生成核心转储(如果启用)
    
    return 0;  // 这行代码不会执行,因为进程已经在 abort() 后终止
}

abort()

  • abort() 被调用时,程序会立即中止,并且通常会生成一个核心转储(如果启用了核心转储)。abort() 会发送 SIGABRT 信号,导致进程异常终止,不会进行正常的清理操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

验证6号信号:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>

void myheander(int signo)
{
    std::cout << "SIGABRT----"<<"signo: " << signo <<std::endl;
    exit(1);
}

int main()
{
    int cnt = 5;
    signal(SIGABRT,myheander);
    while(cnt--)
    {
        signal(SIGQUIT, myheander);
        std::cout << "The process is running ...   Pid: " << getpid() <<std::endl;
        sleep(1);
    }
    abort();
    return 0;
}

执行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

异常

0错误

#inclde<iostream>

int main()
{
    int x = 10;
    int y = 0;
    int c = x / y;

    std::cout << "Calculation completed"<<std::endl; 

    return 0;
}

触发原理

  • CPU检测:CPU (依赖:算术逻辑单元(ALU))在执行除法时,检查除数是否为零,触发异常。
  • 硬件中断:触发硬件中断或陷阱,操作系统介入。
  • 操作系统处理:操作系统处理异常,可能发送信号给进程。
  • 信号响应:进程根据是否捕获信号来决定是否处理或终止。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

野指针错误

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>


int main()
{
    int *p = NULL;
    *p = 10;

    std::cout << "completed"<<std::endl; 

    
    return 0;
}
  • 操作系统通过内存管理单元(MMU)来监控进程对内存的访问。当进程试图访问未分配、保护或非法的内存地址时,MMU 会触发硬件中断,报告错误给操作系统。

  • 操作系统检测到该非法访问后,通常会通过发出 SIGSEGV 信号终止程序,并可能生成核心转储文件(core dump),以便开发人员进行调试。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

软件条件(alarm)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

unsigned int alarm(unsigned int seconds);

参数

  • seconds:指定定时器的时间,单位为秒。函数会在 seconds 秒后触发 SIGALRM 信号,通知进程时间到达。

返回值

  • 返回值:该函数返回当前定时器剩余的时间(即上一个定时器设定的秒数),如果没有设置定时器,则返回 0
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>


void myheander(int signo)
{
    std::cout << "SIGABRT----"<<"signo: " << signo <<std::endl;
    int ret =alarm(2);
    std:: cout << ret<<std::endl;
}

int main()
{   signal(SIGALRM,myheander);
    unsigned int n = 5;
    alarm(20);
    while(n--)
    {
        std::cout << "process id running.."<<"Pid: "<<getpid()<<std::endl;
        sleep(1);
    }

    
    return 0;
}
  • 等待闹钟的响应。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到在在第四秒的时候发送14号信号(alarm),alarm的返回值是16.