• 信号:singal


    信号

    这里的信号:OS提供的一种软件层面的通信机制

    什么是信号

    信号是一种通知进程某件事情发生了的一种通信机制

    通过向进程发送某个信号,可以告诉进程发生了什么事情,进程收到这个信号后,就知道某事情发生了,进程可以做出相应的响应(处理)

    信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制

    信号也是一种通信机制,目的就是用于告诉进程发生了什么事情

    Linux所实现的信号这种通信机制,与IPC(进程间通信)所不同的是:

    信号属于不精确通信,信号只能告诉进程大概发生了什么事情,但是不能准确的告诉进程详细的细节信息

    信号有区别于精确IPC之外,自己的使用场合

    本章所涉及到各个API就是专门与信号相关的API

    有关信号的内容

    	1)比如ctrl+c为什么可以结束进程
    	
    	2)产生指针错误时为什么进程能够自动结束,并提示产生了“segment fault”(段错误)。
    	
    	3)kill命令到底是怎么结束进程的
    
    • 1
    • 2
    • 3
    • 4
    • 5

    所有的信号机制的实现,原理都是相通

    • c、c++、java的线程库有类似的信号概念
    • QT界面开发时有类似信号和槽的概念

    信号的相关概念

    信号的命名
    • Linux定义了很多的信号,所有的信号都是一个整数编号
    • 为了好辨识,Linux系统给这些整数编号都定义了对应的宏名
      • 宏名都是以SIG开头,比如SIGABRT
      • SIG:signal的缩写;ABRT:abort的缩写

    宏名SIG____,____是对信号的描述

    调用abort函数时,该函数就会向进程发送一个SIGABRT信号

    谁会向进程发送信号

    1. 其他进程发送信号

      在命令行终端窗口通过kill命令向某个进程发送一个信号将其终止

    2. 内核发送信号

      Linux内核可能会发送该事件对应的信号给某个进程

      ex:

      进程从管道文件读取数据,但是管道文件的读权限被关闭了,进程会被内核发送一个SIGPIPE信号,提示读管道出错了

      • 指针错误,发送SIGSEGV
    3. 底层硬件发送信号

      底层硬件发生了某个事件,会向进程发送对应的某个信号

      但是硬件先是发送到内核,然后内核转发该硬件信号

      比如:

      按下ctrl+c按键终止进程时,内核收到ctrl+c按键后,会向正在运行的进程发送SIGINT信号,将其异常终止

    进程收到信号的处理

    分别是忽略、捕获、默认

    忽略的意思就是说,进程就当信号从来没有发生过

    捕获的意思就是说,进程会调用相应的处理函数,进行相应的处理

    如果不忽略也不捕获的话,此时进程会使用系统设置的默认处理方式来处理信号

    信号的种类

    在命令行执行kill -l,可以显示所有的信号列表(没有32,33)

    为什么信号多
    //每个信号代表着某种事件,一般情况下,当进程收到某个信号时,就表示该信号所代表的事件发生了。
    //可能发生的事件会有很多种,所以弄出了这么多的信号,每个信号对应着自己特有的事件。
    
    • 1
    • 2
    • 35~64:这些信号是Linux后期增设的信号,这些个信号不需要关心,不用了解
    • 1~34:也不是所有的信号都要掌握,我们只关心其中常用的信号

    对于这些个信号,理解才是关键

    常用信号

    kill + PID如果杀不死进程,中间加入:-SIGKILL 或 -9


    ctrl+\ 和 ctrl+c 都可以终止进程,但是\会产生core文件

    如果发送的这些信号的处理方式是终止,那么进程会被终止掉

    kill()

    kill -l:查询信号列表

    进程是否会被信号终止,是由信号的处理方式来决定的,而不是kill命令

    • kill所起到的作用只是发送信号

    发送信号的完整格式:kill -信号编号 PID

    kill PID,默认发送的是15(SIGTERM)信号,等价于kill -SIGTERM PID或者kill -15 PID

    • 只有发送15这个信号时才能省略信号编号,发送其它信号时必须写明信号编号

    注意:

    命令+&是在后台,让出命令行窗口,交给shell;在终端输入fg即可切换

    pkill()

    用法与kill差不多,只不过kill是按照PID来识别进程的,pkill是按照名字来识别进程的

    Pkill -信号编号 名字:pkill -SIGINT a.out

    如果不写明信号编号的话,默认发送的是15(SIGTERM)这个信号

    信号的发送与接收

    一般来说,大多数发送信号的原因,都是因为内核、硬件发生了某些事件

    向某个进程发送该事件专用的信号,告诉该进程这个事件发生了

    不过对于我们自己写的进程来说,其实更多是接收信号,而不是发送信号

    自己发送信号(非内核和硬件)的情况
    1. ctrl+C、ctrl+\发送信号,终止正在运行进程
    2. 命令行执行kill命令发送信号,终止进程
    3. 因为某些特殊需求,在我们自己的程序里面,需要调用函数向另一个进程发送某个信号
    比如A进程和B进程需要协同工作,A进程将相应事件准备好以后,可能需要发送一个信号给B进程,通知B进程,B进程收到信号后,就知道该事件已经准备好,可以配合A进程做事了。
    --------
    自己进程调用kill函数发送信号时,最好发送的是SIGUSR1、SIGUSR2这两个自定义信号
    发送专用事件的信号的话,很可能造成误会
    
    • 1
    • 2
    • 3
    • 4
    接收信号

    在一般情况下,进程并不会去重新设置信号的处理方式,而是使用信号的默认处理方式来处理信号

    如果需要重新设置信号的处理方式,可以调用相应API来重新设置信号的处理方式。

    core文件

    同样终止进程-----ctrl+c不产生core文件,ctrl+\产生core文件

    • core dumped
    什么是core文件

    ​ 用于保存程序(进程)在当前结束的这一刻,进程在内存中的代码和数据

    **作用:**core文件可以用于分析进程在结束时的状况

    由于进程代码和数据都是二进制的,需要特殊软件翻译后才能看懂

    1.只有某个些信号在终止进程时才会产生core文件
    2.一般情况下并不会创建这个文件,因为系统默认将产生core的设置给关闭了,只有打开后这个设置后才会保存core文件。						
    	所以当你看到提示core dumped,这就表示这个信号终止进程时,会产生core文件,只不过由于关闭了设置,因此core文件被丢弃了,dumped就是丢弃的意思。						
    
    • 1
    • 2
    • 3

    signal()

    #include 
    //功能:设置某个信号的处理方式
    //处理方式可以被设置为忽略,捕获,默认
    typedef void (*sighandler_t)(int);
    /*
    成功:返回上一次的处理方式
    失败:返回SIG_ERR宏值,并且设置errno
    */
    //signum:信号编号
    //handler:信号处理方式
    //sighandler_t handler也有直接写成void (*handler)(int)
    sighandler_t signal(int signum, sighandler_t handler);
    /*
                    (a)忽略:SIG_IGN
    				(b)默认:SIG_DFL
    				(c)捕获:填写类型为void (*)(int)的捕获函数的地址
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    进程的进程表(task_struct)中会有一个“信号处理方式登记表

    专门用于记录信号的处理方式

    • 调用signal函数设置某个信号的处理方式时,会将信号的处理方式登记到该表中

    每个进程拥有独立的task_struct结构体变量,因而每个进程的“信号处理方式登记表”都是独立的,所以每个进程对信号的处理方式自然也是独立的,互不干扰

    捕获函数也被称为信号处理函数			
    			void signal_fun1(int signo)
    			{
    								...
    			}			
    			void signal_fun2(int signo)
    			{
    								...
    			}
    			int main(void)
    			{
    				signal(SIGINT, signal_fun1);
    				signal(SIGSEGV, signal_fun2);
    				return 0;
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    #include
    int main(){
        //SIGINT 2  ctrl+C终止进程
        //采用:SIG_INT后,该方式就不起作用了
        signal(SIGINT,SIG_IGN);
        //再次还原成默认就起作用了
        signal(SIGINT,SIG_DFL);
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    把ctrl+\的信号也设置成忽略

    此时:只能在终端通过ps -al查看pid,通过kill杀死,或pkill+文件名

    #include
    #include
    #include
    void signal_fun1(int signo){
        printf("SIGINT singo=%d\n",signo);
    }
    void signal_fun2(int signo){
        printf("SIGQUIT singo=%d\n",signo);
    }
    int main(){
        signal(SIGINT,signal_fun1);
        signal(SIGQUIT,SIG_IGN);
        signal(SIGQUIT,signal_fun2);
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
    ^CSIGINT singo=2
    ^\SIGQUIT singo=3
    ^CSIGINT singo=2
    ^\SIGQUIT singo=3
    已终止----------pkill main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    signal()返回值
    #include
    #include
    #include
    typedef void (*sighandler_t)(int);
    void signal_fun1(int signo){
        printf("SIGQUIT singo=%d\n",signo);
    }
    int main(){
        //sighandler_t handler也有直接写成void (*handler)(int)
        //sighandler_t signal(int signum, sighandler_t handler);
        sighandler_t ret=NULL;
        ret=signal(SIGINT,SIG_IGN);
        ret=signal(SIGINT,SIG_DFL);
        ret=signal(SIGINT,signal_fun1);
        ret=signal(SIGINT,SIG_DFL);
        if(ret==SIG_ERR){
            perror("signal fails!");
        }
        else if(ret==SIG_IGN){
            printf("ignore!\n");
        }
        else if(ret==SIG_DFL){
            printf("default!\n");
        }
        else{
            printf("capture=%p\n",ret);
        }
        while(1);
        return 0;
    }
    /*每一次返回值是上一次的结果*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    调用捕获函数的过程
    • 当信号没有发生时,进程正常运行
    • 当信号没有发生时,进程正常运行
      • 一看信号的处理方式是捕获,就会从“信号处理方式登记表”中将捕获函数的地址取出并执行捕获函数
      • 捕获函数执行完毕后,恢复进程的正常运行

    当信号来时,如果当前有一条指令正在运行,会先等这条指令运行执行完毕后再去调用信号处理函数

    如果信号处理函数有提前执行return的话,会提前返回到主线

    #include
    #include
    #include
    typedef void (*sighandler_t)(int);
    void signal_fun1(int signo){
        printf("SIGQUIT singo=%d\n",signo);
        exit(1);//_exit()
    }
    void process_exit_deal(void){
        printf("save data in list!\n");
    }
    int main(){
        atexit(process_exit_deal);
        signal(SIGINT,signal_fun1);
        while(1);
        return 0;
    }
    /*
    ^CSIGQUIT singo=2
    save data in list!
    */
    //exit很重要,会在exit前执行进程处理函数:atexit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 信号被设置为SIG_IGN(忽略)
      进程将不会再接收到这个信号,这信号对进城没有任何影响

    2. 设置为捕获时,需要将handler设置为捕获函数的地址,类型为void (*)(int)

      为了确保和捕获函数的类型统一:

      SIG_DFL、SIG_IGN和SIG_ERR宏的类型也必须是void (*)(int)

      这几个宏定义在了头文件中

      #define SIG_DFL	((void (*)(int))0)	
      #define SIG_IGN	((void (*)(int))1)
      #define SIG_ERR	((void (*)(int))-1)
      
      • 1
      • 2
      • 3
      #include
      #include
      #include
      int main(){
          printf("SIG_ERR=%d\n",SIG_ERR);
          printf("SIG_IGN=%d\n",SIG_IGN);
          printf("SIG_DFL=%d\n",SIG_DFL);
          while(1);
          return 0;
      }
      /*有警告
      guojiawei@ubantu-gjw:~/Desktop/signal$ ./a.out 
      SIG_ERR=-1
      SIG_IGN=1
      SIG_DFL=0
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      除了SIGKILL这个信号外,其它所有的信号都可被忽略和捕获

    Linux规定SIGKILL这个信号一定不能被忽略和捕获

  • 相关阅读:
    SpringBoot生成Excel文件并下载到浏览器
    ElementUI 自定义 Tree 树形控件背景
    springboot集成hystrix和feign,解决fallback,fallbackFactory不生效问题
    【源码】C++坦克大战源码
    建模助手:Revit中梁注释设置表达相对净高
    JVM(10)之垃圾收集器PartNew&CMS以及三色标记算法详解
    windows系统git使用ssh方式和gitee/github进行同步
    中国移动云能力中心(苏小研)--秋招面经(已offer)
    LeetCode热题100——链表
    Ubuntu工具-01 UEX
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/127852856