sigprocmask函数使用
    sigprocmask用于修改内核的信号集 将自定义信号集设置到内核的信号集中,内核未决信号集无法修改(只能获取 通过sigpending)即这个函数用于修改阻塞信号集
    int sigprocmask(int how,const sigset_t set,sigset_t oldset);
    int sigpending(sigset_t *set);

    1. /*
    2. 每个进程都有自己的PCB 都有自己的未决信号集和阻塞信号集
    3. int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
    4. 用于将自定义的信号集设置到内核中
    5. how 如何对内核阻塞信号集进行处理
    6. SIG_BLOCK 将用户设置的阻塞信号添加到内核中 即用户的信号集中设置为1的信号 在内核信号集中的对应位置也设置为1
    7. 用户信号集设置为0的部分 则内核信号集中对应位置保留不变 内核信号集 mask = mask | set
    8. SIG_UNBLOCK 根据set将内核信号集的对应位置解除阻塞 即用户的信号集中设置为1的信号 在内核信号集中的对应位置设置为0(解除阻塞)
    9. 用户信号集设置为0的部分 则内核信号集中对应位置保留不变 内核信号集 mask = mask & (~set)
    10. SIG_SETMASK 用自定义的信号集 替换内核中PCB的阻塞信号集
    11. set 用户已经初始化好的用户自定义的信号集
    12. oldset 传出参数 保存着上次(这次设置之前)内核中阻塞信号集的值
    13. 返回值 成功0 失败-1(有两种错误 分别会设置两种错误号
    14. EFAULT set或oldset指向了此进程虚拟地址外
    15. EINVAL how指定错误)
    16. int sigpending(sigset_t *set);
    17. 用与获取内核中的 未决信号集 set为传出的未决信号集
    18. 返回值 0成功 -1失败
    19. */
    20. #include <unistd.h>
    21. #include <stdio.h>
    22. #include <stdlib.h>
    23. #include <sys/types.h>
    24. #include <signal.h> //sigprocmask
    25. #include <sys/time.h>
    26. int main()
    27. {
    28. //编写一个程序 把所有的常规信号(1-31)的未决状态打印到屏幕
    29. //设置某些信号阻塞再通过键盘产生这些信号 这样的话这些信号状态就是未决的
    30. //设置2、3号信号阻塞 2、3号信号可由键盘直接产生
    31. sigset_t set;
    32. sigemptyset(&set);
    33. //将2、3号信号添加到信号集中
    34. sigaddset(&set, SIGINT);
    35. sigaddset(&set, SIGQUIT);
    36. //修改内核中的阻塞信号集为set
    37. sigprocmask(SIG_BLOCK, &set, NULL);
    38. sigset_t pending_set_now;
    39. sigemptyset(&pending_set_now);
    40. while (1)
    41. {
    42. sigpending(&pending_set_now);
    43. //遍历输出1-31位未决信号集
    44. for (int i = 0; i <= 31; i++)
    45. {
    46. if (sigismember(&pending_set_now, i) == 1) //是未决信号
    47. {
    48. printf("1");
    49. }
    50. else if (sigismember(&pending_set_now, i) == 0)
    51. {
    52. printf("0");
    53. }
    54. else
    55. {
    56. perror("sigismember:");
    57. return -1;
    58. }
    59. }
    60. printf("\n");
    61. sleep(1);
    62. //解除阻塞
    63. // sigprocmask(SIG_UNBLOCK, &set, NULL);
    64. }
    65. return 0;
    66. }


    sigaction信号捕捉函数
    前面讲的signal也是用于信号捕捉
    #include
    int sigaction(int signum, const struct sigaction act,
    struct sigaction
    oldact);
    改变信号的处理动作

    1. /*
    2. #include <signal.h>
    3. int sigaction(int signum, const struct sigaction *act,
    4. struct sigaction *oldact);
    5. 检查或改变信号的处理动作(捕获信号 并按我们规定的函数进行回调)
    6. signum 要捕捉信号的宏值(编号)
    7. act 对这个信号的处理动作
    8. struct sigaction {
    9. void (*sa_handler)(int);//void ...(int)函数的指针 指向的是信号回调函数
    10. void (*sa_sigaction)(int, siginfo_t *, void *);
    11. sa_sigaction则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值
    12. 包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理
    13. 函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
    14. 函数指针void ...(int, siginfo_t *, void *)
    15. 三个参数分别是sig 捕获的信号的编号,info siginfo_t包含了超详细的信号信息(太长了自己去man2看这个结构体)
    16. void * ucontext 指向ucontext_t类型的结构体
    17. sigset_t sa_mask;
    18. //信号集类型sigset_t 临时阻塞信号集,在信号回调函数执行期间需要被阻塞的信号集,在回调执行完后就不阻塞了
    19. int sa_flags;
    20. 指定信号的处理方式
    21. 可以为0表示 用sa_handler来回调处理信号
    22. SA_NOCLDSTOP
    23. SA_NOCLDWAIT (since Linux 2.6)
    24. SA_NODEFER
    25. SA_ONSTACK
    26. SA_RESETHAND
    27. SA_RESTART
    28. SA_RESTORER
    29. SA_SIGINFO (since Linux 2.2) 用sa_sigaction来回调处理信号
    30. void (*sa_restorer)(void);//已废弃 指定null
    31. };
    32. oldact 上次对这个信号进行的处理动作,传出参数 可不使用传null
    33. 成功返回 0 失败返回-1
    34. */
    35. #include <unistd.h>
    36. #include <stdio.h>
    37. #include <stdlib.h>
    38. #include <sys/types.h>
    39. #include <signal.h>
    40. #include <sys/time.h>
    41. void sig_handler(int num);
    42. int main()
    43. {
    44. //注册SIGALRM的信号的回调函数
    45. struct sigaction act;
    46. act.sa_flags = 0; //表示使用sa_handler来进行函数回调
    47. act.sa_handler = sig_handler;
    48. sigaction(SIGALRM, &act, NULL);
    49. sigemptyset(&act.sa_mask); //清空临时阻塞信号集表示 不回调时阻塞任何信号
    50. struct itimerval new_value;
    51. new_value.it_interval.tv_sec = 2; //定时器周期时间
    52. new_value.it_interval.tv_usec = 0; //虽然用不到微秒 但还是设置一下 否则是个随机值 负数的话 是tv_sec-|tv_usec| 正数的话是tv_sec+tv_usec
    53. new_value.it_value.tv_sec = 3; //过多长时间第一次启动定时器
    54. new_value.it_value.tv_usec = 0; //
    55. //过3秒后启动定时器 定时器周期为2秒
    56. setitimer(ITIMER_REAL, &new_value, NULL); //不需要上次的定时器属性 所以old传的是null 非阻塞立即向下执行
    57. while (1)
    58. ; //不断地 等待定时器触发回调函数
    59. }
    60. void sig_handler(int num)
    61. {
    62. printf("捕获到的信号编号为:%d\n", num); //num=14 SIGALRM
    63. }


    内核实现信号捕捉的过程

    2 Linux多进程开发7 信号相关函数2 - 图1


    对于同一信号比如SIGALRM信号,在信号被回调捕获后未决信号集中SIGALRM信号对应的位置变为0,此时如果又有SIGALRM信号发出,未决信号集SIGALRM信号对应的位置变为1,如果上次的SIGALRM函数还未执行完成,则这个未决的SIGALRM信号会被阻塞直到上次SIGALRM回调执行完成后,后面的这次SIGALRM信号才有机会被回调函数处理。即在执行某一信号回调时,会自动阻塞同类型的信号。

    在回调函数中使用的是临时阻塞信号集sa_mask,在回调函数处理完回到主流程中又切换为内核的阻塞信号集。
    常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。用户只能获取未决信号集值,无法改变其值。
    (1-31)信号集无法记录目前有多少个xxx信号未决或阻塞,只能知道目前有xxx信号未决或阻塞。—-不支持排队 (33-64)支持排队能记录有几个同类型信号未决或阻塞,分别是哪些发出

    SIGCHILD信号
    SIGCHILD信号产生的条件
    子进程终止时
    子进程收到SIGSTOP信号 停止时
    子进程处在停止态,接受到SIGCONT后唤醒时
    以上三种条件都会给父进程发送SIGCHILD信号,父进程默认忽略该信号

    可利用SIGCHILD信号解决僵尸进程(子进程结束但父进程没有wait去回收子进程资源 导致子进程变为僵尸进程)的问题。wait是阻塞的所以最好wait不要写在父进程的主流程里,而是写在SIGCHILD信号的回调函数里。

    1. /*
    2. 使用SIGCHLD信号解决僵尸进程的问题
    3. */
    4. #include <unistd.h>
    5. #include <stdio.h>
    6. #include <stdlib.h>
    7. #include <sys/types.h>
    8. #include <signal.h>
    9. #include <sys/time.h>
    10. #include <sys/wait.h>
    11. void sig_handler(int num);
    12. int main()
    13. {
    14. //为了防止 回调函数还没注册成功 就已经有子进程死亡了 先阻塞SIGCHLD信号
    15. sigset_t set;
    16. sigemptyset(&set);
    17. sigaddset(&set, SIGCHLD);
    18. sigprocmask(SIG_BLOCK, &set, NULL);
    19. //创建一些子进程
    20. pid_t pid;
    21. for (int i = 0; i < 20; i++) //为当前进程创建20个子进程
    22. {
    23. pid = fork();
    24. if (pid == 0) //进这里的是子进程 不能再让子进程去创建孙子进程
    25. {
    26. break;
    27. }
    28. else
    29. {
    30. ;
    31. }
    32. }
    33. if (pid > 0) //父进程
    34. {
    35. //捕捉子进程死亡时发送的SIGCHILD信号
    36. //注册SIGCHLD的信号的回调函数
    37. struct sigaction act;
    38. act.sa_flags = 0; //表示使用sa_handler来进行函数回调
    39. act.sa_handler = sig_handler;
    40. sigaction(SIGCHLD, &act, NULL);
    41. sigemptyset(&act.sa_mask); //清空临时阻塞信号集表示 不回调时阻塞任何信号
    42. //注册完回调函数后 解除SG的阻塞
    43. sigprocmask(SIG_UNBLOCK, &set, NULL);
    44. while (1)
    45. {
    46. printf("parent pid: %d\n", getpid());
    47. sleep(2);
    48. //wait 一次回收一个子进程(阻塞) or waitpid 一次回收一个子进程(可设置非阻塞)
    49. //waitpid pid < -1 等待所有子进程的group id = -pid
    50. //waitpid pid = -1等待所有子进程 pid = 0 等待所有与当前进程有一样group id的子进程
    51. //waitpid pid > 0 等待多有进程号为指定pid的进程
    52. //waitpid 成功回收返回子进程的pid 若为非阻塞没回收但是还有子进程活着的时候返回0 子进程都死亡返回-1
    53. }
    54. }
    55. else if (pid == 0) //子进程
    56. {
    57. printf("child pid: %d\n", getpid());
    58. }
    59. }
    60. void sig_handler(int num)
    61. {
    62. printf("捕获到的信号编号为:%d\n", num); //num=14 SIGALRM
    63. //回收子进程资源
    64. if (num == SIGCHLD)
    65. {
    66. //wait(NULL); //回收子进程资源
    67. //20个信号这样 并不能完全回收
    68. //当多个信号连续到来会造成后面来的信号被忽略
    69. //在执行相依信号的回调函数体时 那个信号是被阻塞的
    70. //,所以当子进程死亡 发出多个SG信号 SG信号都是未决的 只有第一个信号
    71. //(1-31)信号集无法记录目前有多少个xxx信号未决或阻塞,
    72. //只能知道目前有xxx信号未决或阻塞。---不支持排队
    73. // 从开始注册信号到注册成功这段时间里,有n个SIGCHID信号产生的话,
    74. // 那么第一个产生的SIGCHID会抢先将未决位置为1,余下的n-1个SIGCHID被丢弃,
    75. // 然后当阻塞解除之后,信号处理函数发现这时候对应信号的未决位为1,继而执行函数处理该信号,
    76. // 处理函数中的while循环顺带将其他n-1子进程也一网打尽了,在这期间未决位的状态只经历了两次变化,即0->1->0
    77. while (1) // 一次信号触发为了防止 有连续的SG信号到来 有的信号没看到 这里用个循环回收连续(很快)死亡的子进程的资源
    78. {
    79. //死循环 快速地检查是否有子进程死亡 快速地回收
    80. //回调函数 占用的是父进程的资源 相当于暂时中断 去处理这个信号 处理完回到父进程
    81. //waitpid 和 SIGCHLD 没关系,即使是某个子进程对应的 SIGCHLD 丢失了,
    82. //只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的
    83. int ret = waitpid(-1, NULL, WNOHANG); //回收所有子进程资源 不阻塞 一次调用回收一个子进程资源
    84. if (ret > 0) //回收到了一个子进程
    85. printf("child %d die\n", ret);
    86. else if (ret == 0) //还有子进程 在运行
    87. break; //回到父进程
    88. else if (ret == -1) //无子进程在运行
    89. break; //回到父进程
    90. }
    91. }
    92. }