Linux下的信号机制

基本概念:

  1. 生活中的信号:
    一说到信号,我们可能立马想到的是信号枪(垂直打上去就有空投的那种,博主表示还没捡到过一次,Emmmm),抛开信号枪,我们的身边无时无刻不充斥着信号,比如红绿灯,看到红灯应该停下来,直到绿灯才可以往前走,你一看到就知道是什么意思,下一步该干嘛,但是遵不遵守就看你自己了,不过我们还是应该做一个尊重交通规则的乖宝宝,弘扬正能量。

  2. 计算机中的信号:
    计算机中的信号也是如此,计算机在收到一个信号后,并不会去立刻处理它,而是先把它保存起来,或者说标记起来,然后找一个合适的时机去处理它,下面我们就来详解Linux下的信号机制,包括信号是如何产生的,计算机如何处理信号,如何去捕捉信号等

Linux下查看所有信号的命令:kill -l
这里写图片描述

注意:信号表中是没有32和33号信号的,所以Linux下总共有62种信号

1~31 普通信号
34~64实时信号


本文主要讨论普通信号,对实时信号不做解释

大家可以从表中看出,每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:man 7 signal
这里写图片描述

信号的产生

  1. 通过终端按键产生信号
    用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信号(只作用于前台进程),Ctrl-Z产生SIGTSTP信号(可使前台进程停止),Ctrl-\产生SIGQUIT信号

    前台进程:基本不用和用户交互,优先级稍微低一些(运行前台进程: ./test.c)
    后台进程:需要和用户进行交互,需要较高的相应速度,优先级别高(运行后台进程: ./test.c &)

    #include <stdio.h>                                                                                                              
    int main()
    {
        while(1)
        {
            printf("Wait for Ctrl-C\n");
            sleep(3);//每隔3秒打印一句话,并且让它死循环
        }
        return 0;
    }

在打印了两句话后,我按下了Ctrl-C,进程终止,但是如果将该进程放在后台执行时,Ctrl-C就没有用了,这时候就要用kill命令了,找到该进程的进程ID后,使用kill -9命令可以直接杀死该进程
这里写图片描述

  1. 硬件异常产生信号
    这些异常由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程
    3.系统调用或者用户命令产生信号
    一个进程调用kill()函数可以发送信号给另一个进程,也可以发送kill命令给某个进程,只要知道目标进程的进程ID(PID)就好, kill命令其实也是通过调用kill()函数实现的,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。
    kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号。
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
这两个函数都是都是成功返回0,错误返回-1,这里就不做演示了
  1. 软件条件产生
    当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的 管道写数据时产生SIGPIPE信号。如果不想按默认动作处理信号,用户程序可以调用sigaction()函数告诉内核如何处理某种信号

演示alarm(闹钟)

    #include <stdio.h>                                                                                                              
    #include<signal.h>


    int main()
    {
        int count = 0;
        alarm(1);//设置闹钟时间为1秒
        while(1)
        {
            printf("count = %d\n",count);
            count++;
        }//这个函数实现1秒钟内不停地数数,1秒钟到了就被alarm发送的SIGALRM信号所终止
        return 0;
    }

说明当count数到154391时,接收到了SIGALRM信号,进程终止掉了
这里写图片描述
信号的存储

Linux一共有31中普通信号,也就是1~31号,看到这个数字大家有没有想到bit位呢?所以在Linux下每个进程的PCB中有专门位图来存储该进程接收到的信号信息,而且只需要一个字节的31个bit位就能有效的存储这些信号,bit位的位置就是信号的编号,每个bit位对应的值就是当前信号的信息(0–表示没有收到该信号,1–表示收到该信号),如果当前进程收到了某个信号,把对应的bit位的值由0改为1就行
实时信号不用位图存储,用的是链表,了解一下即可

信号的处理方式

  1. 忽略该信号
  2. 执行该信号的默认处理动作(部分信号的默认处理动作是终止进程)
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数(前两种处理方式是在内核态进行的),这种方式称为捕捉信号

信号的捕捉
上面说过,信号的捕捉大概意思就是当进程在处理某信号时,不按照信号的默认处理动作执行,而是执行用户自定义的函数

signal函数

        #include<signal.h>
        typedef void (*sighandler_t)(int);
        sighandler_t signal(int signum, sighandler_t handler);
参数:signum:信号编号 handler:指向怎样捕捉该信号的函数 
返回值:signal函数的返回值是一个函数指针,成功返回返回以前的处理 配置,失败返回错误码对应的错误提示。 

演示代码:

    #include <stdio.h>
    #include <signal.h>

    void my_hander(int signum)
    {
        printf("got a %d signal\n",signum);//打印收到的信息编号
    }

    int main()
    {
        signal(2,my_hander);//收到2号信号后,执行用户自定义函数
        while(1)            //(Ctrl-C产生的SIGINT信号,原默认处理动作是终止进程)
        {
            sleep(1);
        }                                                                                                                           
        return 0;
    }     

因为Ctrl-C产生的信号默认处理动作已被我们在该进程内更改,所以最后使用Ctrl-Z来使进程终止
这里写图片描述

操作系统处理信号时并不是在用户代码的内部空间执行,而是在内核态转向用户态时处理所有接收到的信号,下面我用一张图来说明

这里写图片描述

第2部表明操作系统不是在进程接收到信号的第一时间就去处理该信号,而是在合适的时机进行处理,图里过程间的相互转换我们可以将其当做一个无穷的标志( ∞ ),便于记忆,而且整个过程共进行了四次用户态和内核态的转换

猜你喜欢

转载自blog.csdn.net/IT_xiaoye/article/details/79979347
今日推荐