ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【Linux】信号2——信号的保存 [打印本页]

作者: 吴旭华    时间: 2024-7-30 05:35
标题: 【Linux】信号2——信号的保存
 1.信号的保存

   为什么信号要被保存
  
  我们看到的平凡信号编号是1到31的

对于平凡信号而言,对平凡历程而言,本身有还是没有,收到哪个信号
给历程发信号,实际上是历程的task_struct发信号
实际上历程的task_struct里面有一个整数,操纵系统把它当成二进制序列来用,一共32位,每一位代表1个信号,一共31个平凡信号,只用它的31位比特位来一一对应即可
   信号是怎样记录的?
          实际上,当一个历程接收到某种信号后,该信号是被记录在该历程的历程控制块 task_struct 当中的。
        你买了一个快递,于我而言,我当然知道寄来的是什么,而快递员是男是女,多大年龄这并不紧张,紧张的是快递是否到了,里面的东西是否完整无损。以是对历程来说,最紧张的无非就是 “是否有信号” + “是谁”。
        操纵系统提供了 31 个平凡信号,以是我们采用位图来保存信号,也就是说在 task_struct 布局中只要写上一个 unsigned int signals; (00000000 … 00000000) 如许一个字段即可比特位的位置代表是哪一个信号,比特位的内容用 0 1 来代表是否。

此中比特位的位置代表信号的编号,而比特位的内容就代表是否收到对应信号,好比第6个比特位是1就表明收到了6号信号。
   信号是怎样发送的?
          发送信号的本质就是写对应历程 task_struct 信号位图。因为 OS 是系统资源的管理者,以是把数据写到 task_struct 中只有 OS 有资格、有义务。以是,信号是操纵系统发送的,通过修改对应历程的信号位图(0 -> 1)完成信号的发送,再朴素点说就是信号不是 OS 发送的,而是写的。
   为什么是操纵系统来发送信号? 
  操纵系统是历程的管理者,只有它有资格去修改历程的task_struct 
   操纵系统为什么不直接执行信号的内容,而让历程去干?
  操纵系统还要考虑别的历程,可能别的历程更紧张,但是操纵系统不知道,贸然让操纵系统来干,可能会对其他历程产生影响
   信号是怎样产生的?
  
        一个历程收到信号,本质就是该历程内的信号位图被修改了,也就是该历程的数据被修改了,而只有操纵系统才有资格修改历程的数据,因为操纵系统是历程的管理者。也就是说,信号的产生本质上就是操纵系统直接去修改目标历程的task_struct中的信号位图。
   注意: 信号只能由操纵系统发送,但信号发送的方式有多种。
    信号是怎样被处理的
  有3种方法
    1.1. 信号其他相干常见概念

在开始内容之前,先介绍一些信号的专业名词:
1.2.信号在内核的表示

        Linux内核的历程控制块PCB是一个布局体task_struct,除了包含历程id、状态、工作目次、用户id、组id、文件描述符表、还包含了信号相干的信息,紧张指阻塞信号集(下面的block)和未决信号集(下面的pending)。
信号在内核中的表示示意图如下:

        每个信号都有两个标志位分别表示阻塞(block)和未决(pending),另有一个函数指针表示处理动作。信号产生时,内核在历程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
   
    
   
          阻塞信号集,就是对信号进行阻塞或屏蔽设置的一个32位信号屏蔽字,同样每一位对应一个信号,如果某一位设置为1,那么该位对应的信号将被屏蔽,该信号会被延后处理,此时如果信号产生,那么未决信号集中对应的位置1,一直到该信号被解除屏蔽的时间(也就是阻塞信号集中对应位置0),才会去处理该信号,处理完信号,未决信号集中对应位反转回0。
          也叫信号屏蔽字,将某些信号参加聚集,对他们设置屏蔽,当屏蔽某个信号后,如果再收到该信号,该信号的处理将推后至解除屏蔽后。(也就是说,信号一旦被阻塞,就不能递达)
   
          未决信号集就是当进步程未处理的信号的聚集,未决信号集实际上是一个32位数,该字的每一个位对应一个信号,如果该位为1表示信号还未被处理,如果改为置为0,表示信号已经被处理或者没有传递该信号。
     
          好比说我在阻塞信号集中将2号信号为置为1,也就是将2号信号屏蔽,那么未决信号集中2号信号对应的位就会变为1(未决状态),一直阻塞在这种状态。(也就是说,信号一旦被阻塞,就不能递达,一直处于未决状态)
   

          内核通过读取未决信号集来判定信号是否应被处理,信号屏蔽字mask可以影响未决信号集,而我们可以在应用步伐中自定义set来改变mask来达到屏蔽指定信号的目标。
  回到这个图


在上图中,
我们下来让各人见见handler里面的SIG_DFL和SIG_IGN
先来SIG_DFL
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. int main()
  6. {
  7.         signal(2, SIG_DFL); //实施2号信号的默认处理动作       
  8.         while (1){
  9.                 cout<<"I am running…… , pid:"<<getpid()<<endl;
  10.         sleep(1);
  11.         }
  12.         return 0;
  13. }
