022 UNIX再学习 -- 信号处理

纵饮孤独 提交于 2019-12-19 02:15:07
                                          
                                                                                   
                                                                                
                                           

一、闹钟和睡眠

1、函数 alarm

  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
  3. 返回值:返回 0 或先前所设闹钟的剩余秒数

(1)函数功能

使用 alarm 函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生 SIGALRM 信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该 alarm 函数的进程。

(2)参数解析

参数 seconds 的值是产生信号 SIGALRM 需要经过的时钟秒数。当这个时刻到达时,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一个时间间隔。

(3)函数解析

每个进程只能有一个闹钟时间。如果在调用 alarm 时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余值作为本次 alarm 函数调用的值返回。以前注册的闹钟时间则被新值代替。
如果有以前注册的尚未超过的闹钟时间,而且本次调用的 seconds 值是 0,则取消以前的闹钟时间,其余留值仍作为 alarm 函数的返回值。
虽然 SIGALRM 的默认动作是终止进程,但是大多数使用闹钟的进程捕捉此信号。如果此时进程要终止,则在终止之前它可以执行所需的清理操作。如果我们想捕捉 SIGALRM 信号,则必须在调用 alarm 之前安装该信号的处理程序。如果我们先调用 alarm,然后在我们能够安装 SIGALRM 处理程序之前已接到该信号,那么进程将终止。

(4)示例说明

  1. //示例一
  2. //alarm函数的使用
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <sys/types.h>
  8. void fa(int signo)
  9. {
  10.  printf("捕获到了信号%d\n",signo);
  11.  //设置1秒后发送信号
  12.  alarm(1);
  13. }
  14. int main(void)
  15. {
  16.  //设置SIGALRM信号进行自定义处理
  17.  signal(SIGALRM,fa);
  18.  //设置5秒后发送SIGALRM信号
  19.  int res = alarm(5);
  20.  printf("res = %d\n",res);//0
  21.  sleep(2);
  22.  //重新修改为10秒后发送SIGALRM信号
  23.  res = alarm(10);
  24.  printf("res = %d\n",res); //3
  25.  
  26.  /*
  27.  sleep(3);
  28.  //重新设置为0秒后,取消之前的闹钟
  29.  res = alarm(0);
  30.  printf("res = %d\n",res); //7
  31.  */
  32.  while(1);
  33.  return 0;
  34. }
  35. 输出结果:
  36. res = 0
  37. res = 3
  38. 捕获到了信号 14
  39. 捕获到了信号 14
  40. 捕获到了信号 14
  41. 捕获到了信号 14
  42. 捕获到了信号 14
  43. 捕获到了信号 14
  44. 捕获到了信号 14
  45. 捕获到了信号 14
  1. //示例二
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. void sigalrm (int signum)
  8. {
  9.  time_t t = time (NULL);
  10.  struct tm *it = localtime (&t);
  11.  printf ("\n%02d:%02d:%02d  按回车键退出\n", it->tm_hour, it->tm_min, it->tm_sec);
  12.  alarm (1);
  13. }
  14. int main (void)
  15. {
  16.  if (signal (SIGALRM, sigalrm) == SIG_ERR)
  17.   perror ("signal"), exit (1);
  18.  sigalrm (SIGALRM);
  19.  getchar ();
  20.  return 0;
  21. }
  22. 输出结果:
  23. 14:07:37  按回车键退出
  24. 14:07:38  按回车键退出
  25. 14:07:39  按回车键退出
  26. 14:07:40  按回车键退出

(5)示例解析

示例一,alarm 设置 5 秒结束后,发送 SIGALRM 信号,如果闹钟时间没有结束,又设了闹钟,则返回剩余闹钟秒数。alarm 参数为 0,则取消闹钟。
示例二,sigalrm (SIGARLM);函数运行,执行到 alarm (1); 产生 SIGALRM 信号,触发 signal。然后就开始循环。

2、函数 pause

  1. #include <unistd.h>
  2. int pause(void);
  3. 成功阻塞,失败返回 -1

(1)函数功能

pause 函数使调用进程挂起直至捕捉到一个信号。(无限睡眠)

(2)函数解析

该函数使调用 进程/线程 进入无时限的睡眠状态,直到有信号终止了调用进程或被其捕获。
如果有信号被调用进程捕获,在信号处理函数返回以后,pause 函数才会返回,且返回值为 -1,同时置 errno 为 EINTR,表示阻塞的系统调用被信号中断。
pause 函数要么不返回,要么返回失败,不会返回成功。

(3)示例说明

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <errno.h>
  6. void sigint (int signo)
  7. {
  8.  printf ("\n中断符号被发送\n");
  9. }
  10. int main (void)
  11. {
  12.  if (signal (SIGINT, sigint) == SIG_ERR)
  13.   perror ("signal"), exit (1);
  14.  printf ("按 ctrl+c 继续\n");
  15.  if (pause () != -1 && errno != EINTR)
  16.   perror ("pause"), exit (1);
  17.  printf ("进程继续\n");
  18.  return 0;
  19. }
  20. 输出结果:
  21. 按 ctrl+c 继续
  22. ^C
  23. 中断符号被发送
  24. 进程继续

(4)示例解析

当有信号被当前进程捕获,在信号处理函数返回以后,pause函数返回,且返回值为-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断。

3、函数 sleep

  1. #include <unistd.h>
  2. unsigned int sleep(unsigned int seconds);
  3. 返回 0 或剩余秒数

(1)函数功能

有限睡眠

(2)参数解析

参数 seconds 以秒为单位的睡眠时限。 

(3)函数解析

该函数使调用进程/线程睡眠 seconds 秒,除非有信号终止了调用进程或被其捕获
如果有信号被调用进程捕获,在信号处理函数返回以后,sleep 函数才会返回,且返回值为剩余的秒数,否则该函数将返回 0,表示睡眠充足。如果由于捕获到某个信号 sleep 提早返回时,返回值是未休眠完的秒数(所要求的时间减去实际休眠时间)。

(4)示例说明

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <errno.h>
  6. void sigint (int signo)
  7. {
  8.  printf ("\n中断符号被发送\n");
  9. }
  10. int main (void)
  11. {
  12.  if (signal (SIGINT, sigint) == SIG_ERR)
  13.   perror ("signal"), exit (1);
  14.  printf ("按 ctrl+c 继续\n");
  15.  int res = sleep (60);
  16.  if (res)
  17.   printf ("进程被提前%d秒叫醒\n", res);
  18.  printf ("进程继续\n");
  19.  return 0;
  20. }
  21. 输出结果:
  22. 按 ctrl+c 继续
  23. ^C
  24. 中断符号被发送
  25. 进程被提前57秒叫醒
  26. 进程继续

(5)sleep 函数简单实现

  1. #include "apue.h"
  2. static void
  3. sig_alrm(int signo)
  4. {
  5.  /* nothing to do, just returning wakes up sigsuspend() */
  6. }
  7. unsigned int
  8. my_sleep(unsigned int seconds)
  9. {
  10.  struct sigaction newact, oldact;
  11.  sigset_t   newmask, oldmask, suspmask;
  12.  unsigned int  unslept;
  13.  /* set our handler, save previous information */
  14.  newact.sa_handler = sig_alrm;
  15.  sigemptyset(&newact.sa_mask);
  16.  newact.sa_flags = 0;
  17.  sigaction(SIGALRM, &newact, &oldact);
  18.  /* block SIGALRM and save current signal mask */
  19.  sigemptyset(&newmask);
  20.  sigaddset(&newmask, SIGALRM);
  21.  sigprocmask(SIG_BLOCK, &newmask, &oldmask);
  22.  alarm(seconds);
  23.  suspmask = oldmask;
  24.  /* make sure SIGALRM isn't blocked */
  25.  sigdelset(&suspmask, SIGALRM);
  26.  /* wait for any signal to be caught */
  27.  sigsuspend(&suspmask);
  28.  /* some signal has been caught, SIGALRM is now blocked */
  29.  unslept = alarm(0);
  30.  /* reset previous action */
  31.  sigaction(SIGALRM, &oldact, NULL);
  32.  /* reset signal mask, which unblocks SIGALRM */
  33.  sigprocmask(SIG_SETMASK, &oldmask, NULL);
  34.  return(unslept);
  35. }
  36. int main (void)
  37. {
  38.  my_sleep (10);
  39.  printf ("hello world!\n");
  40.  return 0;
  41. }
  42. 输出结果:
  43. hello world!

