【Linux】信号概念&信号的产生方式&核心转储问题

【Linux】信号概念&信号的产生方式&核心转储问题

  • 一、信号的概念
    • 1.1 信号的引进
    • 1.2 计算机中的信号
    • 1.3 信号的保存及发送
  • 二、信号的产生
    • 2.1 键盘发送产生信号
    • 2.2 系统调用方式产生信号
      • 2.2.1 kill
      • 2.2.2 raise
      • 2.2.3 abort
      • 2.2.4 信号行为的理解
    • 2.3 硬件异常产生
      • 2.3.1除0错误
      • 2.3.2 野指针操作
    • 2.4 软件条件产生
    • 2.5 小结
  • 三、信号的核心转储问题
    • 3.1 信号终止Core&Trem

一、信号的概念

【Linux】信号概念&信号的产生方式&核心转储问题_第1张图片

Linux 信号是进程之间进行通信和处理异步事件的一种方式。信号由内核向进程发送,用于通知进程某种事件已经发生

1.1 信号的引进

  1. 信号还没有产生的时候,我们要知道如何识别信号,并且知道接收信号后该如何反应。比如我们过马路时,我们首先需要知道红绿灯是一种信号,并且知道根据颜色的不同做出相应动作。
  2. 当信号产生的时候,我们不一定要立刻去处理信号,因为我们可能有优先级更高的事情。比如正在上课,外卖到了,不会立即去取。
  3. 信号已经产生,但我们暂时没有处理,那么一定要有某种方式记录这个信号已经产生。比如我们收到外卖到了的信号后没有立刻去拿,但手机里的短信记录着外卖已经到了。
  4. 处理信号时往往有三种方式:默认行为,自定义行为,忽略行为比如我们在过马路的时候遇到红灯,默认行为是站在原地等。但有一次我突然想上厕所,于是趁着红灯的时间去了一下路边的厕所,这就是自定义行为。而忽略行为就是不管红灯的提示,径直走过马路。

1.2 计算机中的信号

  • 进程虽然现在没有受到信号,但是进程一定知道如何识别信号和做出相应反应。程序员在设计进程的时候已经内置了处理方案,信号是进程中特有的特征。
  • 进程在收到信号时可能不会立即处理,因为有优先级更高的事情。如果这样需要先把信号记录下来,等合适的时候再处理。
  • 进程处理信号有三种方式:
    (1)默认行为:终止进程,暂停,继续运行等。
    (2)自定义行为,由我们自己编写。这种方式也称为捕捉一个信号
    (3)忽略信号。

1.3 信号的保存及发送

每个进程都对应一个task_struct(PCB),在这个task_struct中,记录着进程的各种信息,包括信号的记录。信号在task_struct中是以位图的方式记录的,当收到一个信号的时候,会把位图中对应数字由0置1
【Linux】信号概念&信号的产生方式&核心转储问题_第2张图片

【1.31】普通信号 【32,64】实时信号(不考虑)
【Linux】信号概念&信号的产生方式&核心转储问题_第3张图片
信号发送的本质就是操作系统修改了进程的信号位图,进程根据修改后位图的值做出相应处理。

二、信号的产生

#man 7 signal----信号的默认行为
【Linux】信号概念&信号的产生方式&核心转储问题_第4张图片

2.1 键盘发送产生信号

热键[CTRL+C]:终止进程,ctrl+c实际一个组合键,OS会将ctrl+c解释成2号信号【SIGINT】
[CTRL+\ ]发送3号信号

认识一下【sighandler_t signal(int signum, sighandler_t handler)】自定义信号行为函数
signum:需要自定义哪一个信号
handler:函数指针类型参数,传入函数名,当收到某个信号时调用该函数
返回值:函数指针

#include 
#include 
#include 

//如果捕捉1-31所有的信息号,OS会默认把9号设置成管理员信号,不可被捕捉
void handler(int signo)
{
    std::cout << "进程捕捉到了一个信号,信号编号是: " << signo << std::endl;
    // exit(0);
}

int main()
{
    // 这里是signal函数的调用,并不是handler的调用
    /// 仅仅是设置了对2号信号的捕捉方法,并不代表该方法被调用了
    // 一般这个方法不会执行,除非收到对应的信号!
    signal(2, handler);

    while(true)
    {
        std::cout << getpid() << std::endl;
        sleep(1);
    }
}

