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发送的暂停信号,可以被进程捕获和处理。
进程接收到信号后,有三种处理信号的方式:
- 忽略此信号
- 执行信号的默认处理函数
- 执行信号的自定义处理函数,这种方式也称为
信号捕捉
前两种均为操作系统自带的方式,我们可以通过 man 7 signal 命令来查看这些处理行为:
-
Term (Terminate):这是当一个信号没有被处理(即没有安装信号处理函数)时,信号的默认行为,收到信号的进程将被终止。例如,
SIGTERM
的默认行为是终止进程。 -
Ign (Ignore):进程可以选择忽略一个信号,这意味着当信号到达时,进程将不会执行任何默认行为或自定义处理函数。
-
Core:指的是在进程终止时生成core dump的行为。当进程因为某些信号(如
SIGSEGV
)而终止时,如果设置了对应的信号行为为core
,系统将生成该进程在终止瞬间的内存快照,以便于后续的错误分析。 -
Stop:指的是信号的默认行为或自定义动作是停止进程的执行。例如,
SIGSTOP
信号会使进程停止,且不能被进程捕获或忽略。其他可停止信号(如SIGTSTP
)可以被进程捕获和处理。 -
Cont (Continue):指当一个已停止的进程接收到继续信号(如
SIGCONT
)时,将恢复执行。进程从停止状态恢复到运行状态。
我们继续往下翻可以看到各种信号对应的默认行为。
接下来我们来介绍第三种信号处理的方式,即信号捕捉。需要使用到 signal 函数。signal 函数是C语言标准库中的一个函数,用于设置信号处理程序,当程序运行时接收到特定的信号,signal 函数允许开发者定义一个函数来响应这些信号。
其中,signum
参数指定了要处理的信号编号,handler
参数是一个指向信号处理函数的指针,该函数指针类型为 void (*)(int)
。如果 handler
为SIG_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
了。
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
信号,用于中断当前运行的进程。此外,硬件异常,如非法内存访问,也会导致内核生成相应的信号并发送给发生事件的进程。