⟅UNIX网络编程⟆⦔POSIX信号处理

说在前面

信号

  • 信号,告知某个进程发生了某个事件的通知,有时也被称为软件中断。
  • 信号通常是异步发生的,即进程预先不知道信号准确的发生时刻。
  • 信号传递方向
    • 由一个进程发给另一个进程(或自身)
    进程
    进程
    • 内核发给某个进程
    内核
    进程
  • 每个信号都有一个与之关联的处置(disposition),也称为行为(action)。可以通过调用sigaction函数来设定一个信号的处置,且有三种选择。
    1. 定义一个函数,只要有特定信号发生就调用该函数。这样的函数被称为信号处理函数(signal handler),这种行为被称为捕获(catching)信号。
      有两个信号不可以被捕获,即SIGKILLSIGSTOP
      信号处理函数原型如下,参数为信号值,且无返回值。
      void handler(int signo);
      
    2. 可以将某个信号的处置设置为SIG_IGN来忽略(ignore)。SIGKILL和SIGSTOP不可被忽略。
    3. 可以将某个信号的处置设置为SIG_DEF来启用它的默认(default)处置。默认处置通常是在收到信号后终止进程,其中某个信号还在当前工作目录产生一个进程的核心映像。个别信号的默认处置是忽略,如SIGCHLD和SIGURG。

signal函数

  • 建立信号处置的POSIX方法就是调用sigaction函数。但是直接调用比较复杂,因为该函数的参数之一(const struct sigaction *restrict act)是必须分配并填写的结构(体)。
  • 简单的方法是调用signal函数
    #include <signal.h>
    
    void (*signal(int sig, void (*func)(int)))(int);
    
    参数说明:
    • sig
      信号名,例如SIGCHLD。
    • func
      指向函数的指针,或者SIG_IGN或SIG_DEF。
  • 但是,signal函数的出现早于POSIX标准。所以现在可能有多种函数实现,不同的实现提供不同的信号语义(规则),但POSIX明确了调用sigaction函数时的信号语义,所以若使用POSIX,那么signal函数就只能对应POSIX规定的信号语义(因为前面不是说signal函数是建立信号处置的简单方法吗)。所以,在POSIX标准下,signal函数只是对sigaction函数的简单封装。
  • signal函数实现
    在UNIX网络编程一书中,signal函数为作者提供的版本。这里找了一个glibc的版本,两者的实现是差不多的。
    #define __sighandler_t void(*)(int)
    /* Set the handler for the signal SIG to HANDLER,
       returning the old handler, or SIG_ERR on error.  */
    __sighandler_t
    __bsd_signal (int sig, __sighandler_t handler)
    {
      struct sigaction act, oact;
      /* Check signal extents to protect __sigismember.  */
      if (handler == SIG_ERR || sig < 1 || sig >= NSIG
          || __is_internal_signal (sig))
        {
          __set_errno (EINVAL);
          return SIG_ERR;
        }
      act.sa_handler = handler;
      __sigemptyset (&act.sa_mask);
      __sigaddset (&act.sa_mask, sig);
      act.sa_flags = __sigismember (&_sigintr, sig) ? 0 : SA_RESTART;
      if (__sigaction (sig, &act, &oact) < 0)
        return SIG_ERR;
      return oact.sa_handler;
    }
    
    说明:
    • 设置处理函数
      sigacttion结构中的sa_handler成员被置为handler函数

      act.sa_handler = handler;
      
    • 设置处理函数的信号掩码
      POSIX允许指定这样一组信号,它们在信号处理函数被调用期时阻塞。任何阻塞的信号都不能被递交(delivering,或者说发送)给进程。
      这里将sa_mask设置为空,表示在该信号处理函数调用期间不阻塞任何其它信号。
      POSIX保证被捕获的信号在其信号处理函数运行期间总是阻塞的。

      这里所说的阻塞不同于在系统调用(例如read)时的阻塞。(那么被阻塞的信号在被解除阻塞后会不会被递交呢?)

      __sigemptyset (&act.sa_mask);
      
    • 设置SA_RESTART标志
      该标志可选。
      若设置,那么由相应信号中断的系统调用将由内核自动重启。(例如在accept阻塞期间,进程接收到某个信号,系统调用被中断,在处理完成后,accept由内核自动重启)
      若被捕获的信号不是 SIGALRM且SA_RESTART有定义,那么就设置SA_RESTART。
      (若被捕获的信号 SIGALRM且SA_INTERRUPT有定义,那么就设置SA_INTERRUPT。说是早期系统)

    • 调用sigaction函数

      if (__sigaction (sig, &act, &oact) < 0)
      	return SIG_ERR;
      

POSIX信号语义

  • 一旦安装了信号处理函数(即调用了sigaction函数),它便一直被安装着。
  • 在一个信号处理函数运行期间,正被递交的信号是阻塞的。并且,安装处理函数时在传递给sigaction函数的sa_mask信号集中指定的任何额外信号也会被阻塞。
  • 如果一个信号在被阻塞期间产生了一次或多次,那么该信号在被解除阻塞后通常只递交一次,即UNIX信号默认是不排队的。
  • 利用sigprocmask函数选择性地阻塞或者解除阻塞一组信号是可能的。这使得我们在一段临界区代码执行期间,防止捕获某些信号,以此保护这段代码。
发布了106 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_33446100/article/details/103799018