4、函数 nanosleep

  1. #include <time.h>
  2. int nanosleep(const struct timespec *req, struct timespec *rem);
  3. 返回值:若休眠到要求的时间,返回 0,若出错,返回 -1
  1. struct timespec
  2. {
  3.     time_t  tv_sec;         /* seconds */
  4.     long    tv_nsec;        /* nanoseconds */
  5. };

(1)函数功能

nanosleep 函数与 sleep 函数类似,但是提供了纳秒级的精度

(2)参数解析

这个函数挂起调用进程,直到要求的时间已经超时或者某个信号中断了该函数。
req 参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,rem 参数指向的 timespec 结构就会被设置为未休眠完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数置为 NULL。

如果系统并不支持纳秒这一精度,要求的时间就会取整。因为,nanosleep 函数并不涉及产生任何信号,所以不需要担心与其函数的交互。

(3)示例说明

  1. #include <stdio.h>
  2. #include <time.h>
  3. #include <stdlib.h>
  4. #include <sys/time.h> 
  5. int main (void)
  6. {
  7.  struct timespec req, rem;
  8.  rem.tv_sec = 0
  9.  rem.tv_nsec = 0
  10.  req.tv_sec = 0
  11.  req.tv_nsec = 1000000
  12.  int res = 0;
  13.  struct timeval start,end;     
  14.  gettimeofday( &start, NULL );  /*测试起始时间*/   
  15.  if (res = (nanosleep (&req, &rem)) == -1)
  16.   perror ("nanosleep"), exit (1);
  17.  gettimeofday( &end, NULL );   /*测试终止时间*/     
  18.  int timeuse = (end.tv_usec - start.tv_usec);     
  19.  printf("运行时间为:%d us\n",timeuse);     
  20.  return 0;
  21. }
  22. 输出结果:
  23. 运行时间为:1173 us

5、sleep、usleep 和 nanosleep 区别

时间单位还有:秒(s)、毫秒(ms)、微秒 (μs)、纳秒(ns)、皮秒(ps)、飞秒(fs)、阿秒、渺秒     
usleep 和 sleep 区别在于精度,ulseep 为 微妙级,sleep 为秒级。
sleep() 和 nanosleep() 都是使进程睡眠一段时间后被唤醒,但是二者的实现完全不同。

Linux 中并没有提供系统调用 sleep(),sleep() 是在库函数中实现的,它是通过调用 alarm() 来设定报警时间,调用sigsuspend() 将进程挂起在信号 SIGALARM 上。
nanosleep() 则是 Linux中 的系统调用
,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个 timer_list 型定时器,time_list 结构里包括唤醒时间以及唤醒后执行的函数,通过 nanosleep() 加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用 schedule() 函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以 nanosleep() 精度也不是很高。

二、信号集

1、什么是信号集

顾名思义,多个信号组成的信号集合谓之信号集。
系统内核用 sigset_t 类型表示信号集。sigset_t 类型是一个结构体,但该结构体中只有一个成员,是一个包含 32 个元素的整数数组。
 /usr/include/i386-linux-gnu/bits/sigset.h 中有如下类型定义:
  1. /* A `sigset_t' has a bit for each signal.  */
  2. # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
  3. typedef struct
  4.   {
  5.     unsigned long int __val[_SIGSET_NWORDS];
  6.   } __sigset_t;
可以把 sigset_t 类型看成一个由 1024 个二进制位组成的大整数。
其中的每一位对应一个信号,其实目前远没有那么多信号。某位为 1 就表示信号集中有此信号,反之为 0 就是无此信号。当需要同时操作多个信号时,常以 sigset_t 作为函数的参数或返回值的类型。

2、信号集函数

  1. #include <signal.h>
  2. int sigemptyset(sigset_t *set);
  3. int sigfillset(sigset_t *set);
  4. int sigaddset(sigset_t *set, int signum);
  5. int sigdelset(sigset_t *set, int signum);
  6. 4 个函数返回值:若成功,返回 0,;若出错,返回 -1
  7. int sigismember(const sigset_t *set, int signum);
  8. 返回值:若真,返回 1;若假,返回 0

(1)函数 sigemptyset

清空信号集,即将信号集的全部信号位清 0.
例如:
  1. sigset_t sigset;
  2. if (sigemptyset (&sigset) == -1)
  3.     perror ("sigemptyset"), exit (1);

(2)函数 sigfillset

填满信号集,即将信号集的全部信号位置 1.
例如:
  1. sigset_t sigset;
  2. if (sigfillset (&sigset) == -1)
  3.     perror ("sigfillset"), exit (1);

(3)函数 sigaddset

加入信号,即将信号集中与指定信号编号对应的信号位置 1.
例如:
  1. if (sigaddset (&sigset, SIGINT))
  2.     perror ("sigaddset"), exit (1);

(4)函数 sigdelset

删除信号,即将信号集中与指定信号编号对应的信号位清 0.
例如:
  1. if (sigdelest (&sigset, SIGINT))
  2.     perror ("sigdelset"), exit (1);

(5)函数 sigismember

判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号位是否为 1.
例如:
  1. if (sigismember (&sigset, SIGINT) == 1)
  2.     printf ("信号集中有 SIGINT 信号\n");

(6)示例说明

  1. //信号集的操作
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int main(void)
  7. {
  8.  sigset_t set;
  9.  printf("set = %lu\n",set);
  10.  printf("sizeof(sigset_t) = %d\n",sizeof(sigset_t));
  11.  //清空信号集
  12.  sigemptyset(&set);
  13.  printf("set = %lu\n",set);//0
  14.  //增加信号到信号集中
  15.  sigaddset(&set,2);
  16.  // 0000 0010 => 1*2 = 2
  17.  printf("set = %lu\n",set);//2
  18.  sigaddset(&set,3);
  19.  // 0000 0110 => 4+2 = 6
  20.  printf("set = %lu\n",set);//6
  21.  sigaddset(&set,7);
  22.  // 0100 0110 => 64+4+2 = 70
  23.  printf("set = %lu\n",set);//70
  24.  //从信号集中删除信号3
  25.  sigdelset(&set,3);
  26.  // 0100 0010 => 64 + 2 = 66
  27.  printf("set = %lu\n",set);//66
  28.  //判断信号是否存在
  29.  if(sigismember(&set,2))
  30.  {
  31.   printf("信号2存在\n");
  32.  }
  33.  if(sigismember(&set,3))
  34.  {
  35.   printf("信号3存在\n");
  36.  }
  37.  if(sigismember(&set,7))
  38.  {
  39.   printf("信号7存在\n");
  40.  }
  41.  //填满信号集
  42.  sigfillset(&set);
  43.  printf("set = %lu\n",set);//很大的数
  44.  return 0;
  45. }
  46. 输出结果:
  47. set = -1078591106
  48. sizeof(sigset_t) = 128
  49. set = 0
  50. set = 2
  51. set = 6
  52. set = 70
  53. set = 66
  54. 信号2存在
  55. 信号7存在
  56. set = 2147483647

