【Linux】第三十六站:信号

文章目录

  • 一、信号的概念
    • 1.信号概念
    • 2.前台与后台进程
    • 3.信号的处理
    • 4.硬件层面
    • 5.信号与我们的代码是异步的
  • 二、信号的产生
    • 1.产生的方式
    • 2.键盘组合键
    • 3.kill命令
    • 4.系统调用
      • 4.1 kill系统调用
      • 4.2 raise
      • 4.3 abort
    • 5.异常软件条件
      • 5.1 异常产生信号
      • 5.2 alarm(软件条件产生信号)
    • 6.core dump

一、信号的概念

1.信号概念

我们常见的信号有信号弹、下课上课铃声、求偶、红绿灯、快递发短信取件码等等…

a.那么我们是怎么认识这些信号的??

当然是有人教我们,最后我们记住了

这个认识,首先我们要识别信号,其次还要知道信号的处理方法。最后记住他们

b.即便是我们现在没有信号产生,我也知道信号产生了之后,我该干什么

c.信号产生了,我们可能并不立即处理这个信号,在合适的时候,因为我们可能正在做更重要的事情。 — 所以,信号产生后一直到信号处理时,中间一定有一个时间窗口。在这个时间窗口内,我们必须记住信号到来!

上面这些我们指代的就是进程!

所以

  1. 进程必须识别 + 能够处理信号 — 即便信号没有产生,也要具有处理信号的能力 — 信号的处理能力,属于进程内置功能的一部分
  2. 进程即便是没有收到信号,也能知道哪些信号该怎么处理
  3. 当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,在合适的时候会进行处理
  4. 一个进程必须当信号产生,到信号开始被处理,就一定会有时间窗口,所以进程具有临时保存哪些信号已经发生了的能力

如下图所示,我们前面所作的相当于是一个信号的预备部分

image-20240123232831963


2.前台与后台进程

我们还记得,我们使用CTRL + C可以杀掉前台进程。(像如下所示的,就是前台进程,该进程运行时,shell不会接收其他命令了)

image-20240124221138981

但是如果我们这样做,它就是一个后台进程了,即在程序后面加上一个&即可,而且我们还发现,我们直接CTRL + C已经杀不掉它了。

image-20240124221652570

在我们的Linux中,一次登录中,一个终端,一般会配上一个bash。每一个登录只允许一个进程是前台进程,可以允许多个进程是后台进程。

如果我们要杀掉后台进程,我们只能使用kill -9命令了

image-20240124222011674

一般来说,前台进程和后台进程的区别就是谁能获取键盘输入

键盘输入首先是被前台进程收到的

那么既然一开始bash是前台进程,那么为什么使用CTRL+C时候,bash不退出呢?

这当然是因为bash在里面对这个信号做了特殊处理

CTRL +C 本质是被进程解释成为收到了信号,2号信号

我们知道我们的系统一共有62个信号(没有0号,32号,33号)

image-20240124235545846

我们将前31个信号称之为普通信号。后面的34~64我们称之为实时信号

一旦信号产生,不立即处理就是普通信号,立即处理是实时信号

这些信号本质就是一些数字,在linux中它们是以宏的方式定义的,就是这些数字。

3.信号的处理


信号的处理方式

  1. 默认动作
  2. 忽略
  3. 自定义动作

我们现在可以验证一下,进程收到2号信号的默认动作,就是终止自己!

我们先来看一下这个函数

image-20240125000914422

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这个函数的作用是设置对于signum信号的处理方法,处理方法为handler

这个signal函数是一个系统调用

它可以修改进程对于特定信号的处理动作

我们用如下代码来验证

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
}
int main()
{
    signal(SIGINT, myhandler);
    while(true)
    {
        cout << "I am a crazy process" << endl;
        sleep(1);
    }
    return 0;
}

运行结果为

image-20240125001657759

可见我们已经证明了2号信号的默认动作

我们也可以设置让2号信号可以退出

image-20240125002151798

运行结果为

image-20240125002215607

对于这个signal函数,只需要设置一次即可,往后都有效

只有收到了对应的信号,才会调用这个方法

注意:不是所有的信号都可以自定义的。有些信号不能自定义

4.硬件层面

键盘数据是如何输入给内核的,CTRL + C又是如何变成信号的?

