sigprocmask函数使用
sigprocmask用于修改内核的信号集 将自定义信号集设置到内核的信号集中,内核未决信号集无法修改(只能获取 通过sigpending)即这个函数用于修改阻塞信号集
int sigprocmask(int how,const sigset_t set,sigset_t oldset);
int sigpending(sigset_t *set);
/*每个进程都有自己的PCB 都有自己的未决信号集和阻塞信号集int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);用于将自定义的信号集设置到内核中how 如何对内核阻塞信号集进行处理SIG_BLOCK 将用户设置的阻塞信号添加到内核中 即用户的信号集中设置为1的信号 在内核信号集中的对应位置也设置为1用户信号集设置为0的部分 则内核信号集中对应位置保留不变 内核信号集 mask = mask | setSIG_UNBLOCK 根据set将内核信号集的对应位置解除阻塞 即用户的信号集中设置为1的信号 在内核信号集中的对应位置设置为0(解除阻塞)用户信号集设置为0的部分 则内核信号集中对应位置保留不变 内核信号集 mask = mask & (~set)SIG_SETMASK 用自定义的信号集 替换内核中PCB的阻塞信号集set 用户已经初始化好的用户自定义的信号集oldset 传出参数 保存着上次(这次设置之前)内核中阻塞信号集的值返回值 成功0 失败-1(有两种错误 分别会设置两种错误号EFAULT set或oldset指向了此进程虚拟地址外EINVAL how指定错误)int sigpending(sigset_t *set);用与获取内核中的 未决信号集 set为传出的未决信号集返回值 0成功 -1失败*/#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <signal.h> //sigprocmask#include <sys/time.h>int main(){//编写一个程序 把所有的常规信号(1-31)的未决状态打印到屏幕//设置某些信号阻塞再通过键盘产生这些信号 这样的话这些信号状态就是未决的//设置2、3号信号阻塞 2、3号信号可由键盘直接产生sigset_t set;sigemptyset(&set);//将2、3号信号添加到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);//修改内核中的阻塞信号集为setsigprocmask(SIG_BLOCK, &set, NULL);sigset_t pending_set_now;sigemptyset(&pending_set_now);while (1){sigpending(&pending_set_now);//遍历输出1-31位未决信号集for (int i = 0; i <= 31; i++){if (sigismember(&pending_set_now, i) == 1) //是未决信号{printf("1");}else if (sigismember(&pending_set_now, i) == 0){printf("0");}else{perror("sigismember:");return -1;}}printf("\n");sleep(1);//解除阻塞// sigprocmask(SIG_UNBLOCK, &set, NULL);}return 0;}
sigaction信号捕捉函数
前面讲的signal也是用于信号捕捉
#include
int sigaction(int signum, const struct sigaction act,
struct sigaction oldact);
改变信号的处理动作
/*#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);检查或改变信号的处理动作(捕获信号 并按我们规定的函数进行回调)signum 要捕捉信号的宏值(编号)act 对这个信号的处理动作struct sigaction {void (*sa_handler)(int);//void ...(int)函数的指针 指向的是信号回调函数void (*sa_sigaction)(int, siginfo_t *, void *);sa_sigaction则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。函数指针void ...(int, siginfo_t *, void *)三个参数分别是sig 捕获的信号的编号,info siginfo_t包含了超详细的信号信息(太长了自己去man2看这个结构体)void * ucontext 指向ucontext_t类型的结构体sigset_t sa_mask;//信号集类型sigset_t 临时阻塞信号集,在信号回调函数执行期间需要被阻塞的信号集,在回调执行完后就不阻塞了int sa_flags;指定信号的处理方式可以为0表示 用sa_handler来回调处理信号SA_NOCLDSTOPSA_NOCLDWAIT (since Linux 2.6)SA_NODEFERSA_ONSTACKSA_RESETHANDSA_RESTARTSA_RESTORERSA_SIGINFO (since Linux 2.2) 用sa_sigaction来回调处理信号void (*sa_restorer)(void);//已废弃 指定null};oldact 上次对这个信号进行的处理动作,传出参数 可不使用传null成功返回 0 失败返回-1*/#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <signal.h>#include <sys/time.h>void sig_handler(int num);int main(){//注册SIGALRM的信号的回调函数struct sigaction act;act.sa_flags = 0; //表示使用sa_handler来进行函数回调act.sa_handler = sig_handler;sigaction(SIGALRM, &act, NULL);sigemptyset(&act.sa_mask); //清空临时阻塞信号集表示 不回调时阻塞任何信号struct itimerval new_value;new_value.it_interval.tv_sec = 2; //定时器周期时间new_value.it_interval.tv_usec = 0; //虽然用不到微秒 但还是设置一下 否则是个随机值 负数的话 是tv_sec-|tv_usec| 正数的话是tv_sec+tv_usecnew_value.it_value.tv_sec = 3; //过多长时间第一次启动定时器new_value.it_value.tv_usec = 0; ////过3秒后启动定时器 定时器周期为2秒setitimer(ITIMER_REAL, &new_value, NULL); //不需要上次的定时器属性 所以old传的是null 非阻塞立即向下执行while (1); //不断地 等待定时器触发回调函数}void sig_handler(int num){printf("捕获到的信号编号为:%d\n", num); //num=14 SIGALRM}
内核实现信号捕捉的过程
![]() |
|---|
对于同一信号比如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信号的回调函数里。
/*使用SIGCHLD信号解决僵尸进程的问题*/#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <signal.h>#include <sys/time.h>#include <sys/wait.h>void sig_handler(int num);int main(){//为了防止 回调函数还没注册成功 就已经有子进程死亡了 先阻塞SIGCHLD信号sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);//创建一些子进程pid_t pid;for (int i = 0; i < 20; i++) //为当前进程创建20个子进程{pid = fork();if (pid == 0) //进这里的是子进程 不能再让子进程去创建孙子进程{break;}else{;}}if (pid > 0) //父进程{//捕捉子进程死亡时发送的SIGCHILD信号//注册SIGCHLD的信号的回调函数struct sigaction act;act.sa_flags = 0; //表示使用sa_handler来进行函数回调act.sa_handler = sig_handler;sigaction(SIGCHLD, &act, NULL);sigemptyset(&act.sa_mask); //清空临时阻塞信号集表示 不回调时阻塞任何信号//注册完回调函数后 解除SG的阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);while (1){printf("parent pid: %d\n", getpid());sleep(2);//wait 一次回收一个子进程(阻塞) or waitpid 一次回收一个子进程(可设置非阻塞)//waitpid pid < -1 等待所有子进程的group id = -pid//waitpid pid = -1等待所有子进程 pid = 0 等待所有与当前进程有一样group id的子进程//waitpid pid > 0 等待多有进程号为指定pid的进程//waitpid 成功回收返回子进程的pid 若为非阻塞没回收但是还有子进程活着的时候返回0 子进程都死亡返回-1}}else if (pid == 0) //子进程{printf("child pid: %d\n", getpid());}}void sig_handler(int num){printf("捕获到的信号编号为:%d\n", num); //num=14 SIGALRM//回收子进程资源if (num == SIGCHLD){//wait(NULL); //回收子进程资源//20个信号这样 并不能完全回收//当多个信号连续到来会造成后面来的信号被忽略//在执行相依信号的回调函数体时 那个信号是被阻塞的//,所以当子进程死亡 发出多个SG信号 SG信号都是未决的 只有第一个信号//(1-31)信号集无法记录目前有多少个xxx信号未决或阻塞,//只能知道目前有xxx信号未决或阻塞。---不支持排队// 从开始注册信号到注册成功这段时间里,有n个SIGCHID信号产生的话,// 那么第一个产生的SIGCHID会抢先将未决位置为1,余下的n-1个SIGCHID被丢弃,// 然后当阻塞解除之后,信号处理函数发现这时候对应信号的未决位为1,继而执行函数处理该信号,// 处理函数中的while循环顺带将其他n-1子进程也一网打尽了,在这期间未决位的状态只经历了两次变化,即0->1->0while (1) // 一次信号触发为了防止 有连续的SG信号到来 有的信号没看到 这里用个循环回收连续(很快)死亡的子进程的资源{//死循环 快速地检查是否有子进程死亡 快速地回收//回调函数 占用的是父进程的资源 相当于暂时中断 去处理这个信号 处理完回到父进程//waitpid 和 SIGCHLD 没关系,即使是某个子进程对应的 SIGCHLD 丢失了,//只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的int ret = waitpid(-1, NULL, WNOHANG); //回收所有子进程资源 不阻塞 一次调用回收一个子进程资源if (ret > 0) //回收到了一个子进程printf("child %d die\n", ret);else if (ret == 0) //还有子进程 在运行break; //回到父进程else if (ret == -1) //无子进程在运行break; //回到父进程}}}

