再谈POSIX信号

作者: 404Not_Found | 来源:发表于2021-07-17 18:19 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20210718S 20:37
    • 目的:信号集、未决信号集,信号屏蔽字等
    # 信号机制
    # 信号集
      ## 相关系统调用
      ## 读取未决信号机
    # 捕捉信号
      ## sigact.sa_mask 的作用
    # 系统调用函数被中断
    # raise() 自己给自己发信号
    # alarm
    

    信号机制

    Linux的信号也成为软中断,是软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理起收到一个中断请求来说是一样的。

    信号是异步的,一个进程不必通过任何操作来等待信号的到达。 信号是进程间通信异步通信机制。

    1. 硬件信号
      除0, 段错误等
    2. 软件来源
      kill rais alarm cll + c 等

    kill -l 可以查看所有信号,只了解了 前32个 从unix继承来的经典信号。

    信号集

    信号集故名思意是 信号的集合, 信号集的类型为sigset_t, 每一种信号用1bit 来表示。
    sizeof(sigset_t) = 8 ,即8个字节,64bit. 表示了 64种信号.足够表示所有信号

    • pending 未决信号集 -- sigset_t
      信号进来,在未决信号集中,将自己对应的信号bit置为1
      但实际上并没有对信号进行处理。

    • 信号屏蔽字 -- sigset_t
      是否让信号进来
      0 : 允许
      1 : 不允许此信号进来,忽略 此信号

    • 信号的抵达

    1. 默认处理
    • TERM
    • CORE
    • IGNORE
    • STOP 挂起
    • CONTINUE 继续S
    1. 忽略
    2. 捕捉

    前32位经典信号:未决信号集并不支持排队。
    如果同时来了很多个 相同的型好,Ctrl+C, 未决信号集只会把对应的bit置为1, 只会把所有的ctrl+c 当作一次处理
    后32位经典信号:支持排队

    用户无法修改 未决信号集,只有内核可以修改
    用户可以修改信号屏蔽字

    相关系统调用

    信号机制的流程.png

    信号处理函数

    int sigemtyset(sigset_t * set); //将set 每一位置为0
    int sigfillset(sigset_t * set) ; //将set 每一位置为1
    int sigaddset(sigset_t *set, int signo); signo信号对应的位置1
    int sigdelset(sigset_t *set, int signo); signo信号对于国内位置0
    int sigismember(const sigset_t *set, int signo); //怕段set中signo信号对应的bit是否位1,返回 1 or 0
    
    int sigprocmask(int how, const sigset_t * set, sigset *oset);
    /* how: 
      SIG_BLOCK set 包含了我们希望添加到当前信号屏蔽字的信号, 相当于 mast = mask|set
      SIG_UNBLOCK set 包含了我们希望从当前信号屏蔽字中接触阻塞的信号, 相当于 mast = mask&~set,取反求与操作
      SIG_SETMASK 相当于当前信号屏蔽字位set 所指向的值,相当于 mask=set
    set: 传入参数
    oset: 传出参数,原来的信号屏蔽字,一般可以填0
    return: 成功返回0 , 否则出错则位-1
    */
    

    通过上述系统调用可以看出来,默认的信号屏蔽字全位0, 通过我们手动的创建的set 去修改原来默认的信号屏蔽字。

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    
    int main(int argc, char **argv) {
      sigset_t mask_set;
      sigemptyset(&mask_set);
      sigaddset(&mask_set, SIGINT);
      sigaddset(&mask_set, SIGQUIT);
    
      sigprockask(SIGBLOCK, &mask_set, NULL);
    
      while(1)
        sleep(1);
      return 0;
    }
    

    直接在信号屏蔽字阻塞掉,不会走到后续的信号处理函数。

    kill -9 与 kill -7 不能被阻塞 不能被捕捉,不能被忽略略
    挂起,以免某些进程一直在竞争CPU

    读取未决信号集

    读取当前进程的未决信号集

    #include <signal.h>
    int sigpending(sigset_t *set);
    /*
      通过set 参数传出,调用成功则返回0 否则返回
    */
    
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    
    void print_set(sigset_t set) {
      int i = 0;
      for(int i = 1; i<32; i++)
         if(sigismember(&set, i))
            putchar('1');
          else
            putchar('0');
      putchar('\n');
    }
    
    int main(int argc, char **argv) {
      sigset_t mask_set, pending_set;
      sigemptyset(&mask_set);
      sigaddset(&mask_set, SIGINT);
      sigaddset(&mask_set, SIGQUIT);
    
      sigprockmask(SIGBLOCK, &mask_set, NULL);
    
      while(1) {
        sigpending(&pending_set);
        print_set(pending_set);
        sleep(1);
      }
      return 0;
    }
    

    有意思的测试:

    int count =0;
    while(1) {
      sigpending(&pending_set);
      print_set(pending_set);
      if(count ++ = 10)
        sigprocmask(SIG_UNBLOCK, &mark_set, NULL);
      sleep(1);
    }
    

    在10s之前 我连续输入多次 ctrl +c,此时ctrl+c 一直在未决信号集中,此时信号屏蔽字一直将信号 SIGINT 阻塞。
    当过了10s后 清除信号屏蔽字,则开始响应 ctrl+c。仅仅响应一次S

    捕捉信号

    #include <signal.h>
    
    int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);
    
    struct sigaction {
      void (*sa_handler) (int);
      void (*sa_sigaction) (int, siginfo_t *, void *);
      sigset_t sa_mask;
      int sa_flags;
      void (*sa_restorer)(void);
    }
    
    /*
      sa_handler : 早期的捕捉函数
      sa_sigaction: 新增加的捕捉函数,可以传参,和 sa_handler 互斥,两者通过sa_flags选择哪种捕捉函数
      sa_mask:执行捕捉函数时, 设置阻塞其他信号,sa_mask|进程阻塞信号集,退出捕捉函数后,还原回原有的阻塞信号集
      sa_flags: 值为SA_SIGINFO时,用sa_sigacton, 当未指定值时用sa_handler, SA_RESTART 让被打断的系统调用重新开始
      sa_restorer: 保留,已过时
    */
    
    #Include <signal.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    void sig_handler(int sig) {
      printf("sig_handler %d\n", sig);
    }
    
    int main(int argc, char **argv) {
      struct sigaction sigact;
      sigact.sa_flags = 0;
      sigact.sa_handler = sig_handler;
      sigemptyset(&sigact.sa_mask);
      
      sigaction(SIGINT, &sigact, NULL);
    
      while(1) 
        sleep(1);
      return 0;
    }
    
    

    sigact.sa_maks 的作用

    1. 中断信号来了, 进入中断函数1
    2. 此时如果相同的中断信号进入,则一直在未决信号集排队,等到信号处理函数结束后,响应
    3. 如果来的是不同的中断信号,则在中断函数1中,会被打断,响应新的信号。

    如果在中断函数1中, 不想响应指定的新的信号。 则可以用到,sa_mask

    #Include <signal.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    void sig_handler(int sig) {
      printf("sig_handler %d\n", sig);
      sleep(5);
      pirntf("sig_handler end\n");
    }
    
    int main(int argc, char **argv) {
      struct sigaction sigact;
      sigact.sa_flags = 0;
      sigact.sa_handler = sig_handler;
      sigemptyset(&sigact.sa_mask);
      sigaddset(&sigact.sa_mask, SIGQUIT);
      
      sigaction(SIGINT, &sigact, NULL);
    
      while(1) 
        sleep(1);
      return 0;
    }
    

    只是在中断函数执行的过程中忽略响应信号,中断函数1结束后,会继续响应信号.

    sigaddset(&sigact.sa_mask, SIGQUIT);
    sigaction(SIGINT, &sigact,NULL) //相当于增加信号的一种属性啦
    vs.
    sigaddset(&mask_set, SIGQUIT);
    sigprockmask(SIGBLOCK, &mask_set, NULL);

    1. 信号屏蔽字在进程中永远有效
    2. sigaction.sa_mask 只在中断函数中有效,中断函数结束后就没用啦。

    系统调用函数被中断

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    void sig_handler(int sig) {
      printf("sig_handler %d\n", sig);
      sleep(5);
      printf("sig_handler end\n");
    }
    
    int main(int argc, char ** argv) {
      char buf[32];
      int ret;
      struct sigaction sigact;
      sigact.sa_flags = SA_RESTART;
      sigact.sa_hanlder = sig_handler;
      sigemptyset(&sigact.sa_mask);
      sigaddset(&sigact.sa_mask, SIGQUIT);
      sigaction(SIGINT, &sigact, NULL);
      
    
      ret = read(STDIN_FILENO, buf, sizeof(buf));
      if(ret == -1) {
        if(errno = EINTR)
          perror("read");
        else
           exit(1);
      }
    }
    

    read 系统调用被信号打断,可以设置sigaction 的 sa_flags, 就可以重新执行系统调用了。

    另外的处理方式:系统调用被打断,重新读buffer

    size_t readn(int fd, void * usrbuf, size_t n) {
      size_t nleft = n;
      ssize_t nread;
      char * bufp = usrbuf;
      
      while(nleft > 0) {
        if(nread = read(fd, bufp, nleft)) == -1)
        {
          if(errno == EINTER) { /*中断继续*/ contnue; }
          else {return -1} /*error*/
        }
        else if( nread == 0 ) {break; /*督导文件末尾EOF*/
      else
      {
        nleft -= nread;
        bufp += nread;
      }
      }
      return (n-nleft);
    }
    

    raise() 自己给自己发信号

    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    
    void handle_sig(int sig) {
      if(sig == SIGBUS)
      {
        printf("get SIGBUS\n");
      }
    }
    
    int main(int argc, char ** argv) {
      signal(SIGBUS, handle_sig);
      raise(SIGBUS);
      return 0;
    }
    

    alarm() && setitimer()

    定时器函数,alarm() 专门为SIGALRM 信号设计
    alarm 的默认动作是 TERM

    #include <signal.h>
    #include <time.h>
    #include <stdio.h>
    #include <unistd.h>
    #Include <fcntl.h>
    void sig_handler(int sig) {
      printf("signal%d\n", sig);
      alarm(5);
    }
    
    int main(int argc, char ** argv) {
      signal(SIGALRM, sig_handler);
      alarm(5);
      while(1) {
        puts("helloworld\n");
        sleep(1);
      }
    }
    

    现象: 每间隔5s 打印一次 signal 14

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <time.h>
    void sig_handler(int sig) {
      printf("signal%d\n", sig);
      alarm(5);
    }
    
    int main(int argc, char ** argv) {
      signal(SIGALARM, handle_sig);
      struct itimerval timval;
      timval.it_interval.tv_sec = 1;
      timval.it_interval.tv_usec = 0;
      
      timval.it_value.tv_sec = 5; //第一次触发时间
      timval.it_value.tv_usec = 0;
      
      setitimer(ITIMER_REAL, &timval, NULL);
      while(1)
      {
        sleep(1);
      }
      return 0;
    }
    

    相关文章

      网友评论

        本文标题:再谈POSIX信号

        本文链接:https://www.haomeiwen.com/subject/riikultx.html