键盘被摁下,肯定是OS先知道的

那么OS怎么知道键盘上有数据了???

如下图所示,是冯诺依曼体系结构

image-20240125003524986

我们知道,Linux下一切皆文件。键盘也是,它也有自己对应的struct file。以及缓冲区

image-20240125003730349

往键盘输入数据本质就是把输入的数据拷贝到缓冲区上。所以操作系统就知道了

所以我们就可以用read,write通过文件的方式把数据读到进程当中

image-20240125003934697

那么操作系统要把键盘上的数据拷贝到缓冲区中。在这个过程中,操作系统怎么知道键盘上有数据了?

其实CPU上是有很多针脚的。我们的CPU是直接插到主板上的。而键盘是可以间接的和CPU直接物理上连接到的。虽然CPU不从键盘读数据。但是键盘可以给CPU发送一个硬件中断。

也就是说,一旦外设有数据,就可以给CPU发送一个中断,从而让操作系统去完成文件拷贝。键盘、网卡等都可以给CPU发送中断。

可是外设一多,CPU如何知道是谁给发送的中断呢?

这就会有一个中断号的概念。他们会通过这些针脚,直接将中断号发送给CPU

一旦硬件CPU知道键盘上有数据了。

CPU的寄存器凭什么能保存数据呢??

其实这个本质就是充放电的过程。如果是高电平代表充电了,就有1了。

image-20240125005906919

在软件层面上,操作系统一启动,就会形成一张,中断向量表。里面放的是方法的地址。这些方法是直接访问外设的方法—主要是磁盘,显示器,键盘

image-20240125010244301

然后最后这个读取键盘的方法,才是将键盘的数据放到缓冲区的方法

image-20240125010528251

所以其实整个流程就是,键盘一旦有数据,会通过中断将中断号给CPU,CPU会利用这个中断号,让操作系统直接去通过中断向量表,找到对应的读取键盘的方法,然后通过这个方法就会让数据从键盘拷贝到这个文件缓冲区上。

所以键盘这个外设是通过中断来工作的。这个就是硬件中断

而我们前面所说的信号,也是通过一堆数字来进行控制。这两者其实比较相似,但是没有关系。一个是软硬件结合的,一个是纯软件行为。

我们所用的信号,就是用软件的方式,对进程模拟的硬件中断

当我们键盘读取的是CTRL + C这样的组合键的时候,操作系统其实还会对键盘上的数据进行判断。判断是数据还是控制,如果是控制,比如CTRL+ C会把这个转化为2号信号发送给进程。而不是放到缓冲区中。所以进程就收到了2号信号

像我们之前所谓的输入数据后往显示器上回显的过程其实是这样的,先将数据放到键盘的缓冲区,然后将键盘的缓冲区的数据放到显示器的缓冲区,最后就能输出了

image-20240125012101505

如果这里不给显示器的缓冲区,就是不回显

当然,在这个过程中,也会有其他的进程给显示器的缓冲区上放数据

image-20240125012217825

所以即便在显示器上的是乱的,但是我们还是能成功的执行指令

5.信号与我们的代码是异步的

信号的产生的和我们自己的代码的运行是异步的

同步就是发生一件事后等这件事发生完了才继续做我们的事情

异步就是这件事发生后我们不管这个事情,继续做我们的事情

信号是进程之间事件异步通知的一种方式,属于软中断

二、信号的产生

1.产生的方式

  1. 键盘组合键
  2. kill命令
  3. 系统调用
  4. 异常软件条件

以上是信号产生的方式!但是无论信号如何产生,最终一定是谁发送给进程的?

当然是OS

那么是为什么呢?

操作系统是进程的管理者!

2.键盘组合键

比如CTRL+C是2号信号

我们可以试一下捕捉三号信号

使用CTRL + \即可捕捉3号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    //exit(1);
}
int main()
{
    //signal(SIGINT, myhandler);
    signal(3, myhandler);
    while(true)
    {
        cout << "I am a crazy process" << endl;
        sleep(1);
    }
    return 0;
}

image-20240125013753341

CTRL + Z是19号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    //exit(1);
}
int main()
{
    //signal(SIGINT, myhandler);
    //signal(3, myhandler);
    signal(19, myhandler);
    while(true)
    {
        cout << "I am a crazy process" << endl;
        sleep(1);
    }
    return 0;
}