3、信号集函数实现 (了解)

  1. #include <signal.h>
  2. #include <errno.h>
  3. /*
  4. * <signal.h> usually defines NSIG to include signal number 0.
  5. */
  6. #define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG)
  7. int
  8. sigaddset(sigset_t *set, int signo)
  9. {
  10.  if (SIGBAD(signo)) {
  11.   errno = EINVAL;
  12.   return(-1);
  13.  }
  14.  *set |= 1 << (signo - 1);  /* turn bit on */
  15.  return(0);
  16. }
  17. int
  18. sigdelset(sigset_t *set, int signo)
  19. {
  20.  if (SIGBAD(signo)) {
  21.   errno = EINVAL;
  22.   return(-1);
  23.  }
  24.  *set &= ~(1 << (signo - 1)); /* turn bit off */
  25.  return(0);
  26. }
  27. int
  28. sigismember(const sigset_t *set, int signo)
  29. {
  30.  if (SIGBAD(signo)) {
  31.   errno = EINVAL;
  32.   return(-1);
  33.  }
  34.  return((*set & (1 << (signo - 1))) != 0);
  35. }

三、信号屏蔽

1、递送、未决与掩码

当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程就叫做递送
信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号状态称为未决
每个进程都有一个信号掩码,它实际上是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给相应的进程,而是会被阻塞在未决状态。
在信号处理函数执行期间,这个正在被处理的信号总是处于信号掩码中,如果又有该信号产生,则会被阻塞,直到上一个针对该信号的处理过程结束以后才会被递送。
当进程正在执行类似更新数据库这样的敏感任务时,可能不希望被某些信号中断。这时可以通过信号掩码暂时屏蔽而非忽略掉这些信号,使其一旦产生即被阻塞于未决状态,待特定任务完成后,再回过头来处理这些信号。

2、设置掩码与检测未决

(1)设置调用进程的信号掩码

  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  3. 返回值:若成功,返回 0;若出错,返回 -1

1》》参数解析

参数 how 为修改信号掩码的方式,可取以下值
    SIG_BLOCK        将 sigset 中的信号加入当前信号掩码
    SIG_UNBLOCK  从当前信号掩码中删除 sigset 中的信号
    SIG_SETMASK   把 sigset 设置成当前信号掩码
参数 sigset 为信号集,取 NULL 则忽略此参数
参数 oldset 为输出原信号掩码,取 NULL 则忽略此参数

2》》示例说明

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7.     sigset_t sigset;
  8.     sigemptyset (&sigset);
  9.     sigaddset (&sigset, SIGINT);
  10.     sigaddset (&sigset, SIGQUIT);
  11.    
  12.     sigset_t oldset;
  13.     if (sigprocmask (SIG_SETMASK, &sigset, &oldset) == -1)
  14.     {
  15.         perror ("sigprocmask");
  16.         exit (EXIT_FAILURE);
  17.     }
  18.    
  19.     if (sigpending (&sigset) == -1)
  20.     {
  21.         perror ("sigpending");
  22.         exit (EXIT_FAILURE);
  23.     }
  24.     if (sigismember (&sigset, SIGINT) == 1)
  25.   printf ("SIGINT信号未决\n");
  26.  while (1)
  27.   pause ();
  28.  return 0;
  29. }
  30. 输出结果:
  31. 按 ctrl+c 和 ctrl+\ 失效

(2)获取调用进程的未决信号集

  1. #include <signal.h>
  2. int sigpending(sigset_t *set);
  3. 返回值:成功返回 0,失败返回 -1

1》》函数解析

sigpending 函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过 set 参数返回。

2》》示例说明

  1. #include "apue.h"
  2. static void sig_quit(int);
  3. int
  4. main(void)
  5. {
  6.  sigset_t newmask, oldmask, pendmask;
  7.  if (signal(SIGQUIT, sig_quit) == SIG_ERR)
  8.   err_sys("can't catch SIGQUIT");
  9.  /*
  10.   * Block SIGQUIT and save current signal mask.
  11.   */
  12.  sigemptyset(&newmask);
  13.  sigaddset(&newmask, SIGQUIT);
  14.  if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
  15.   err_sys("SIG_BLOCK error");
  16.  sleep(5); /* SIGQUIT here will remain pending */
  17.  if (sigpending(&pendmask) < 0)
  18.   err_sys("sigpending error");
  19.  if (sigismember(&pendmask, SIGQUIT))
  20.   printf("\nSIGQUIT pending\n");
  21.  /*
  22.   * Restore signal mask which unblocks SIGQUIT.
  23.   */
  24.  if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
  25.   err_sys("SIG_SETMASK error");
  26.  printf("SIGQUIT unblocked\n");
  27.  sleep(5); /* SIGQUIT here will terminate with core file */
  28.  exit(0);
  29. }
  30. static void
  31. sig_quit(int signo)
  32. {
  33.  printf("caught SIGQUIT\n");
  34.  if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
  35.   err_sys("can't reset SIGQUIT");
  36. }
  37. 输出结果:
  38. ^\^\^\
  39. SIGQUIT pending
  40. caught SIGQUIT
  41. SIGQUIT unblocked
  42. ^\^\退出 (核心已转储)

3》》示例解析

进程阻塞 SIGQUIT 信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠 5 秒。在此期间所产生的退出信号 SIGQUIT 都被阻塞,不递送至该进程,直到该信号不再被阻塞。在 5 秒休眠结束后,检查该信号是否是未决的,然后将 SIGQUIT 设置为不再阻塞。

(3)可靠和不可靠信号的屏蔽

对于可靠信号,通过 sigprocmask 函数设置信号掩码以后,每种被屏蔽信号中的每个信号都会被阻塞,并按先后顺序排队,一旦解除屏蔽,这些信号会被依次递送。

对于不可靠信号,通过 sigprocmask 函数设置信号掩码以后,每种被屏蔽信号中只有第一个会被阻塞,并在解除屏蔽后被递送,其余的则全部丢失。

四、信号处理与发送

1、信号处理函数 sigaction

  1. #include <signal.h>
  2. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  3. 返回值:成功返回 0,失败返回 -1

(1)函数功能

sigaction 函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。
此函数取代了 UNIX 早期版本使用的 signal 函数。可以理解为,是 signal 函数的增强版。

(2)参数解析

signum:信号编号
act:信号行为
oldact:输出原信号行为,可置 NULL

当 signum 信号被递送时,按 act 所描述的行为响应。若 oldact 非 NULL,则通过该参数输出原来的响应行为。
sigaction 函数通过信号行为结构类型 sigaction 来描述对一个信号的响应行为。
  1. struct sigaction
  2. {
  3. 1 void  (*sa_handler)(int); 
  4.  =>函数指针,用于设置信号的处理方式,与signal函数中第二个参数相同,SIG_IGW,SIG_DFL函数名
  5. 2 void  (*sa_sigaction)(int, siginfo_t */*结构体指针*/, void *);
  6.  =>函数指针,作为第二种处理信号的方式 是否使用该处理方式,依赖与sa_flags的值
  7. 3 sigset_t  sa_mask;
  8.  =>用于设置在信号处理函数的执行期间,需要屏蔽的信号
  9.  =>自动屏蔽与正在处理的信号相同的信号
  10. 4  int sa_flags;
  11.  =>处理的标志
  12.  =>SA_SIGINFO 表示采用第二个函数指针处理信号
  13.  =>SA_NODEFER 表示解除对相同信号的屏蔽
  14.  =>SA_RESETHAND 表示自定义处理信号后恢复默认处理方式
  15. 5 void  (*sa_restorer)(void);
  16.  => 保留成员,暂时不是用,目前置NULL
  17. };
