当前位置: 首页 > article >正文

Linux 信号的产生

1. 概念

在Linux系统中,信号是一种进程间通信的机制,它允许操作系统或其他进程向特定进程发送异步通知。我们可以通过命令 kill -l来查看信号的种类:

Linux系统中的信号可以分为两大类:传统信号和实时信号。从上图可以看出它们分为两个区间:[1, 31] 和[34, 64],无32和33号信号,[1, 31] 区间为传统信号共有31种,当进程收到传统信号后,可以自己选择合适的时候处理;[34, 64]为实时信号共有31种,当进程收到实时信号后,立马处理。

以下是一些常见信号的简要说明:

  • SIGINT(2):用户通过Ctrl+C发送的中断信号,通常用于请求进程终止。
  • SIGQUIT(3):用户通过Ctrl+\发送的退出信号,通常用于请求进程终止并生成core dump。
  • SIGABRT(6):通过调用abort()函数产生的信号,用于异常终止程序。
  • SIGFPE(8):发生致命的算术运算错误时产生。
  • SIGKILL(9):用于立即强制终止进程的信号,不能被进程捕获、阻塞或忽略。
  • SIGSEGV(11):试图写入无效内存地址时产生。
  • SIGALRM(14):由alarm()函数设置的计时器到期时产生。
  • SIGSTOP(19):停止进程执行的信号,不能被进程捕获、阻塞或忽略。
  • SIGTSTP(20):由用户通过Ctrl+Z发送的暂停信号,可以被进程捕获和处理。

进程接收到信号后,有三种处理信号的方式:

  1. 忽略此信号
  2. 执行信号的默认处理函数
  3. 执行信号的自定义处理函数,这种方式也称为信号捕捉

前两种均为操作系统自带的方式,我们可以通过 man 7 signal 命令来查看这些处理行为:

  1. Term (Terminate):这是当一个信号没有被处理(即没有安装信号处理函数)时,信号的默认行为,收到信号的进程将被终止。例如,SIGTERM的默认行为是终止进程。

  2. Ign (Ignore):进程可以选择忽略一个信号,这意味着当信号到达时,进程将不会执行任何默认行为或自定义处理函数。

  3. Core:指的是在进程终止时生成core dump的行为。当进程因为某些信号(如SIGSEGV)而终止时,如果设置了对应的信号行为为core,系统将生成该进程在终止瞬间的内存快照,以便于后续的错误分析。

  4. Stop:指的是信号的默认行为或自定义动作是停止进程的执行。例如,SIGSTOP信号会使进程停止,且不能被进程捕获或忽略。其他可停止信号(如SIGTSTP)可以被进程捕获和处理。

  5. Cont (Continue):指当一个已停止的进程接收到继续信号(如SIGCONT)时,将恢复执行。进程从停止状态恢复到运行状态。

我们继续往下翻可以看到各种信号对应的默认行为。

接下来我们来介绍第三种信号处理的方式,即信号捕捉。需要使用到 signal 函数。signal 函数是C语言标准库中的一个函数,用于设置信号处理程序,当程序运行时接收到特定的信号,signal 函数允许开发者定义一个函数来响应这些信号。

其中,signum参数指定了要处理的信号编号,handler参数是一个指向信号处理函数的指针,该函数指针类型为 void (*)(int)。如果 handlerSIG_IGN,则信号将被忽略;如果为 SIG_DFL,则信号将采用默认的操作系统行为。

#include<iostream>
#include<signal.h>
using namespace std;
void handler(int signum)
{
    cout<<"received signal: "<<signum<< endl;
}
int main()
{
    signal(2,handler);
    while(1)
    {
        cout<<"waiting for signal..."<<endl;
        sleep(1);
    }
    return 0;
}

以上代码中,我们通过 signal(2, handler) 把2号信号的处理方式变成了执行函数handler。而从上文介绍我们了解到从键盘上按下 ctrl + C 可以发送2号SIGINT信号。

可以看到按下ctrl + C后,本来是发送2号信号时会直接终止进程,但修改处理行为后,现在输出receive signal: 2了。

3. 注意
1. ctrl +  C  产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行 , 这样 Shell 不必等待进程结束就可以接受新的命令, 启动新的进程。
2. Shell 可以同时运行一个前台进程和任意多个后台进程 , 只有前台进程才能接到像 ctrl +  C 这种控制键产生的信号。
3. 前台进程在运行过程中用户随时可能按下 Ctrl - C 而产生一个信号 , 也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止 , 所以信号相对于进程的控制流程来说是异步
(Asynchronous) 的。

2. 产生

信号可以由多种方式产生,包括硬件产生和软件产生。

软件产生

软件产生的信号指的是由进程自身或其他进程产生的信号,其可以通过系统调用实现,常用的系统调用包括 kill()raise()abort()alarm()

kill

kill函数是在Unix和类Unix系统中,包括Linux,用于向进程发送信号的系统调用。这个函数允许一个进程请求操作系统向另一个进程发送指定的信号,从而可以控制目标进程的行为,如终止进程、请求进程停止或继续执行等。

参数:

  • pid:目标进程的进程ID。如果pid为正数,则信号发送给指定的进程;如果pid为0,则信号发送给调用进程所属的进程组。
  • sig:准备发送的信号码。如果sig为0,则不发送信号,但会执行错误检查,通常用于检测目标进程是否存在。

返回值:

  • 返回0:发送信号成功
  • 返回-1:发送信号失败