复制代码

好像跟它原来的差不多
我们再看看
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. int main()
  6. {
  7.         signal(2, SIG_IGN); //忽略2号信号
  8.        
  9.         while (1){
  10.                 cout<<"I am running…… , pid:"<<getpid()<<endl;
  11.         sleep(1);
  12.         }
  13.         return 0;
  14. }
复制代码

我们看到历程不理我的2号信号了
再看看第3种
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void sighandler(int signo)
  6. {
  7.         cout<<"get a signo :"<<signo<<endl;
  8.         exit(1);
  9. }
  10. int main()
  11. {
  12.         signal(2, sighandler); //将2号信号的默认处理动作修改为sighandler方法       
  13.         while (1){
  14.                 cout<<"I am running…… , pid:"<<getpid()<<endl;
  15.         sleep(1);
  16.         }
  17.         return 0;
  18. }
复制代码

   总结一下:
    我们对于信号最焦点的明白就在上面了
1.3.信号集范例——sigset_t

        根据信号在内核中的表示方法,每个信号的未决标志只有一个比特位,非0即1,如果不记录该信号产生了多少次,那么阻塞标志也只有一个比特位。
因此,未决和阻塞标志可以用操纵系统的数据范例sigset_t来存储。
        在我当前的云服务器中,sigset_t范例的定义如下:(不同操纵系统实现sigset_t的方案可能不同)
  1. #define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
  2. typedef struct
  3. {
  4.         unsigned long int __val[_SIGSET_NWORDS];
  5. } __sigset_t;
  6. typedef __sigset_t sigset_t;
复制代码
sigset_t称为信号集,这个范例可以表示每个信号的“有效”或“无效”状态。
阻塞信号集也叫做当进步程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该明白为阻塞而不是忽略。
1.4.信号集操纵函数

        sigset_t范例对于每种信号用一个bit表示“有效”或“无效”,至于这个范例内部怎样存储这些bit则依赖于系统的实现,从利用者的角度是不必关心的,利用者只能调用以下函数来操纵sigset_t变量,而不应该对它的内部数据做任何解释,好比用printf直接打印sigset_t变量是没故意义的。
  1. #include <signal.h>
  2. typedef unsigned long sigset_t; /*信号集类型,其实就是一个32位的字*/
  3. /*清空信号集,将某个信号集全部清0*/
  4. int sigemptyset(sigset_t *set);
  5. /*填充信号集,将某个信号集全部置1*/
  6. int sigfillset(sigset_t *set);
  7. /*将某个信号signum加入信号集set*/
  8. int sigaddset(sigset_t *set, int signum);
  9. /*将某个信号清出信号集,从信号集ste中删除信号signum,(其实就是本来某个屏蔽信号字中置1的位清0)*/
  10. int sigdelset(sigset_t *set, int signum);
  11. /*判断某个信号是否在信号集中,
  12. 返回值 在集合:1;不在:0;出错:-1 (其余四个函数成功返回0,失败返回-1)*/
  13. int sigismember(const sigset_t *set, int signum);
复制代码
函数解释:

注意: 在利用sigset_t范例的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号处于确定的状态。
例如,我们可以按照如下方式利用这些函数。
  1. #include <stdio.h>
  2. #include <signal.h>
  3. int main()
  4. {
  5.         sigset_t s; //用户空间定义的变量
  6.         sigemptyset(&s);
  7.         sigfillset(&s);
  8.         sigaddset(&s, SIGINT);
  9.         sigdelset(&s, SIGINT);
  10.         sigismember(&s, SIGINT);
  11.         return 0;
  12. }
复制代码
注意: 代码中定义的sigset_t范例的变量s,与我们平常定义的变量一样都是在用户空间定义的变量,以是背面我们用信号集操纵函数对变量s的操纵实际上只是对用户空间的变量s做了修改,并不会影响历程的任何行为。
        因此,我们还需要通过系统调用,才能将变量s的数据设置进操纵系统。
阻塞信号集——sigprocmask

        sigprocmask函数可以用于读取或更改历程的信号屏蔽字(阻塞信号集,也就是block),该函数的函数原型如下:

设置阻塞或解除阻塞信号集,用来屏蔽信号或解除屏蔽,其本质是读取或修改历程的PCB中的信号屏蔽字。
需要注意的是,屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。
函数参数
how:
假设当前的信号屏蔽字(阻塞信号集,也就是block)为mask

set:传入参数,是一个位图(32位),set中哪位置1,就表示当进步程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集(阻塞信号集,也就是block),可用于规复上次设置。
   参数阐明:
  
  假设当前的信号屏蔽字(阻塞信号集,也就是block)为mask,下表阐明了how参数的可选值及其含义:
  

  返回值阐明:

   注意: 如果调用sigprocmask解除了对当前多少个未决信号的阻塞,则在sigprocmask函数返回前,至少将此中一个信号递达。
  
未决信号集——sigpending

sigpending函数可以用于读取历程的未决信号集,该函数的函数原型如下:

sigpending函数读取当进步程的未决信号集,并通过set参数传出,set是个传出参数。和上面的oldest参数是一样的
该函数调用成功返回0,出错返回-1。
1.5.利用例子