image-20240125014500820

如下所示,我们似乎会发现,我们上面似乎并没有将19号信号用自定义的方法进行处理

其实这是因为不是所有的信号,都是可以被signal捕捉的,比如19,9号信号

我们可以用下面的代码进行测试。这里就不做演示了

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    //exit(1);
}
int main()
{
    //signal(SIGINT, myhandler);
    //signal(3, myhandler);
    //signal(19, myhandler);
    
    for(int i = 1; i <= 31; i++)
    {
        signal(i, myhandler);
    }
    while(true)
    {
        cout << "I am a crazy process" << endl;
        sleep(1);
    }
    return 0;
}

19号是用来暂停进程的,9号信号是用来杀掉进程的。

这两个都是跟执行相关的。这是为了预防进程出现意外所设计的。不能被捕捉的

3.kill命令

kill -信号 进程pid

4.系统调用

4.1 kill系统调用

image-20240125151455582

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

它的两个参数分别是pid和信号的编号。与命令行中的kill是很相似的

如果成功返回0,失败返回-1

我们可以简单的利用这个系统调用接口实现一个kill命令

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
using namespace std;

void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << "signum pid\n\n";
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int signum = stoi(argv[1]);
    pid_t pid = stoi(argv[2]);

    int n = kill(pid, signum);
    if(n == -1)
    {
        perror("kill");
        exit(2);
    }
    return 0;
}

测试结果如下所示

image-20240125153352529

4.2 raise

image-20240125153520451

#include <signal.h>
int raise(int sig);

它的作用,发送一个信号给调用本方法者

我们可以用如下代码进行测试

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
using namespace std;
void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    exit(1);
}
int main()
{
    signal(2, myhandler);
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt == 0) raise(2);
    }
    return 0;
}

运行结果为

image-20240125154306514

这个raise相当于

kill(getpid(), 2);

4.3 abort

image-20240125154538468

它的作用是引起一个正常的进程直接终止

它相当于给自己发送一个6号信号

我们先用下面代码进行测试

int main()
{
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt == 0) abort();
    }
    return 0;
}

image-20240125155156642

如果我们继续将代码改为下面的,让6号信号可以被捕捉

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    //exit(1);
}
int main()
{
    signal(6, myhandler);
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt == 0) abort();
    }
    return 0;
}

那么结果为

image-20240125155415576

我们会注意到,虽然我们捕捉了以后,并没有将他给终止,但是abort依然将他给终止了。

我们可以再来观察一下

在下面的代码中,我们不让他自己abort了,我们现在在命令行上发送6号信号

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    //exit(1);
}
int main()
{
    signal(6, myhandler);
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        //if(cnt == 0) abort();
    }
    return 0;
}

image-20240125155613395

我们发现进程并没有被终止

所以其实是abort里面封装了一层,必须让进程终止

5.异常软件条件

5.1 异常产生信号

我们先使用如下代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

int main()
{
    cout << "div before" << endl;
    sleep(1);
    int a = 10;
    a /= 0; 
    cout << "div after" << endl;
    sleep(1);
    return 0;
}

运行结果为

image-20240125162723106

像这种情况就是收到了信号了

收到的是8号信号

我们可以用七号手册

man 7 signal

往下翻就可以找到这个信号详情了,可以看到确实是八号信号

image-20240125163007468

如果我们用下面的代码进行测试

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "get a signal, number: " << signo << endl;
}

int main()
{
    signal(SIGFPE, handler);
    cout << "div before" << endl;
    sleep(1);
    int a = 10;
    a /= 0; 
    cout << "div after" << endl;
    sleep(1);
    return 0;
}

那么运行结果为。打印很多的收到八号信号

image-20240125164356868

而且这个过程中进程是不会退出的

image-20240125164730496

这里的都还是比较好解释的:

不过我们可能比较好奇的是为什么在这里他会一直发送这个八号信号呢?

即信号为什么会一直被触发?

我们先看下面的代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
    //signal(SIGFPE, handler);
    cout << "point error before" << endl;
    sleep(1);
    // int a = 10;
    // a /= 0; 
    int* p = nullptr;
    *p = 10;
    cout << "point error after" << endl;
    sleep(1);
    return 0;
}

运行结果为

image-20240125170943491