void handler(int signum)
{
    cout<<"received signal: "<<signum<< endl;
    exit(1);
}
int main()
{
    pid_t pid = fork();
    if(pid == 0)//child
    {
        signal(2,handler);
        while(1)
        {
            cout<<"child process waiting for signal..."<<endl;
            sleep(1);
        }
    }
    sleep(5);
    kill(pid,2);//send signal to child process
    return 0;
}

代码示例如上,子进程等待信号,5秒后,父进程向子进程发出2号信号,子进程收到该信号并执行handler 函数,最终退出。

同时,我们也可以在 Shell 中使用 kill 命令,如下,其底层为调用 kill接口。

kill -sig pid

raise

参数:

  • sig:要发送的信号的编号

返回值:

  • 返回0:发送信号成功
  • 返回-1:发送信号失败

在Linux系统编程中,raise函数用于给当前进程发送一个信号,这个函数等效于kill(getpid(), sig)raise函数通常用于引发特定的信号处理程序,或者在需要立即响应信号的场景中使用。

void handler(int signum)
{
    cout << "received signal: " << signum << endl;
    exit(1);
}
int main()
{
    signal(2, handler);
    int cnt = 5;
    while (cnt--)
    {
        cout << "waiting for signal,cnt = " << cnt << endl;
        sleep(1);
    }
    raise(2);
    return 0;
}

代码示例如上,while 循环执行5秒后,进程给自己发出2号信号,进程收到该信号并执行handler 函数,最终退出。

abort

在Linux系统中,abort函数是一个用于立即终止程序执行的系统调用。它不仅终止程序,而且会生成一个core dump(如果系统配置允许),这有助于调试和理解程序崩溃时的状态。

abort函数被调用时,它会向调用进程发送SIGABRT信号,SIGABRT信号的值通常为6。如果这个信号没有被处理,则默认行为是终止进程并生成core dump,以便后续的错误分析。

core dump的概念

接下来我们来了解 core dump 是什么,core dump 是指当Linux或Unix-like系统中的程序因为异常或错误而崩溃时,操作系统会生成的一个包含了程序崩溃时内存映像的文件。这个文件通常包含了程序的寄存器状态、堆栈信息、内存管理信息等,可以用于调试目的,帮助开发者分析程序崩溃的原因。

代码示例:

int Div(int a, int b)
{
    if (b == 0)
        abort();
    return a / b;
}
int main()
{
    int a = 5, b = 0;
    cout << Div(a, b);
    return 0;
}

上述代码发生了除0错误,进而调用了 abort 函数,可我们并没有看到在当前目录下生成的core dump文件,这是因为生成core dump文件是要有条件的。

core dump文件的生成条件

core dump文件的生成不是自动的,它依赖于几个条件:

  • 当前用户的 ulimit -c+n(单位为byte) 设置必须允许生成core文件,或者设置为 ulimit -c unlimited 以移除大小限制。

  • 程序必须有权限在其当前工作目录中创建文件。

我们可以使用 ulimit -a 命令显示当前的所有资源限制。

我们可以看到 core file size 为 0,即当前我们不能创建core file,我们需要使用ulimit -c 命令设置core file size的大小进而能创建core file。

core dump文件的作用

core dump文件对于软件开发和维护至关重要,因为它们提供了程序崩溃时的详细快照。通过使用调试工具(如GDB)分析​​​​​​​core dump文件,开发者可以追溯程序崩溃时的函数调用堆栈,检查变量的状态,从而定位到导致崩溃的代码行和潜在的错误。

alarm

alarm函数用于设置一个定时器,该定时器会在指定的秒数后向进程发送SIGALRM信号(14)。

参数:

  • seconds:在seconds秒后发送信号

返回值:

  • 如果之前有还没响的闹钟:取消上一次的闹钟,并返回上一次闹钟的剩余秒数
  • 如果之前没有闹钟了:返回0

硬件产生

硬件产生的信号通常是由于用户输入或系统异常触发的。例如,当用户在终端上按下组合键如Ctrl+C时,会产生SIGINT信号,用于中断当前运行的进程。此外,硬件异常,如非法内存访问,也会导致内核生成相应的信号并发送给发生事件的进程。 


http://www.kler.cn/a/311815.html

相关文章:

  • Java基于SpringBoot+Vue的宠物共享平台的设计与实现(附源码,文档)
  • 扫雷游戏代码分享(c基础)
  • 即插即用篇 | YOLOv8 引入 代理注意力 AgentAttention
  • 【go从零单排】Mutexes互斥锁
  • 项目模块详细说明
  • Linux——gcc编译过程详解与ACM时间和进度条的制作
  • Windows本地pycharm使用远程服务器conda虚拟环境
  • 【Android】Handler用法及原理解析
  • Rust编程的作用域与所有权
  • 面向开发者的LLM入门教程(学习笔记02):提示原则
  • 探索AI大模型:从入门到精通的学习路径
  • spring cxf 常用注解
  • 大数据时代的等保测评:数据安全与隐私保护
  • [数据集][目标检测]智慧养殖场肉鸡目标检测数据集VOC+YOLO格式3548张1类别
  • leetcode75. 颜色分类
  • 【HTML】入门教程
  • 【SpinalHDL】Scala编程之伴生对象
  • Vue 项目中引入 Axios 详解
  • 【论文阅读笔记】YOLOv10: Real-Time End-to-End Object Detection
  • 【高级编程】网络编程 基于 TCPUDP 协议的 Socket 编程
  • Remix 学习 - @remix-run/react 中的主要组件
  • 网络-内核是如何与用户进程交互
  • MySQL从入门到精通
  • MyBatis 数据处理:主键获取、批量删除与动态表名
  • Linux 磁盘清理重新格式化挂载脚本及问题解决
  • flink doris批量sink