运行结果
【Linux】信号概念&信号的产生方式&核心转储问题_第5张图片
ctrl+c默认动作是退出进程,但是使用signal函数修改了它的默认行为,导致ctrl+c不能退出了。
注意⚠️:键盘是硬件,通过组合键按下给OS识别,OS将组合键解释成信号,向目标进程发信号,目标进程在合适的时候处理这个信号

2.2 系统调用方式产生信号

int kill(pid_t pid, int signo); 向某个进程发送信号
int raise(int sig); 给自己发送信号,相当于kill(getpid(),sig)
void abort(void);给自己发送指定信号【SIGABRT(6)】 ,相当于kill(getpid(),6)

注意⚠️:发送信号的能力是OS的,但是有这个能力并不一定有使用这个能力的权力,一般情况下用户使用OS向目标进程发信号。OS有这个能力,但是它的能力并不是为自己准备的,是为用户准备的。

2.2.1 kill

//mysignal.cc
void UseBook(const std::string& proc)
{
     std::cout << "\nUseBook: " << proc << " pid signo\n"
              << std::endl;
}
//argc:命令行参数个数
//argv:命令行参数字符串
int main(int argc,char*argv[])
{
	/*NO1:int kill(pid_t pid, int signo);*/
    //例如 kill       36892       2
    //    argv[0]     argv[1]     argv[2]
    if(argc!=3)
    {
        UseBook(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    
    int n=kill(pid,signo);
    if(n!=0) perror("kill");

}
//mytest.cc
#include 
#include 
int main()
{
     while (true)
     {
        std::cout << "我是一个进程: " << getpid() << std::endl;
        sleep(1);
     }
     
}

运行结果
【Linux】信号概念&信号的产生方式&核心转储问题_第6张图片
结论:kill命令底层实际上就是kill的系统调用,信号的发送由用户发起而OS执行的

2.2.2 raise

int main()
{
    //int raise(int sig);  给自己发送任意信号
    //这里原本是打印十次cnt,
    //修改后:当cnt>=5的时候,让进程退出
    int cnt=0;
    while(cnt<=10)
    {
        std::cout<<"cnt: "<<cnt++<<std::endl;
        sleep(1);
        if(cnt>=5) raise(3);
    }
}

【Linux】信号概念&信号的产生方式&核心转储问题_第7张图片

2.2.3 abort

int main()
{
    //abort给自己发送6号信号【SIGABRT】
    int cnt=0;
    while(cnt<=10)
    {
        std::cout<<"cnt: "<<cnt++<<std::endl;
        sleep(1);
        if(cnt>=5) abort();
    }
}

【Linux】信号概念&信号的产生方式&核心转储问题_第8张图片

2.2.4 信号行为的理解

有很多的情况进程收到大部分的信号,默认处理动作都是终止进程。
信号的意义:信号的不同代表不同的事件,但是对事件发生之后的处理动作是可以一样的。

2.3 硬件异常产生

2.3.1除0错误

int main()
{
    while (true)
    {
        std::cout << "我是一个进程: " << getpid() << std::endl;
        sleep(1);
        int a=1;
        //当前进程会受到来自OS系统的信号,SIGFPE【8号信号】
        a/=0;//除0错误
    }
    
}

在这里插入图片描述
产生原因
除0之后,CPU状态寄存器溢出标记位被置1,OS检测到状态寄存器异常给进程发送异常信号,目标进程在后续合适的时候处理信号,进而让自己退出
【Linux】信号概念&信号的产生方式&核心转储问题_第9张图片

void catchSig(int signo)
{
    cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
}
int main(int argc,char*argv[])
{
   
    signal(SIGFPE,catchSig);//捕捉任意信号
    int a = 1;
    a/=0;//只执行一次,却一直打印
    while(true)
    {
        cout<<"我在运行中..."<<endl;
        sleep(1);
    }
}

【Linux】信号概念&信号的产生方式&核心转储问题_第10张图片
收到信号不一定会引起进程退出进程没有退出,则还有可能还会被调度,CPU内部的寄存器只有一份,但是寄存器中的内容属于当前进程的上下文,一旦出现异常我们没有能力去修正这个问题,所以当进程被切换的时候,就有无数次状态寄存器被保存和恢复的过程,所以每一次恢复的时候就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1。
进程切换概念
  进程切换就是从正在运行的进程中,收回CPU的使用权利,交给下一个要运行的进程。
  
  实际上,因为被切换的进程下一次可能还要继续运行,所以这个过程又是被切换进程和将要运行进程的上下文切换,这个上下文就包括进程正在运行时的一些信息。

2.3.2 野指针操作

void catchSig(int signo)
{
    std::cout<<"获取到一个信号,信号编号是:"<<signo<<std::endl;
    exit(1);
}
int main(int argc,char*argv[])
{
    //收到11号信号才会调用catchSig
    signal(11,catchSig);
    while(true)
    {
        std::cout<<"我在运行中..."<<std::endl;
        sleep(1);
        int*p = nullptr;
        *p=10;
    }
}

在这里插入图片描述

产生原因:
【Linux】信号概念&信号的产生方式&核心转储问题_第11张图片

虚拟地址向物理地址转换,需要用到mmu(内存管理单元),它被集成在CPU中,使用野指针会被mmu识别报错【试图访问0地址处】,OS识别到之后发送11号信号【非法的内存引用】给进程,进程才去终止。

2.4 软件条件产生

管道——13号信号SIGPIPE
管道文件,如果读端关闭,写端一直在写,写的数据没有进程去读就没有意义了,OS不允许这种情况,会终止这个进程,向写进程发送13号信号SIGPIPE。这中间没有仅仅是软件导致的。
定时器——14号信号SIGALRM
定时器软件条件:alarm():设定闹钟,调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

int cnt=0;
void catchSig(int signo)
{
    std::cout<<"获取到一个信号,信号编号是:"<<signo<<std::endl;
    exit(1);
    //alarm();//循环闹钟,每隔1s打印
}
int main()
{
    signal(SIGALRM,catchSig);
    alarm(1);
    
    while (true)
    {
        std::cout<<"cnt: "<<cnt++<<std::endl;
    }
    
}

【Linux】信号概念&信号的产生方式&核心转储问题_第12张图片
理解闹钟为软件条件产生:(超时条件)

“闹钟”其实就是用软件实现的,任意一个进程都可以通过alarm系统调用在内核中设置闹钟,那么OS内可能会存在很多的闹钟,OS则需要管理闹钟:先描述,再组织,所以OS内部设置闹钟的时候,要为闹钟创建特定的数据结构对象。
【Linux】信号概念&信号的产生方式&核心转储问题_第13张图片
内核管理闹钟比如最大堆、最小堆:比如100个闹钟可以把100个闹钟的when建小堆,最小的就在堆顶,只要堆顶的没有超时那其余的自然没有超时,所以只需要检查堆顶即可,就可以管理好闹钟。

2.5 小结

上面所说的所有信号产生,最终都要有OS来进行执行,因为OS是进程的管理者
信号的处理在合适的时候处理的
信号如果不是被立即处理,那么信号需要暂时被进程记录下来,记录在PCB中
一个进程在没有收到信号的时候能知道自己应该对合法信号作何处理,程序员默认在系统中写好的
理解OS向进程发送信号:OS直接修改目标进程的PCB信号位图

三、信号的核心转储问题

3.1 信号终止Core&Trem

【Linux】信号概念&信号的产生方式&核心转储问题_第14张图片

数组越界问题

int main()
{
    //核心转储
    while(true)
    {
        int a[10];
        //a[100]=10;//没报错
        a[10000] = 10;
        //11号信号产生,引发段错误
    }
}

在这里插入图片描述
Term是正常结束,OS不会做额外的工作,Core代表OS初了终止的工作,还有其他工作。

云服务器上如果想看到Core类型信号的其他工作,需要使用 ulimit -c 1024设置块数;产生如下
【Linux】信号概念&信号的产生方式&核心转储问题_第15张图片
核心转储是当进程出现异常的时候,我们将进程在对应的时刻,在内存中的有效数据转储到磁盘中
为什么要有核心转储:支持调试
如何支持:gdb mysignal ;输入core-file core.pid,即可快速定位错误处!

你可能感兴趣的:(Linux系统编程,linux,服务器,c++,centos)