这里也是代码没有跑完直接崩溃了,这个本质也是收到了信号。收到的是11号信号

我们在捕捉一下11号信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "get a signal, number: " << signo << endl;
}

int main()
{
    signal(SIGSEGV, handler);
    cout << "point error before" << endl;
    sleep(1);
    // int a = 10;
    // a /= 0; 
    int* p = nullptr;
    *p = 10;
    cout << "point error after" << endl;
    sleep(1);
    return 0;
}

运行结果为

image-20240125171249312

和前面十分类似,会不断的捕捉11号信号,而且还不会退出

所以进程收到异常信号,不一定会退出。但是一定会执行异常处理方法

虽然上面捕捉后一直在打印,没有退出,不过其实我们大概率还是要将他们给退出的。因为一直打印也没什么用

image-20240125171650007

运行结果为

image-20240125171721591

为什么 /0 ,野指针会给进程发送信号?导致进程崩溃?

这里是因为,除零,野指针会给系统带来问题,操作系统识别到这些问题,然后OS给进程发送的信号。信号的默认处理动作就是终止自己,所以就崩了

操作系统为什么能检测到除零,野指针呢?

首先如下所示,如果是除零错误

在CPU上有一个eip/pc寄存器可以用来记录当前执行的是哪一行代码

image-20240125172949278

还有一种寄存器是状态寄存器。它里面的每一位都有特定的含义

image-20240125173431114

其中有一个就是溢出标志位

一旦我们的代码除零了,这个溢出标志位就变为1了

像我们在CPU的这些数据,都是这个进程的上下文

image-20240125173701835

也就是说,这里虽然我们修改的是CPU内部的状态寄存器,但是这里只影响我们自己。不会影响其他进程,进程切换的时候,其他进程会将自己的上下文数据放上去

在这里操作系统一定会知道这里出错了。因为CPU是硬件,OS是硬件的管理者。

所以操作系统才会向进程发送信号


如果是野指针异常

如下图所示,在CPU里面有一个内存管理单元,因为直接查页表太慢了,所以有一个MMU硬件来进行查表。

一旦异常,也就是地址转化失败了。虚拟到物理转化失败了。

在CPU内还有一个寄存器,一旦转化失败了。它会把转化失败的虚拟地址放在这里

image-20240125180552673

也就是说,一旦转化失败,CPU也能识别


而且因为他们是用的不同的寄存器,所以CPU也能区分出来是哪种报错,操作系统也就知道了


这里我们进程出异常以后,我们本应该退出。但是如果我们非要不退出。

意味着这个进程一直被调度运行。

硬件一直存在这个问题。我们也没有修正。随着我们的调度。操作系统一直在检测到这个异常,然后我们也一直在捕捉这个信号。所以就一直打印。


所以捕捉异常其实就不是让我们不让进程退出的,而是让我们知道我们这个进程是怎么死掉的

那么异常只能由硬件产生吗?

当然不是。

比如我们之前的管道,如果一开始读写端都打开,但是我们突然关闭了读端。那么写端进程就会被杀掉。会收到一个SIGPIPE(13)号信号。这就是一种软件异常。

也有的异常,操作系统只是会返回值出错的形式进行处理

image-20240125183437279

运行结果为

image-20240125183450658

5.2 alarm(软件条件产生信号)

image-20240126155127170

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

alarm 系统调用用于设置一个定时器,当定时器计时器达到指定的时间时,内核会发送一个 SIGALRM 信号(14号信号)给调用进程。这可以用于实现定时器功能,例如在一定时间间隔内执行某个特定的操作或执行定时任务

seconds 参数表示定时器的秒数。如果 seconds 参数为非零值,表示设置定时器,在指定秒数后会发送 SIGALRM 信号给进程。如果 seconds 参数为零,则表示取消之前设置的定时器。

返回值是剩余的未完成的定时器秒数。如果之前有一个定时器已经设置,调用 alarm 会取消之前的定时器,并返回剩余的秒数。如果没有之前的定时器,或者之前的定时器已经到期,返回值为 0。

比如如下的代码中

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
    int n = alarm(5);
    while(1)
    {
        cout << "proc is running..." << endl;
        sleep(1);
    }
    return 0;
}

运行结果为

image-20240126160304788

