各种服务器

再谈POSIX信号

2021-07-17  本文已影响0人  404Not_Found
# 信号机制
# 信号集
  ## 相关系统调用
  ## 读取未决信号机
# 捕捉信号
  ## 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种信号.足够表示所有信号

  1. 默认处理
  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;
}
上一篇下一篇

猜你喜欢

热点阅读