本期,我们今天要将的是信号的第二个知识,即信号的产生。
目录
(一)通过终端按键产生信号
(二)调用系统函数向进程发信号
(三)由软件条件产生信号
(四)硬件异常产生信号
(五)小结
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下。
我们之前学习进程等待的时候,给大家介绍了以下这张图片,其中【core dump】没有讲,今天我将给大家解释这个词的含义。
【解释说明】
命令用于显示有关用户当前资源限制(ulimits)的信息。此命令显示各种系统资源的硬限制和软限制,以下是一个示例输出:
【解释说明】
【注意】
接下来,我们手动设置核心转储文件的大小。具体如下:
【解释说明】
-c
: 表示设置或显示核心文件的最大大小限制。10240
: 表示核心文件的最大大小限制为10240个块。如果以字节计算,这意味着核心文件的最大大小为10240个块乘以每个块的字节数。【解释说明】
core
的文件,其中包含了进程在崩溃瞬间的内存状态。接下来,我们就去验证上述结论:
int main(int argc, char *argv[])
{
while (true)
{
cout << "我是一个正常运行的进程:" << getpid() << endl;
sleep(1);
}
return 0;
}
ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制而来,所以也具 有和Shell进程相同的Resource Limit值,这样就可以产生Core Dump了。 使用core文件:
首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号
【解释说明】
kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
#include
int kill(pid_t pid, int sig);
pid_t pid
: 要发送信号的进程的进程ID。如果 pid
为正数,信号将发送到具有该进程ID的进程。如果 pid
为0,信号将发送到与调用进程属于同一进程组的所有进程。如果 pid
为-1,信号将发送到调用进程有权限发送信号的所有进程。int sig
: 要发送的信号的编号。而raise函数可以给当前进程发送指定的信号(自己给自己发信号)
#include
int raise(int sig);
abort函数使当前进程接收到信号而异常终止
#include
void abort(void);
使用abort函数非常简单,只需在需要终止进程的地方调用它即可。当调用abort函数时,以下操作将被执行:
SIGABRT
信号。SIGABRT
信号会导致进程终止,并生成一个核心转储文件。_Exit
函数或返回,则进程会异常终止,并打印一条错误消息到标准错误流(stderr)。代码展示:
void cleanup() {
std::cout << "Performing cleanup before aborting..." << std::endl;
// 执行一些清理工作
}
void handler(int signo) {
std::cout << "Received signal " << signo << std::endl;
// 自定义信号处理逻辑
exit(signo);
}
int main()
{
// 注册终止处理程序
if (atexit(cleanup) != 0) {
std::cerr << "Failed to register cleanup function" << std::endl;
exit(EXIT_FAILURE);
}
// 注册信号处理函数
if (signal(SIGABRT, handler) == SIG_ERR) {
std::cerr << "Failed to register signal handler" << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "Starting program..." << std::endl;
std::cout << "Triggering abort..." << std::endl;
// 调用abort函数,触发进程终止
abort();
std::cout << "This line will not be reached" << std::endl;
return 0;
}
输出展示:
【解释说明】
SIGABRT
信号,并执行了注册的终止处理程序和信号处理函数;SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。本节主要介绍alarm函数 和SIGALRM信号。
#include
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
//io的效率低下
int main()
{
alarm(1);
int count = 0;
while (true)
{
// 打印,显示器打印网络
std::cout << "count : " << count++ << std::endl; //1s之内计算机将一个正数累计到多少
}
return 0;
}
int count = 0;
void myhandler(int signo)
{
std::cout << "get a signal: " << signo << " count: " << count << std::endl;
exit(0);
}
int main(int argc, char *argv[])
{
signal(SIGALRM,myhandler);
alarm(1);
while (true) count++;
return 0;
}
接下来,我们代码简单的演示一下:
void myhandler(int signo)
{
std::cout << "get a signal: " << signo << " count: " << count << std::endl;
int n = alarm(10);
std::cout << "return: " << n << std::endl;
}
int main(int argc, char *argv[])
{
std::cout << "pid: " << getpid() << std::endl;
signal(SIGALRM,myhandler);
alarm(10);
while(true)
{
sleep(1);
}
return 0;
}
输出展示:
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
以上便是本文的主要内容,接下来简单小结本文都讲了些什么!!!
在Linux中,信号可以通过多种方式产生,包括:
硬件异常: 这些是由硬件引起的异常事件,例如:
SIGFPE
信号。SIGSEGV
信号。SIGBUS
信号。软件中断: 这些是由软件引发的事件,通常是为了通知进程已经达到了某个预定条件,例如:
SIGALRM
信号。其他进程发送信号: 一个进程可以通过系统调用向另一个进程发送信号,例如:
kill
命令或者在程序中使用 kill
函数可以向指定的进程发送信号。SIGINT
信号。进程自身发送信号: 进程可以通过调用 raise
函数或者 kill
函数向自身发送信号。
kill
函数向自身发送信号,使用进程ID为 getpid()
。软件条件满足: 在程序中,当特定的条件满足时,可以使用信号来通知其他部分程序执行某些动作,这通常需要由程序本身显示地触发。
以上便是本文的全部内容了,感谢大家的观看和支持!!!