我们可以捕捉一下这个信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "get a signal, number: " << signo << endl;
    //exit(1);
}

int main()
{   
    signal(SIGALRM, handler);
    int n = alarm(5);
    while(1)
    {
        cout << "proc is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240126160519144

这里并不是异常导致的,所以不会循环式的疯狂捕捉。

我们也可以下面这样做,就可以每隔三秒打印一次了

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "get a signal, number: " << signo << endl;
    int n = alarm(3);
    //exit(1);
}
int main()
{   
    signal(SIGALRM, handler);
    int n = alarm(3);
    while(1)
    {
        cout << "proc is running..." << endl;
        sleep(1);
    }
    return 0;
}

image-20240126160814824

有了这个闹钟,我们就可以在执行主要任务的同时,去定时完成其他任务了。像下面这样即可

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void work()
{
    cout << "print log ..." << endl;
}

void handler(int signo)
{
    work();
    //cout << "get a signal, number: " << signo << endl;
    int n = alarm(3);
    //exit(1);
}
int main()
{   
    signal(SIGALRM, handler);
    int n = alarm(3);
    while(1)
    {
        cout << "proc is running..." << endl;
        sleep(1);
    }
    return 0;
}

关于它的返回值,我们可以这样做

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "get a signal, number: " << signo << endl;
    int n = alarm(5);
    cout << "剩余时间: " << n << endl; 

}

int main()
{   
    signal(SIGALRM, handler);
    int n = alarm(50);
    while(1)
    {
        cout << "proc is running..., pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

运行结果如下

image-20240126161754595

理解alarm

我们知道每个进程都可以使用alarm设置闹钟,所以操作系统中一定有大量的闹钟。所以操作系统要管理闹钟,所以闹钟就会用struct结构体描述,然后用链表等数据结构管理起来。这样所谓的闹钟管理就变成了对链表等的增删查改。

这个alarm结构体里面,一定有pid,或者pcb的指针。

操作系统底层中alarm的底层所用的时间用的是时间戳。这样最简单。

只要系统的当前时间大于等于里面设置的时间,就会发信号。

不过我们遍历链表的时候是比较浪费时间的。所以用一个小堆是最简单的。

6.core dump

我们可以看一下信号的详细信息手册

image-20240126163842284

我们可以注意到,常见的信号中大部分是终止信号的。还有一些是暂停(Stop),继续(Cont),忽略(Ign)等。

我们可以注意到终止信号中,有一些是Core,有一些是Term

在我们当时提到进程退出的时候,有一个这个字段core dump标志

image-20240126164328929

这个是用来表示是Core方式被杀还是Term方式

接下来我们使用这段代码来看看这个标志位

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

using namespace std;
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        int cnt = 500;
        while (cnt)
        {
            cout << "I am a chid process, pid: " << getpid() << "cnt: " << cnt << endl;
            sleep(1);
            cnt--;
        }

        exit(0);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) 
            << " exit signal: " << (status&0x7F) << " core dump: " << ((status>>7)&1) << endl;
    }
}

如果被2号信号所杀,如下所示,这个标志为为0。而2号信号对应的Term方式

image-20240126171331475

如果我们用8号信号所杀,我们发现这个core dump 标志位还是0。但是8号信号对应的是Core。

image-20240126171709965

我们发现这两个都没有什么变化。

其实如果我们当前用的是虚拟机的话。如果进程崩掉的话。我们会发现当前目录会形成一个临时文件,这个临时文件是XXX.core文件。而我们当前的云服务器上会发现没有,这是因为默认云服务器上的core功能是被关闭的

那么这是为什么呢?是什么呢?怎么办呢?

我们使用下面这个命令,它可以查看系统当中一些标准的配置

ulimit -a 

image-20240126173808287

其中这个core file size选项,我们可以使用下面指令去查

ulimit -c

image-20240126174000025

我们可以看到结果为0,也就是说这个,core默认是被关掉的

我们可以使用下面指令去设置它

ulimit -c 10240(要设置的大小)

如下所示,也就是说,最大是这么大

image-20240126174145061

上面就是开启core

如果要关闭的话,我们可以直接设置为0

image-20240126174350554

当我们设置好了以后,我们再去使用2号信号和8号信号,结果就不一样了

image-20240126175209648

现在此时,八号信号的core dump就是1了