其中第二个函数指针的第二个参数类型如下:
  1. struct siginfo_t
  2. {
  3. pid_t  si_pid;  /* Sending process ID */  //发送信号进程的ID
  4. sigval_t si_value;  /* Signal value */    //发送信号时的附加数据  
  5. };

(3)示例说明

示例一:增减信号掩码

  1. //使用sigaction函数处理信号
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <sys/types.h>
  7. void fa(int signo)
  8. {
  9.  printf("捕获到了信号%d\n",signo);
  10.  sleep(5);
  11.  printf("信号处理完毕\n");
  12. }
  13. int main(void)
  14. {
  15.  //定义结构体变量并且进行初始化
  16.  struct sigaction action = {};
  17.  //指定函数指针的初始值
  18.  action.sa_handler = fa;
  19.  //清空信号集,然后放入信号3
  20.  sigemptyset(&action.sa_mask);
  21.  sigaddset(&action.sa_mask,3);
  22.  //设置处理信号的标志
  23.  //解除对相同信号的屏蔽,信号2
  24.  //action.sa_flags = SA_NODEFER;
  25.  //自定义处理后恢复默认处理方式
  26.  action.sa_flags = SA_RESETHAND;
  27.  //使用sigaction对信号2自定义处理
  28.  sigaction(2,&action,NULL);
  29.  while(1);
  30.  return 0;
  31. }
  32. 输出结果:
  33. ^C捕获到了信号2
  34. ^\^\^\^\^\^\^\信号处理完毕
  35. 退出 (核心已转储)

示例二:一次性信号处理

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. void oldsigint(int signum)
  6. {
  7.     printf ("\n%d进程:收到%d信号\n", getpid (), signum);
  8. }
  9. int main()
  10. {
  11.     struct sigaction sigact = {};
  12.     sigact.sa_handler = oldsigint;
  13.     sigaddset (&sigact.sa_mask, SIGQUIT);
  14.     sigact.sa_flags = SA_NODEFER | SA_RESETHAND;
  15.    
  16.     if (sigaction (SIGINT, &sigact, NULL) == -1)
  17.     {
  18.         perror ("sigaction");
  19.         exit (EXIT_FAILURE);
  20.     }
  21.    
  22.     pause();
  23.     return 0;
  24. }
  25. 输出结果:
  26. ^C
  27. 2512进程:收到2信号
  28. ^C

示例三:提供更多信息的信号处理函数

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. void newsigint (int signum, siginfo_t* siginf, void* reserved)
  6. {
  7.     printf ("%d进程给我发了一个SIGINT信号\n", siginf->si_pid);
  8. }
  9. int main()
  10. {
  11.     struct sigaction sigact = {};
  12.     sigact.sa_sigaction = newsigint;
  13.     sigaddset (&sigact.sa_mask, SIGQUIT);
  14.     sigact.sa_flags = SA_NODEFER | SA_SIGINFO;
  15.     if (sigaction (SIGINT, &sigact, NULL) == -1)
  16.     {
  17.         perror ("sigaction");
  18.         exit (EXIT_FAILURE);
  19.     }
  20.    
  21.     pause();
  22.     return 0;
  23. }
  24. 输出结果:
  25. ^C0进程给我发了一个SIGINT信号

(4)用 sigaction 实现 signal 函数

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include "apue.h"
  4. /* Reliable version of signal(), using POSIX sigaction().  */
  5. Sigfunc * signal_my(int signo, Sigfunc *func)
  6. {
  7.  struct sigaction act, oact;
  8.  act.sa_handler = func;
  9.  sigemptyset(&act.sa_mask);
  10.  act.sa_flags = 0;
  11.  if (signo == SIGALRM) {
  12. #ifdef SA_INTERRUPT
  13.   act.sa_flags |= SA_INTERRUPT;
  14. #endif
  15.  } else {
  16.   act.sa_flags |= SA_RESTART;
  17.  }
  18.  if (sigaction(signo, &act, &oact) < 0)
  19.   return(SIG_ERR);
  20.  return(oact.sa_handler);
  21. }
  22. void fa (int signo)
  23. {
  24.  printf ("捕获到了信号%d\n", signo);
  25. }
  26. int main (void)
  27. {
  28.  signal_my (2, fa); 
  29.  sleep (5);
  30.  return 0;
  31. }
  32. 输出结果:
  33. ^C捕获到了信号2
另一个版本,可阻止被中断的系统调用重启动。
  1. #include "apue.h"
  2. Sigfunc *
  3. signal_intr(int signo, Sigfunc *func)
  4. {
  5.  struct sigaction act, oact;
  6.  act.sa_handler = func;
  7.  sigemptyset(&act.sa_mask);
  8.  act.sa_flags = 0;
  9. #ifdef SA_INTERRUPT
  10.  act.sa_flags |= SA_INTERRUPT;
  11. #endif
  12.  if (sigaction(signo, &act, &oact) < 0)
  13.   return(SIG_ERR);
  14.  return(oact.sa_handler);
  15. }

2、信号发送函数 sigqueue

  1. #include <signal.h>
  2. int sigqueue(pid_t pid, int sig, const union sigval value);
  3. 返回值:成功返回 0;失败返回 -1

(1)函数功能

表示向指定进程发送指定的信号和附加数据

(2)参数解析

pid:接收信号进程的 PID
sig:信号编号
value:附加数据
注意,该参数的类型 sigval 是一个联合:
  1. union sigval
  2. {
  3.  int   sival_int;//整数
  4.  void *sival_ptr;//地址
  5. };

(3)示例说明

  1. //sigqueue函数和sigaction函数的使用
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <signal.h>
  7. void fa(int signo,siginfo_t* info,void* p)
  8. {
  9.  printf("进程%d发送来了信号%d,附加数据是:%d\n",info->si_pid,signo,info->si_value);
  10. }
  11. int main(void)
  12. {
  13.  //定义结构体变量进行初始化
  14.  struct sigaction action = {};
  15.  //给第二个函数指针进行初始化
  16.  action.sa_sigaction = fa;
  17.  //给处理标志进行赋值
  18.  //表采用结构中第二个函数指针处理
  19.  action.sa_flags = SA_SIGINFO;
  20.  //使用sigaction对信号40自定义处理
  21.  sigaction(40,&action,NULL);
  22.  //创建子进程给父进程发信号和数据
  23.  pid_t pid = fork();
  24.  if(-1 == pid)
  25.  {
  26.   perror("fork"),exit(-1);
  27.  }
  28.  if(0 == pid) //子进程
  29.  {
  30.   int i = 0;
  31.   for(i = 0; i < 100; i++)
  32.   {
  33.    //定义联合进行初始化
  34.    union sigval v;
  35.    v.sival_int = i;
  36.    //发送信号和附加数据
  37.    sigqueue(getppid(),40,v);
  38.   }
  39.   sleep(1);
  40.   exit(100);//终止子进程
  41.  }
  42.  //父进程等待处理信号和附加数据
  43.  while(1);
  44.  return 0;
  45. }
  46. 输出结果:
  47. 进程2721发送来了信号40,附加数据是:0
  48. 进程2721发送来了信号40,附加数据是:1
  49. 进程2721发送来了信号40,附加数据是:2
  50. 。。。。
  51. 进程2721发送来了信号40,附加数据是:97
  52. 进程2721发送来了信号40,附加数据是:98
  53. 进程2721发送来了信号40,附加数据是:99

五、函数 sigsetjmp 和 siglongjmp

暂时不讲

六、 函数 sigsuspend

暂时不讲

七、未讲部分

中断的系统调用  未讲
可靠信号术语和语义  未讲
函数 system  未讲
函数 clock_nanosleep  未讲
作业控制信号  未讲
信号名和编号  未讲
信号这一章,内容有点杂,好多东西没有用过,所以不太熟悉。
                                   
                                   
               
                   
                                           

