• Linux进程信号


    什么是信号

    生活中的信号:闹钟,红绿灯,鸡叫声,,,,,,
    这些场景触发的时候我们就知道自己该做什么了。 在信号产生之前,我们也知道遇到什么信号该做什么事情。
    同样的,我们受到信号以后也不一定马上会去执行,也有可能信号发出来了,我们收不到的情况。
    例如闹钟在客厅响了,你戴着耳机在卧室和女朋友一起愉快的开黑玩游戏,这时候你就听不到信号,听到了也不想管。

    同样的信号产生后,是OS给进程发送的,向进程内核数据结构task_struct写入信号数据
    同样的道理,进程收到信号以后也像我们人一样收到信号。 未收到的时候知道该如何处理,不立即处理,暂时保存信号数据,
    到时候再处理,也有可能不处理或者收不到信号

    也就是进程处理信号的方式:
    1,忽略此信号。
    2,执行该信号的默认处理动作。
    3,提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉
    (Catch)一个信号。

    如何产生信号:
    1,键盘产生(只作用于前台进程,./test & 就变成了后台进程)
    后台程序基本上不和用户交互,优先级别稍微低一点
    前台的程序和用户交互,需要较高的响应速度,优先级别稍微高一点
    2,程序中的异常问题,导致进程受到信号而退出
    3,通过系统调用去产生信号
    4,软件条件产生信号。(例如在匿名管道中,读端关闭,写端收到13信号后关闭)

    例如进程在死循环的时候,我们在键盘上输入ctrl+c,实际上就是通过OS给该进程发送了2号信号
    在这里插入图片描述

    kill -l 可以查看信号对应的编号信息。
    在这里插入图片描述

    man 7 signal 可以查看受到编号信号后进程的默认动作。
    在这里插入图片描述

    signal函数的功能(捕捉信号后自己处理)

    其中9号进程无法被定义。
    在这里插入图片描述

    Core Dump(核心转储)

    1,在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常情况)
    2,如果进程异常,进程的退出信号会被设置表明进程退出的原因,如果必要,OS会设置core dump标志位。
    在这里插入图片描述

    什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁
    盘上,文件名通常是core,这叫做Core Dump
    默认是不允许产生core文件的,
    因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许
    产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c
    1024

    有什么作用呢? 方便我们调试,寻找到错误所在的地方。
    但是要注意一下,Linux下默认是release版本,编译的时候要 + -g

    在这里插入图片描述

    说到这里,顺便复习下gdb的调试功能把
    r 是运行
    l 列出代码
    s 逐语句
    b 是设置断点
    info b 查看断点
    p sum 查看sum的值,值显示一次
    display sum 常显示
    undispaly 不显示
    d x 删除编号x的断点

    kill,raise,alarm系统调用

    在这里插入图片描述

    kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
    raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
    alarm()函数 比较简单,就不做演示了。

     int main()
     10 {
     11      int pid = fork();
     12      if(pid < 0)
     13      {
     14        perror("fork");
     15        exit(-1);
     16      }
     17 
     18      if(pid == 0)
     19      {
     20        sleep(5);
     21        printf("I am a child");                                                                                                               
     22        kill(getppid(),2); // 给父进程发送2号信号ctrl c终止
     23      }
     24      else
     25      {
     26         while(1)
     27         {
     28           printf("I am panret\n");
     29           sleep(1);
     30         }
     31 
     32      }
             
     35      return 0;
     36 }
    
    
    • 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
    4 int main()                            
     15 {                                     
     16     int count = 0;                    
     17     while(1)                          
     18     {                                 
     19        printf("hello chen\n");        
     20        count++;                       
     21                                       
     22        if(count == 10)                
     23          raise(9); //自己给自己发送9号信号                                                                                                   
     24     }
     25   
     26     return 0;
     27 }            
     28     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    再度理解OS给进程发送信号

    实际上就是OS向进程控制块写入数据
    信号递达(如何处理信号的动作):1,自定义捕捉。 2,默认处理。3,忽略
    未决:信号暂存于task_struct中,(还未处理)
    阻塞:OS运行进程暂时屏蔽指定信号
    1,该信号还是未决状态,2,该信号不能被处理,直到解除阻塞

    下图的SIG_DEL是默认动作,SIG_IGN是忽略动作。
    在这里插入图片描述

    总结:
    上面所说的所有信号产生,最终都要有OS来进行执行,为什么?
    因为OS是进程的管理者,所以无论信号如何产生,最终都是通过OS来发送给进程的。

    信号的处理是否是立即处理的?
    在合适的时候(就是由内核态转换为用户态的时候,后面详细说明)

    信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
    信号肯定会被立即记录,记录在进程的PCB中

    一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
    肯定知道。如果不知道的话,进程收到信号难道原地发呆吗?

    如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
    当信号产生后,OS找到对应进程的PCB,向PCB中写入相关的信号数据,将进程PCB中的pending位图相应位置置1,
    如果block位图中的相应信号编号位置是阻塞,则等到解除阻塞后再执行。
    如果不是阻塞,则从handler表(函数指针数组)进行递达。
    递达方式有忽略,默认,自定义。
    忽略直接将pending位图直接置0.
    默认,OS直接执行。
    如果是自定义,由内核态进入用户态,切换成用户身份后来执行相应的代码和数据,然后返回内核态,再返回用户态最终处理完成。

    信号集操作函数

    OS提供系统调用接口,那肯定也提供了相应的数据类型。
    例如sigset_t 是信号集类型,只能由OS提供的系统调用去处理
    #include

    int sigemptyset(sigset_t *set);  //全部置0
    int sigfillset(sigset_t *set);   //全部置1
    int sigaddset (sigset_t *set, int signo);  //相应位置置1
    int sigdelset(sigset_t *set, int signo);   //相应位置置0
    int sigismember(const sigset_t *set, int signo)  //检查相应位置是否为1 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    sigprocmask 和sigpending
    sigprocmask

    来段代码操作一下

    8 void showsigpending(sigset_t* set)
      9 {    
     10     int i = 1;                                         
     11     for(i = 1; i <= 31; i++)
     12     {       
     13         if(sigismember(set,i))  //如果未决存在,则输出1
     14          printf("1");
     15         else       
     16           printf("0");
     17     }
     18     fflush(stdout);
     19     printf("\n");                                                                                                                            
     20 }         
     21  
     22                  
     23 int main()          
     24 {                                              
     25     sigset_t s,p;
     26     sigemptyset(&s);                                           
     27     sigisemptyset(&p);   //这个用来获取未决状态                         
     28 
     29     sigaddset(&s,2); //将s里面信号集中的二号信号设为1  ctrl + c
     30     sigprocmask(SIG_SETMASK,&s,NULL); // 阻塞2号,不关心之前的信号屏蔽字
     31 
     32     while(1)
     33     {
               sigpending(&p);  //获取当前信号的未决 集
     35         showsigpending(&p);
     36         sleep(1);
     37     }
     
     41     return 0;
     42 }
    
       
    
    • 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
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    自定义捕捉详解

    我们分析一下递达方式:自定义捕捉,当我们发送特定信号的时候,去执行用户给的自定义函数。

    要说清这个,需要知道用户态和内核态的区别。
    用户态:只能执行用户的代码,使用用户的数据所处的状态,其中用户以进程为代表(可理解为进程就是用户,但并不准确)
    内核态:只能执行OS的代码和数据,所处的状态。
    之间的根本区别:就是权限问题。 OS是进程的管理者,意味着内核态的权限非常大。
    在这里插入图片描述

    解析:
    1过程,为什么要切换内核态呢? OS是进程的管理者,进程出现异常,肯定由OS发送信号通知。
    3过程为什么要返回用户态去执行呢? 如果不返回在内核态执行用户态的代码这肯定是不允许的,为什么呢?
    内核态的权限非常大,万一来一个 rm -rf / ,OS不就凉凉了吗。
    4过程,执行完后为什么要返回呢?main 和handler不属于调用的关系,完全是两个独立的执行流,所以无法从handler返回到main()
    所以handler()执行完后,只能借助相应的系统调用函数,返回内核后,在从内核返回main的执行流
    在这里插入图片描述

    简单记忆:数学符号无穷大
    在这里插入图片描述

  • 相关阅读:
    关于我用xhtmlrenderer将html转换img结果样式飞了的这档事
    基于SSM实现家政管理平台的开发和实现
    力扣第617题 合并二叉树 c++ 前中后序 完成 附加迭代版本
    ElasticSearch 5.6.3 自定义封装API接口
    嵌入式分享合集114
    如何在智能合约中调用另一个合约的函数
    思科端口安全怎么关闭配置静态地址?
    Linux 开源数据库Mysql-11-mysql集群代理
    HTML VUE
    c++中的string和vector
  • 原文地址:https://blog.csdn.net/CL2426/article/details/127573902