更关键的是,我们发现现在确实有这个临时文件了

image-20240126175425600

这个很明显就是刚刚的pid

打开系统的core dump功能

一旦进程出现异常,OS会将进程在内存中的运行信息,给我dump(转储)到进程的当前目录(磁盘),形成core.pid文件。

这就是核心转储(core dump)

而这个功能在云服务器上默认是被关闭的

那么为什么要进行核心转储呢?

上面的错误一定是运行时错误。

此时我们要知道什么原因错误了。在哪一行错误了。

所以我们要用core dump来进行定位我们的原始在运行时哪里出错了

我们可以先生成调试版本的可执行程序

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

using namespace std;
int main()
{
int a = 10;
int b =0;

a /= b;

cout << "a = " << a << endl;
}

运行结果如下所示

image-20240126180623130

此时很显然,已经帮我们核心转储了

然后我们使用gdb调试时候,可以直接使用下面命令

core-file core.23652

image-20240126181049695

我们发现就可以直接定位出错原因了

所以core可以直接复现问题之后,直接定位到出错行

也就是说,先运行,在core-file,是事后调试

所以它就可以为这些最常出现的问题,有一个core功能去终止。

image-20240126181341219

所以这个就相当于Term + Core

为什么这个功能云服务器是关闭的呢?

因为core dump功能消耗的内存比较大。而我们的服务器一般一旦挂掉就会自动重启。计算机的速度是很快的。如果重启后又挂掉了。这样瞬间会冲击磁盘。磁盘被写满后,可能操作系统也会挂掉。

此时问题就严重了。所以一般都要禁掉这个功能的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/353920.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】第三十七站:信号保存

文章目录 一、信号发送二、信号保存1.为什么要进行信号保存&#xff1f; 三、阻塞信号1.信号的一些相关概念2.在内核中的表示3.sigset_t4.信号集操作函数5.sigprocmask6.sigpending7. 总结 一、信号发送 如下所示&#xff0c;对于普通信号&#xff0c;它的编号是从1~31。这个是…

N-141基于springboot,vue网上拍卖平台

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plusredi…

九州金榜|为什么鼓励式家庭教育?

鼓励式教育是一种积极的教育方式&#xff0c;它强调通过鼓励和肯定来激发孩子的积极性和自信心&#xff0c;帮助孩子更好地成长和发展。在家庭教育中&#xff0c;鼓励式教育同样具有重要意义。九州金榜家庭教育和大家一起探讨关于鼓励式教育的好处以及意义&#xff1a; 一.有助…

JRT实体比对

之前已经实现了JRT实体编译的菜单&#xff0c;用Linux指令编译放在网站下的实体代码。那么就有个问题&#xff0c;有人就是直接换实体jar文件不修改网站的实体代码。或者就只修改实体代码不编译搁置在那里&#xff0c;那么后面更新实体的人就得给别人背锅&#xff0c;后面人新编…

机器学习 | 如何使用 Seaborn 提升数据分析效率

Seaborn和Matplotlib都是Python可视化库&#xff0c;它们都可以用于创建各种类型的图表。但是&#xff0c;Seaborn 和Matplotlib在概念和设计上有一些不同。 Matplotlib虽然已经是比较优秀的绘图库了&#xff0c;但是它有个今人头疼的问题&#xff0c;那就是API使用过于复杂&am…

Blender教程(基础)-物体的移动、旋转与缩放-04

一、新建一个立方体 ShiftA新建一个立方体用来演示。 二、物体的移动 xyz轴移动 点击下图图左侧的移动选项后&#xff0c;选中要移动的物体&#xff0c;会出现三个箭头的方向&#xff0c;这分别代表沿着x、y、z轴移动。xyz平面移动 这个小正方体代表沿着某一个面移动&#…

04.领域驱动设计:了解聚合和聚合根,怎样设计聚合

目录 1、概述 2、聚合 3、聚合根 4、怎么设计聚合 4.1 聚合的构建过程主要步骤 第 1 步&#xff1a;采用事件风暴。 第 2 步&#xff1a;选出聚合根。 第 3 步&#xff1a;找出与聚合根关联的所有紧密依赖的实体和值对象。 第 4 步&#xff1a;画出对象的引用和依赖模型…

vue3框架基本使用