一、闹钟和睡眠

1、函数 alarm

  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
  3. 返回值:返回 0 或先前所设闹钟的剩余秒数

(1)函数功能

使用 alarm 函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生 SIGALRM 信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该 alarm 函数的进程。

(2)参数解析

参数 seconds 的值是产生信号 SIGALRM 需要经过的时钟秒数。当这个时刻到达时,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一个时间间隔。

(3)函数解析

每个进程只能有一个闹钟时间。如果在调用 alarm 时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余值作为本次 alarm 函数调用的值返回。以前注册的闹钟时间则被新值代替。
如果有以前注册的尚未超过的闹钟时间,而且本次调用的 seconds 值是 0,则取消以前的闹钟时间,其余留值仍作为 alarm 函数的返回值。
虽然 SIGALRM 的默认动作是终止进程,但是大多数使用闹钟的进程捕捉此信号。如果此时进程要终止,则在终止之前它可以执行所需的清理操作。如果我们想捕捉 SIGALRM 信号,则必须在调用 alarm 之前安装该信号的处理程序。如果我们先调用 alarm,然后在我们能够安装 SIGALRM 处理程序之前已接到该信号,那么进程将终止。

(4)示例说明

  1. //示例一
  2. //alarm函数的使用
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <sys/types.h>
  8. void fa(int signo)
  9. {
  10.  printf("捕获到了信号%d\n",signo);
  11.  //设置1秒后发送信号
  12.  alarm(1);
  13. }
  14. int main(void)
  15. {
  16.  //设置SIGALRM信号进行自定义处理
  17.  signal(SIGALRM,fa);
  18.  //设置5秒后发送SIGALRM信号
  19.  int res = alarm(5);
  20.  printf("res = %d\n",res);//0
  21.  sleep(2);
  22.  //重新修改为10秒后发送SIGALRM信号
  23.  res = alarm(10);
  24.  printf("res = %d\n",res); //3
  25.  
  26.  /*
  27.  sleep(3);
  28.  //重新设置为0秒后,取消之前的闹钟
  29.  res = alarm(0);
  30.  printf("res = %d\n",res); //7
  31.  */
  32.  while(1);
  33.  return 0;
  34. }
  35. 输出结果:
  36. res = 0
  37. res = 3
  38. 捕获到了信号 14
  39. 捕获到了信号 14
  40. 捕获到了信号 14
  41. 捕获到了信号 14
  42. 捕获到了信号 14
  43. 捕获到了信号 14
  44. 捕获到了信号 14
  45. 捕获到了信号 14
  1. //示例二
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. void sigalrm (int signum)
  8. {
  9.  time_t t = time (NULL);
  10.  struct tm *it = localtime (&t);
  11.  printf ("\n%02d:%02d:%02d  按回车键退出\n", it->tm_hour, it->tm_min, it->tm_sec);
  12.  alarm (1);
  13. }
  14. int main (void)
  15. {
  16.  if (signal (SIGALRM, sigalrm) == SIG_ERR)
  17.   perror ("signal"), exit (1);
  18.  sigalrm (SIGALRM);
  19.  getchar ();
  20.  return 0;
  21. }
  22. 输出结果:
  23. 14:07:37  按回车键退出
  24. 14:07:38  按回车键退出
  25. 14:07:39  按回车键退出
  26. 14:07:40  按回车键退出

(5)示例解析

示例一,alarm 设置 5 秒结束后,发送 SIGALRM 信号,如果闹钟时间没有结束,又设了闹钟,则返回剩余闹钟秒数。alarm 参数为 0,则取消闹钟。
示例二,sigalrm (SIGARLM);函数运行,执行到 alarm (1); 产生 SIGALRM 信号,触发 signal。然后就开始循环。

2、函数 pause

  1. #include <unistd.h>
  2. int pause(void);
  3. 成功阻塞,失败返回 -1

(1)函数功能

pause 函数使调用进程挂起直至捕捉到一个信号。(无限睡眠)

(2)函数解析

该函数使调用 进程/线程 进入无时限的睡眠状态,直到有信号终止了调用进程或被其捕获。
如果有信号被调用进程捕获,在信号处理函数返回以后,pause 函数才会返回,且返回值为 -1,同时置 errno 为 EINTR,表示阻塞的系统调用被信号中断。
pause 函数要么不返回,要么返回失败,不会返回成功。

(3)示例说明

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <errno.h>
  6. void sigint (int signo)
  7. {
  8.  printf ("\n中断符号被发送\n");
  9. }
  10. int main (void)
  11. {
  12.  if (signal (SIGINT, sigint) == SIG_ERR)
  13.   perror ("signal"), exit (1);
  14.  printf ("按 ctrl+c 继续\n");
  15.  if (pause () != -1 && errno != EINTR)
  16.   perror ("pause"), exit (1);
  17.  printf ("进程继续\n");
  18.  return 0;
  19. }
  20. 输出结果:
  21. 按 ctrl+c 继续
  22. ^C
  23. 中断符号被发送
  24. 进程继续

(4)示例解析

当有信号被当前进程捕获,在信号处理函数返回以后,pause函数返回,且返回值为-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断。

3、函数 sleep

  1. #include <unistd.h>
  2. unsigned int sleep(unsigned int seconds);
  3. 返回 0 或剩余秒数

(1)函数功能

有限睡眠

(2)参数解析

参数 seconds 以秒为单位的睡眠时限。 

(3)函数解析

该函数使调用进程/线程睡眠 seconds 秒,除非有信号终止了调用进程或被其捕获
如果有信号被调用进程捕获,在信号处理函数返回以后,sleep 函数才会返回,且返回值为剩余的秒数,否则该函数将返回 0,表示睡眠充足。如果由于捕获到某个信号 sleep 提早返回时,返回值是未休眠完的秒数(所要求的时间减去实际休眠时间)。

(4)示例说明

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <errno.h>
  6. void sigint (int signo)
  7. {
  8.  printf ("\n中断符号被发送\n");
  9. }
  10. int main (void)
  11. {
  12.  if (signal (SIGINT, sigint) == SIG_ERR)
  13.   perror ("signal"), exit (1);
  14.  printf ("按 ctrl+c 继续\n");
  15.  int res = sleep (60);
  16.  if (res)
  17.   printf ("进程被提前%d秒叫醒\n", res);
  18.  printf ("进程继续\n");
  19.  return 0;
  20. }
  21. 输出结果:
  22. 按 ctrl+c 继续
  23. ^C
  24. 中断符号被发送
  25. 进程被提前57秒叫醒
  26. 进程继续

(5)sleep 函数简单实现

  1. #include "apue.h"
  2. static void
  3. sig_alrm(int signo)
  4. {
  5.  /* nothing to do, just returning wakes up sigsuspend() */
  6. }
  7. unsigned int
  8. my_sleep(unsigned int seconds)
  9. {
  10.  struct sigaction newact, oldact;
  11.  sigset_t   newmask, oldmask, suspmask;
  12.  unsigned int  unslept;
  13.  /* set our handler, save previous information */
  14.  newact.sa_handler = sig_alrm;
  15.  sigemptyset(&newact.sa_mask);
  16.  newact.sa_flags = 0;
  17.  sigaction(SIGALRM, &newact, &oldact);
  18.  /* block SIGALRM and save current signal mask */
  19.  sigemptyset(&newmask);
  20.  sigaddset(&newmask, SIGALRM);
  21.  sigprocmask(SIG_BLOCK, &newmask, &oldmask);
  22.  alarm(seconds);
  23.  suspmask = oldmask;
  24.  /* make sure SIGALRM isn't blocked */
  25.  sigdelset(&suspmask, SIGALRM);
  26.  /* wait for any signal to be caught */
  27.  sigsuspend(&suspmask);
  28.  /* some signal has been caught, SIGALRM is now blocked */
  29.  unslept = alarm(0);
  30.  /* reset previous action */
  31.  sigaction(SIGALRM, &oldact, NULL);
  32.  /* reset signal mask, which unblocks SIGALRM */
  33.  sigprocmask(SIG_SETMASK, &oldmask, NULL);
  34.  return(unslept);
  35. }
  36. int main (void)
  37. {
  38.  my_sleep (10);
  39.  printf ("hello world!\n");
  40.  return 0;
  41. }
  42. 输出结果:
  43. hello world!