好了,接口讲到这里,我们接下来来利用一下
老例子,先复习makefile

实行步调如下:
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void printPending(sigset_t *pending)
  6. {
  7.         int i = 1;
  8.         for (i = 1; i <= 31; i++){
  9.                 if (sigismember(pending, i)){
  10.                         printf("1 ");
  11.                 }
  12.                 else{
  13.                         printf("0 ");
  14.                 }
  15.         }
  16.         printf("\n");
  17. }
  18. int main()
  19. {
  20.         //1.对2号信号进行屏蔽
  21.        
  22.         //1.1.准备好我们要传进去的block信号集
  23.         sigset_t blockset;//创建一个要传入的block信号集
  24.         sigemptyset(&blockset);//清空要传入的block信号集
  25.         sigaddset(&blockset,2);//将要传入的block信号集的2号信号位置于1
  26.        
  27.         //1.2.将准备好的block信号集通过系统调用接口传进进程
  28.         sigset_t oldblockset;//创建1个block信号集来存储当前进程旧的block信号集
  29.         sigemptyset(&oldblockset);//清空block信号集,准备存储当前进程旧的block信号集
  30.         sigprocmask(SIG_SETMASK,&blockset,&oldblockset);//更换block信号集合
  31.         //至此我们已经把2号信号屏蔽了
  32.         //2.重复打印当前进程的未决信号集
  33.         sigset_t pending;
  34.         while(1)
  35.         {
  36.                 //2.1.获取
  37.         int n=sigpending(&pending);
  38.         if(n<0) continue;
  39.         //2.2.打印pending
  40.         printPending(&pending);
  41.         sleep(1);
  42.         }
  43.        
  44. }
复制代码
        可以看到,步伐刚刚运行时,因为没有收到任何信号,以是此时该历程的pending表一直是全0,而当我们利用kill下令向该历程发送2号信号后,由于2号信号是阻塞的,因此2号信号一直处于未决状态,以是我们看到pending表中的第二个数字一直是1。

        为了看到2号信号递达后pending表的变革,我们可以设置一段时间后,自动解除2号信号的阻塞状态,解除2号信号的阻塞状态后2号信号就会立即被递达。因为2号信号的默认处理动作是终止历程,以是为了看到2号信号递达后的pending表,我们可以将2号信号进行捕捉,让2号信号递达时执行我们所给的自定义动作。
  1. #include<iostream>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. using namespace std;
  5. void printPending(sigset_t *pending)
  6. {
  7.         int i = 1;
  8.         for (i = 1; i <= 31; i++){
  9.                 if (sigismember(pending, i)){
  10.                         printf("1 ");
  11.                 }
  12.                 else{
  13.                         printf("0 ");
  14.                 }
  15.         }
  16.         printf("\n");
  17. }
  18. void handler(int signo)
  19. {
  20.         printf("handler signo:%d\n", signo);
  21. }
  22. int main()
  23. {
  24.         signal(2, handler);//更改2号信号的动作
  25.        
  26.        
  27.         //1.对2号信号进行屏蔽
  28.        
  29.         //1.1.准备好我们要传进去的block信号集
  30.         sigset_t blockset;//创建一个要传入的block信号集
  31.         sigemptyset(&blockset);//清空要传入的block信号集
  32.         sigaddset(&blockset,2);//将要传入的block信号集的2号信号位置于1
  33.        
  34.         //1.2.将准备好的block信号集通过系统调用接口传进进程
  35.         sigset_t oldblockset;//创建1个block信号集来存储当前进程旧的block信号集
  36.         sigemptyset(&oldblockset);//清空block信号集,准备存储当前进程旧的block信号集
  37.         sigprocmask(SIG_SETMASK,&blockset,&oldblockset);//更换block信号集合
  38.         //至此我们已经把2号信号屏蔽了
  39.         //2.重复打印当前进程的未决信号集
  40.         sigset_t pending;
  41.         int cnt=0;
  42.         while(1)
  43.         {
  44.                 //2.1.获取
  45.         int n=sigpending(&pending);
  46.         if(n<0) continue;
  47.         //2.2.打印pending
  48.         printPending(&pending);
  49.         sleep(1);
  50.         cnt++;
  51.                 if (cnt == 20){
  52.                         sigprocmask(2, &oldblockset, NULL); //恢复曾经的信号屏蔽字
  53.                         printf("恢复信号屏蔽字\n");
  54.                 }
  55.         }
  56.        
  57. }
复制代码
         此时就可以看到,历程收到2号信号后,该信号在一段时间内处于未决状态,当解除2号信号的屏蔽后,2号信号就会立即递达,执行我们所给的自定义动作,而此时的pending表也变回了全0。

        细节: 在解除2号信号后,2号信号的自定义动作是在打印“规复信号屏蔽字”之前执行的。因为如果调用sigprocmask解除对当前多少个未决信号的阻塞,则在sigprocmask函数返回前,至少将此中一个信号递达。
   我们可以通过上面这个操纵屏蔽全部信号吗?
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4