一、安装包管理工具 vite和vue-cli一样&#xff0c;都是脚手架。 1.node版本 PS E:\vuecode\vite1> node -v v18.12.12.安装yarn工具 2.1 yarn简单介绍 yarn是一个包管理工具&#xff0c;也是一个构建、打包工具 yarn需要借助npm进行安装&#xff1a;执行的命令行npm i…

找不同-《企业应用架构模式》2024典藏版

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 以下是2004年《企业应用架构模式》中译本和2024年《企业应用架构模式》典藏版译本的页面。 您能从中找出至少10处不同吗&#xff1f; 如何选择UMLChina服务 UMLChina公众号精选&…

Blender教程(基础)-面的细分与删除、挤出选区-07

一、Blender之面的细分 新建一个立方体&#xff0c;在编辑模式下、选中一个面。 在选中的面上单击右键弹出细分选项&#xff0c;选择细分。 在选中细分后、会默认细分1次。修改细分次数在左下角 二、Blender之面的删除 选择中需要操作的面&#xff0c;在英文状态下按X键弹…

VSCode 1.85.0更新的3个实用功能

1、单个文件可直接拖拽为独立窗口 当单文件过长&#xff0c;直接分成两个视图就不用上下频繁滚动 2、将终端移动到编辑器区域 此时&#xff0c;终端也可像文件一样拖拽为独立窗口 3、文件夹目录粘性头部 默认关闭&#xff0c;需要设置 "workbench.tree.enableStickyScro…

Linux权限的概念,shell命令以及运行原理

目录 1.shell命令以及运行原理2.Linux权限2.1Linux中的两类用户2.2Linux权限管理2.2.1文件访问者的分类&#xff08;人&#xff09;2.2.2文件类型和访问权限&#xff08;事物属性&#xff09;2.2.3文件的类型以及权限的缩写2.2.4文件权限值的表示方法2.2.5文件访问权限的相关设…

HTML 曲线图表特效

下面是代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>基于 ApexCharts 的 HTML5 曲线图表DEMO演示</title><style> body {background: #000524; }#wrapper {padding-top: 20px;background: #000524;b…

opencv#33 边缘检测

边缘检测原理 图像的每一行每一列都可以看成是一个连续的信号经过离散后得到的数值&#xff0c;例如上图左侧给出的图像由黑色到白色的一个信号&#xff0c;也就是图像中某一行像素变化是由黑色逐渐到白色&#xff0c;我们将其对应在一个坐标轴中&#xff0c;将像素值的大小对应…

sysfs: cannot create duplicate filename ‘/devices/virtual/leds/led1‘问题查找及解决

问题描述&#xff1a;安装模块时出现如下错误&#xff1a; [rootVinxin_PC leds]# cd /driver_Test/ [rootVinxin_PC driver_Test]# ls app leds-s5pv210.ko [rootVinxin_PC driver_Test]# lsmod Module Size Used by Not tainted [rootVin…

最全全国十七个数据入表和资产化案例深度解析

2024年1月1日起&#xff0c;财政部会计司发布的《企业数据资源相关会计处理暂行规定》正式施行&#xff0c;规定为数据资源的会计处理提供了明确的指导原则。这一里程碑事件也标志着我国在数据资产入表领域正式进入实际操作阶段&#xff0c;随后&#xff0c;数据资产入表在全国…

stable diffusion学习笔记——文生图(一)

模型设置 基本模型 基本模型也就是常说的checkpoint&#xff08;大模型&#xff09;&#xff0c;基本模型决定了生成图片的主体风格。 如上图所示&#xff0c;基本模型的后缀为.safetensors。需要存放在特定的文件夹下。 如果用的是启动器&#xff0c;可以在启动器内直接下载。…

2024年黑龙江省安全员C证证考试题库及黑龙江省安全员C证试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年黑龙江省安全员C证证考试题库及黑龙江省安全员C证试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考…

(java)idel中将对与json的相互转

1、目录结构 2、导入包 在模块下面建立lib目录将包导入模块中 包的百度网盘 链接&#xff1a;https://pan.baidu.com/s/1abNF8cOTeNb00rM7tp04iQ?pwd39wc 提取码&#xff1a;39wc 3、建立两个测试类person和dog类 public class Dog {private String name;private int age…

STM32标准库——(5)EXTI外部中断

1.中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&#xff…