4、函数 nanosleep

  1. #include <time.h>
  2. int nanosleep(const struct timespec *req, struct timespec *rem);
  3. 返回值:若休眠到要求的时间,返回 0,若出错,返回 -1
  1. struct timespec
  2. {
  3.     time_t  tv_sec;         /* seconds */
  4.     long    tv_nsec;        /* nanoseconds */
  5. };

(1)函数功能

nanosleep 函数与 sleep 函数类似,但是提供了纳秒级的精度

(2)参数解析

这个函数挂起调用进程,直到要求的时间已经超时或者某个信号中断了该函数。
req 参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,rem 参数指向的 timespec 结构就会被设置为未休眠完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数置为 NULL。

如果系统并不支持纳秒这一精度,要求的时间就会取整。因为,nanosleep 函数并不涉及产生任何信号,所以不需要担心与其函数的交互。

(3)示例说明

  1. #include <stdio.h>
  2. #include <time.h>
  3. #include <stdlib.h>
  4. #include <sys/time.h> 
  5. int main (void)
  6. {
  7.  struct timespec req, rem;
  8.  rem.tv_sec = 0
  9.  rem.tv_nsec = 0
  10.  req.tv_sec = 0
  11.  req.tv_nsec = 1000000
  12.  int res = 0;
  13.  struct timeval start,end;     
  14.  gettimeofday( &start, NULL );  /*测试起始时间*/   
  15.  if (res = (nanosleep (&req, &rem)) == -1)
  16.   perror ("nanosleep"), exit (1);
  17.  gettimeofday( &end, NULL );   /*测试终止时间*/     
  18.  int timeuse = (end.tv_usec - start.tv_usec);     
  19.  printf("运行时间为:%d us\n",timeuse);     
  20.  return 0;
  21. }
  22. 输出结果:
  23. 运行时间为:1173 us

5、sleep、usleep 和 nanosleep 区别

时间单位还有:秒(s)、毫秒(ms)、微秒 (μs)、纳秒(ns)、皮秒(ps)、飞秒(fs)、阿秒、渺秒     
usleep 和 sleep 区别在于精度,ulseep 为 微妙级,sleep 为秒级。
sleep() 和 nanosleep() 都是使进程睡眠一段时间后被唤醒,但是二者的实现完全不同。

Linux 中并没有提供系统调用 sleep(),sleep() 是在库函数中实现的,它是通过调用 alarm() 来设定报警时间,调用sigsuspend() 将进程挂起在信号 SIGALARM 上。
nanosleep() 则是 Linux中 的系统调用
,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个 timer_list 型定时器,time_list 结构里包括唤醒时间以及唤醒后执行的函数,通过 nanosleep() 加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用 schedule() 函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以 nanosleep() 精度也不是很高。

二、信号集

1、什么是信号集

顾名思义,多个信号组成的信号集合谓之信号集。
系统内核用 sigset_t 类型表示信号集。sigset_t 类型是一个结构体,但该结构体中只有一个成员,是一个包含 32 个元素的整数数组。
 /usr/include/i386-linux-gnu/bits/sigset.h 中有如下类型定义:
  1. /* A `sigset_t' has a bit for each signal.  */
  2. # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
  3. typedef struct
  4.   {
  5.     unsigned long int __val[_SIGSET_NWORDS];
  6.   } __sigset_t;
可以把 sigset_t 类型看成一个由 1024 个二进制位组成的大整数。
其中的每一位对应一个信号,其实目前远没有那么多信号。某位为 1 就表示信号集中有此信号,反之为 0 就是无此信号。当需要同时操作多个信号时,常以 sigset_t 作为函数的参数或返回值的类型。

2、信号集函数

  1. #include <signal.h>
  2. int sigemptyset(sigset_t *set);
  3. int sigfillset(sigset_t *set);
  4. int sigaddset(sigset_t *set, int signum);
  5. int sigdelset(sigset_t *set, int signum);
  6. 4 个函数返回值:若成功,返回 0,;若出错,返回 -1
  7. int sigismember(const sigset_t *set, int signum);
  8. 返回值:若真,返回 1;若假,返回 0

(1)函数 sigemptyset

清空信号集,即将信号集的全部信号位清 0.
例如:
  1. sigset_t sigset;
  2. if (sigemptyset (&sigset) == -1)
  3.     perror ("sigemptyset"), exit (1);

(2)函数 sigfillset

填满信号集,即将信号集的全部信号位置 1.
例如:
  1. sigset_t sigset;
  2. if (sigfillset (&sigset) == -1)
  3.     perror ("sigfillset"), exit (1);

(3)函数 sigaddset

加入信号,即将信号集中与指定信号编号对应的信号位置 1.
例如:
  1. if (sigaddset (&sigset, SIGINT))
  2.     perror ("sigaddset"), exit (1);

(4)函数 sigdelset

删除信号,即将信号集中与指定信号编号对应的信号位清 0.
例如:
  1. if (sigdelest (&sigset, SIGINT))
  2.     perror ("sigdelset"), exit (1);

(5)函数 sigismember

判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号位是否为 1.
例如:
  1. if (sigismember (&sigset, SIGINT) == 1)
  2.     printf ("信号集中有 SIGINT 信号\n");

(6)示例说明

  1. //信号集的操作
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int main(void)
  7. {
  8.  sigset_t set;
  9.  printf("set = %lu\n",set);
  10.  printf("sizeof(sigset_t) = %d\n",sizeof(sigset_t));
  11.  //清空信号集
  12.  sigemptyset(&set);
  13.  printf("set = %lu\n",set);//0
  14.  //增加信号到信号集中
  15.  sigaddset(&set,2);
  16.  // 0000 0010 => 1*2 = 2
  17.  printf("set = %lu\n",set);//2
  18.  sigaddset(&set,3);
  19.  // 0000 0110 => 4+2 = 6
  20.  printf("set = %lu\n",set);//6
  21.  sigaddset(&set,7);
  22.  // 0100 0110 => 64+4+2 = 70
  23.  printf("set = %lu\n",set);//70
  24.  //从信号集中删除信号3
  25.  sigdelset(&set,3);
  26.  // 0100 0010 => 64 + 2 = 66
  27.  printf("set = %lu\n",set);//66
  28.  //判断信号是否存在
  29.  if(sigismember(&set,2))
  30.  {
  31.   printf("信号2存在\n");
  32.  }
  33.  if(sigismember(&set,3))
  34.  {
  35.   printf("信号3存在\n");
  36.  }
  37.  if(sigismember(&set,7))
  38.  {
  39.   printf("信号7存在\n");
  40.  }
  41.  //填满信号集
  42.  sigfillset(&set);
  43.  printf("set = %lu\n",set);//很大的数
  44.  return 0;
  45. }
  46. 输出结果:
  47. set = -1078591106
  48. sizeof(sigset_t) = 128
  49. set = 0
  50. set = 2
  51. set = 6
  52. set = 70
  53. set = 66
  54. 信号2存在
  55. 信号7存在
  56. set = 2147483647

