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

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 processreturn 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.mrgr.cn/news/30612.html

相关文章:

  • CS61C 2020计算机组成原理Lab03
  • Linux 进程2
  • kubernetes存储之GlusterFS(GlusterFS for Kubernetes Storage)
  • 代码随想录算法训练营第51天 | 岛屿数量、岛屿的最大面积
  • index嵌入vue.js, 包括了vue.js的下载地址
  • iotop 命令:磁盘IO监控和诊断
  • 前缀和与差分(一维)
  • Springboot请求响应案例
  • Deutsch intensiv C1 Schreiben
  • Maven 和 gradle JavaFX 项目的休眠行为差异
  • 2024java面试-软实力篇
  • VMware Workstation Pro 17下载及安装教程
  • 828华为云征文|华为Flexus云服务器打造 mediacms 线上影院
  • JS和Node.js的事件循环
  • 计算生物学:概念、历史、现状与展望?
  • iostat 命令:系统状态监控
  • 典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
  • Springboot的三层架构
  • JDK自带的序列化
  • C++ : 继承问题 [virtual函数调用,为什么禁止在virtual使用默认参数]