3、信号集函数实现 (了解)

  1. #include <signal.h>
  2. #include <errno.h>
  3. /*
  4. * <signal.h> usually defines NSIG to include signal number 0.
  5. */
  6. #define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG)
  7. int
  8. sigaddset(sigset_t *set, int signo)
  9. {
  10.  if (SIGBAD(signo)) {
  11.   errno = EINVAL;
  12.   return(-1);
  13.  }
  14.  *set |= 1 << (signo - 1);  /* turn bit on */
  15.  return(0);
  16. }
  17. int
  18. sigdelset(sigset_t *set, int signo)
  19. {
  20.  if (SIGBAD(signo)) {
  21.   errno = EINVAL;
  22.   return(-1);
  23.  }
  24.  *set &= ~(1 << (signo - 1)); /* turn bit off */
  25.  return(0);
  26. }
  27. int
  28. sigismember(const sigset_t *set, int signo)
  29. {
  30.  if (SIGBAD(signo)) {
  31.   errno = EINVAL;
  32.   return(-1);
  33.  }
  34.  return((*set & (1 << (signo - 1))) != 0);
  35. }

三、信号屏蔽

1、递送、未决与掩码

当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程就叫做递送
信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号状态称为未决
每个进程都有一个信号掩码,它实际上是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给相应的进程,而是会被阻塞在未决状态。
在信号处理函数执行期间,这个正在被处理的信号总是处于信号掩码中,如果又有该信号产生,则会被阻塞,直到上一个针对该信号的处理过程结束以后才会被递送。
当进程正在执行类似更新数据库这样的敏感任务时,可能不希望被某些信号中断。这时可以通过信号掩码暂时屏蔽而非忽略掉这些信号,使其一旦产生即被阻塞于未决状态,待特定任务完成后,再回过头来处理这些信号。

2、设置掩码与检测未决

(1)设置调用进程的信号掩码

  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  3. 返回值:若成功,返回 0;若出错,返回 -1

1》》参数解析

参数 how 为修改信号掩码的方式,可取以下值
    SIG_BLOCK        将 sigset 中的信号加入当前信号掩码
    SIG_UNBLOCK  从当前信号掩码中删除 sigset 中的信号
    SIG_SETMASK   把 sigset 设置成当前信号掩码
参数 sigset 为信号集,取 NULL 则忽略此参数
参数 oldset 为输出原信号掩码,取 NULL 则忽略此参数

2》》示例说明

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7.     sigset_t sigset;
  8.     sigemptyset (&sigset);
  9.     sigaddset (&sigset, SIGINT);
  10.     sigaddset (&sigset, SIGQUIT);
  11.    
  12.     sigset_t oldset;
  13.     if (sigprocmask (SIG_SETMASK, &sigset, &oldset) == -1)
  14.     {
  15.         perror ("sigprocmask");
  16.         exit (EXIT_FAILURE);
  17.     }
  18.    
  19.     if (sigpending (&sigset) == -1)
  20.     {
  21.         perror ("sigpending");
  22.         exit (EXIT_FAILURE);
  23.     }
  24.     if (sigismember (&sigset, SIGINT) == 1)
  25.   printf ("SIGINT信号未决\n");
  26.  while (1)
  27.   pause ();
  28.  return 0;
  29. }
  30. 输出结果:
  31. 按 ctrl+c 和 ctrl+\ 失效

(2)获取调用进程的未决信号集

  1. #include <signal.h>
  2. int sigpending(sigset_t *set);
  3. 返回值:成功返回 0,失败返回 -1

1》》函数解析

sigpending 函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过 set 参数返回。

2》》示例说明

  1. #include "apue.h"
  2. static void sig_quit(int);
  3. int
  4. main(void)
  5. {
  6.  sigset_t newmask, oldmask, pendmask;
  7.  if (signal(SIGQUIT, sig_quit) == SIG_ERR)
  8.   err_sys("can't catch SIGQUIT");
  9.  /*
  10.   * Block SIGQUIT and save current signal mask.
  11.   */
  12.  sigemptyset(&newmask);
  13.  sigaddset(&newmask, SIGQUIT);
  14.  if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
  15.   err_sys("SIG_BLOCK error");
  16.  sleep(5); /* SIGQUIT here will remain pending */
  17.  if (sigpending(&pendmask) < 0)
  18.   err_sys("sigpending error");
  19.  if (sigismember(&pendmask, SIGQUIT))
  20.   printf("\nSIGQUIT pending\n");
  21.  /*
  22.   * Restore signal mask which unblocks SIGQUIT.
  23.   */
  24.  if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
  25.   err_sys("SIG_SETMASK error");
  26.  printf("SIGQUIT unblocked\n");
  27.  sleep(5); /* SIGQUIT here will terminate with core file */
  28.  exit(0);
  29. }
  30. static void
  31. sig_quit(int signo)
  32. {
  33.  printf("caught SIGQUIT\n");
  34.  if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
  35.   err_sys("can't reset SIGQUIT");
  36. }
  37. 输出结果:
  38. ^\^\^\
  39. SIGQUIT pending
  40. caught SIGQUIT
  41. SIGQUIT unblocked
  42. ^\^\退出 (核心已转储)

3》》示例解析

进程阻塞 SIGQUIT 信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠 5 秒。在此期间所产生的退出信号 SIGQUIT 都被阻塞,不递送至该进程,直到该信号不再被阻塞。在 5 秒休眠结束后,检查该信号是否是未决的,然后将 SIGQUIT 设置为不再阻塞。

(3)可靠和不可靠信号的屏蔽

对于可靠信号,通过 sigprocmask 函数设置信号掩码以后,每种被屏蔽信号中的每个信号都会被阻塞,并按先后顺序排队,一旦解除屏蔽,这些信号会被依次递送。

对于不可靠信号,通过 sigprocmask 函数设置信号掩码以后,每种被屏蔽信号中只有第一个会被阻塞,并在解除屏蔽后被递送,其余的则全部丢失。

四、信号处理与发送

1、信号处理函数 sigaction

  1. #include <signal.h>
  2. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  3. 返回值:成功返回 0,失败返回 -1

(1)函数功能

sigaction 函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。
此函数取代了 UNIX 早期版本使用的 signal 函数。可以理解为,是 signal 函数的增强版。

(2)参数解析

signum:信号编号
act:信号行为
oldact:输出原信号行为,可置 NULL

当 signum 信号被递送时,按 act 所描述的行为响应。若 oldact 非 NULL,则通过该参数输出原来的响应行为。
sigaction 函数通过信号行为结构类型 sigaction 来描述对一个信号的响应行为。
  1. struct sigaction
  2. {
  3. 1 void  (*sa_handler)(int); 
  4.  =>函数指针,用于设置信号的处理方式,与signal函数中第二个参数相同,SIG_IGW,SIG_DFL函数名
  5. 2 void  (*sa_sigaction)(int, siginfo_t */*结构体指针*/, void *);
  6.  =>函数指针,作为第二种处理信号的方式 是否使用该处理方式,依赖与sa_flags的值
  7. 3 sigset_t  sa_mask;
  8.  =>用于设置在信号处理函数的执行期间,需要屏蔽的信号
  9.  =>自动屏蔽与正在处理的信号相同的信号
  10. 4  int sa_flags;
  11.  =>处理的标志
  12.  =>SA_SIGINFO 表示采用第二个函数指针处理信号
  13.  =>SA_NODEFER 表示解除对相同信号的屏蔽
  14.  =>SA_RESETHAND 表示自定义处理信号后恢复默认处理方式
  15. 5 void  (*sa_restorer)(void);
  16.  => 保留成员,暂时不是用,目前置NULL
  17. };
其中第二个函数指针的第二个参数类型如下:
  1. struct siginfo_t
  2. {
  3. pid_t  si_pid;  /* Sending process ID */  //发送信号进程的ID
  4. sigval_t si_value;  /* Signal value */    //发送信号时的附加数据  
  5. };

(3)示例说明

示例一:增减信号掩码

  1. //使用sigaction函数处理信号
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <sys/types.h>
  7. void fa(int signo)
  8. {
  9.  printf("捕获到了信号%d\n",signo);
  10.  sleep(5);
  11.  printf("信号处理完毕\n");
  12. }
  13. int main(void)
  14. {
  15.  //定义结构体变量并且进行初始化
  16.  struct sigaction action = {};
  17.  //指定函数指针的初始值
  18.  action.sa_handler = fa;
  19.  //清空信号集,然后放入信号3
  20.  sigemptyset(&action.sa_mask);
  21.  sigaddset(&action.sa_mask,3);
  22.  //设置处理信号的标志
  23.  //解除对相同信号的屏蔽,信号2
  24.  //action.sa_flags = SA_NODEFER;
  25.  //自定义处理后恢复默认处理方式
  26.  action.sa_flags = SA_RESETHAND;
  27.  //使用sigaction对信号2自定义处理
  28.  sigaction(2,&action,NULL);
  29.  while(1);
  30.  return 0;
  31. }
  32. 输出结果:
  33. ^C捕获到了信号2
  34. ^\^\^\^\^\^\^\信号处理完毕
  35. 退出 (核心已转储)

示例二:一次性信号处理

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. void oldsigint(int signum)
  6. {
  7.     printf ("\n%d进程:收到%d信号\n", getpid (), signum);
  8. }
  9. int main()
  10. {
  11.     struct sigaction sigact = {};
  12.     sigact.sa_handler = oldsigint;
  13.     sigaddset (&sigact.sa_mask, SIGQUIT);
  14.     sigact.sa_flags = SA_NODEFER | SA_RESETHAND;
  15.    
  16.     if (sigaction (SIGINT, &sigact, NULL) == -1)
  17.     {
  18.         perror ("sigaction");
  19.         exit (EXIT_FAILURE);
  20.     }
  21.    
  22.     pause();
  23.     return 0;
  24. }
  25. 输出结果:
  26. ^C
  27. 2512进程:收到2信号
  28. ^C

示例三:提供更多信息的信号处理函数

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. void newsigint (int signum, siginfo_t* siginf, void* reserved)
  6. {
  7.     printf ("%d进程给我发了一个SIGINT信号\n", siginf->si_pid);
  8. }
  9. int main()
  10. {
  11.     struct sigaction sigact = {};
  12.     sigact.sa_sigaction = newsigint;
  13.     sigaddset (&sigact.sa_mask, SIGQUIT);
  14.     sigact.sa_flags = SA_NODEFER | SA_SIGINFO;
  15.     if (sigaction (SIGINT, &sigact, NULL) == -1)
  16.     {
  17.         perror ("sigaction");
  18.         exit (EXIT_FAILURE);
  19.     }
  20.    
  21.     pause();
  22.     return 0;
  23. }
  24. 输出结果:
  25. ^C0进程给我发了一个SIGINT信号

(4)用 sigaction 实现 signal 函数

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include "apue.h"
  4. /* Reliable version of signal(), using POSIX sigaction().  */
  5. Sigfunc * signal_my(int signo, Sigfunc *func)
  6. {
  7.  struct sigaction act, oact;
  8.  act.sa_handler = func;
  9.  sigemptyset(&act.sa_mask);
  10.  act.sa_flags = 0;
  11.  if (signo == SIGALRM) {
  12. #ifdef SA_INTERRUPT
  13.   act.sa_flags |= SA_INTERRUPT;
  14. #endif
  15.  } else {
  16.   act.sa_flags |= SA_RESTART;
  17.  }
  18.  if (sigaction(signo, &act, &oact) < 0)
  19.   return(SIG_ERR);
  20.  return(oact.sa_handler);
  21. }
  22. void fa (int signo)
  23. {
  24.  printf ("捕获到了信号%d\n", signo);
  25. }
  26. int main (void)
  27. {
  28.  signal_my (2, fa); 
  29.  sleep (5);
  30.  return 0;
  31. }
  32. 输出结果:
  33. ^C捕获到了信号2
另一个版本,可阻止被中断的系统调用重启动。
  1. #include "apue.h"
  2. Sigfunc *
  3. signal_intr(int signo, Sigfunc *func)
  4. {
  5.  struct sigaction act, oact;
  6.  act.sa_handler = func;
  7.  sigemptyset(&act.sa_mask);
  8.  act.sa_flags = 0;
  9. #ifdef SA_INTERRUPT
  10.  act.sa_flags |= SA_INTERRUPT;
  11. #endif
  12.  if (sigaction(signo, &act, &oact) < 0)
  13.   return(SIG_ERR);
  14.  return(oact.sa_handler);
  15. }

2、信号发送函数 sigqueue

  1. #include <signal.h>
  2. int sigqueue(pid_t pid, int sig, const union sigval value);
  3. 返回值:成功返回 0;失败返回 -1

(1)函数功能

表示向指定进程发送指定的信号和附加数据

(2)参数解析

pid:接收信号进程的 PID
sig:信号编号
value:附加数据
注意,该参数的类型 sigval 是一个联合:
  1. union sigval
  2. {
  3.  int   sival_int;//整数
  4.  void *sival_ptr;//地址
  5. };

(3)示例说明

  1. //sigqueue函数和sigaction函数的使用
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <signal.h>
  7. void fa(int signo,siginfo_t* info,void* p)
  8. {
  9.  printf("进程%d发送来了信号%d,附加数据是:%d\n",info->si_pid,signo,info->si_value);
  10. }
  11. int main(void)
  12. {
  13.  //定义结构体变量进行初始化
  14.  struct sigaction action = {};
  15.  //给第二个函数指针进行初始化
  16.  action.sa_sigaction = fa;
  17.  //给处理标志进行赋值
  18.  //表采用结构中第二个函数指针处理
  19.  action.sa_flags = SA_SIGINFO;
  20.  //使用sigaction对信号40自定义处理
  21.  sigaction(40,&action,NULL);
  22.  //创建子进程给父进程发信号和数据
  23.  pid_t pid = fork();
  24.  if(-1 == pid)
  25.  {
  26.   perror("fork"),exit(-1);
  27.  }
  28.  if(0 == pid) //子进程
  29.  {
  30.   int i = 0;
  31.   for(i = 0; i < 100; i++)
  32.   {
  33.    //定义联合进行初始化
  34.    union sigval v;
  35.    v.sival_int = i;
  36.    //发送信号和附加数据
  37.    sigqueue(getppid(),40,v);
  38.   }
  39.   sleep(1);
  40.   exit(100);//终止子进程
  41.  }
  42.  //父进程等待处理信号和附加数据
  43.  while(1);
  44.  return 0;
  45. }
  46. 输出结果:
  47. 进程2721发送来了信号40,附加数据是:0
  48. 进程2721发送来了信号40,附加数据是:1
  49. 进程2721发送来了信号40,附加数据是:2
  50. 。。。。
  51. 进程2721发送来了信号40,附加数据是:97
  52. 进程2721发送来了信号40,附加数据是:98
  53. 进程2721发送来了信号40,附加数据是:99

五、函数 sigsetjmp 和 siglongjmp

暂时不讲

六、 函数 sigsuspend

暂时不讲

七、未讲部分

中断的系统调用  未讲
可靠信号术语和语义  未讲
函数 system  未讲
函数 clock_nanosleep  未讲
作业控制信号  未讲
信号名和编号  未讲
信号这一章,内容有点杂,好多东西没有用过,所以不太熟悉。
                                   
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!