linux 信号集 信号捕捉函数
信号集:
许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t
在 PCB 中有两个非常重要的信号集。 一个称之为:“阻塞信号集”, 另一个称之为 “未决信号集”。这两个信号集都是内核使用 位图机制(类似 O_AAA | O_BBB)来实现的。 但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。
信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的 阻塞 就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
阻塞信号集和未决信号集:
PCB在内核中,PCB内有 pid、ppid、未决信号集、阻塞信号集等等。
1.用户通过键盘 Ctrl + C, 产生2号信号 SIGINT(信号被创建) 2.信号产生但是没有被处理(未决) - 在内核中将所有的没有被处理的信号存储在一个集合中 - 未决信号集 - SIGINT信号状态被存储在第二个标志位上, - 这个标志位的值为0, 说明信号不是未决状态 - 这个标志位的值为1, 说明信号处理未决状态 3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集), 进行比较 - 阻塞信号集默认不阻塞任何的信号 - 如果想要阻塞某些信号需要用户调用系统的API 4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了 - 如果没有阻塞,这个信号就被处理 - 如果阻塞了, 这个信号就继续处理未决状态,直到阻塞解除,这个信号就被处理 例: 2号未决信号集是1, 2号阻塞信号集是0 表示不阻塞 则未决信号2号则发送给进程,且2号未决信号集变为0 2号阻塞信号集是1 表示阻塞 则未决信号2号被阻塞,不被处理, 当阻塞信号2号变为0之后,未决信号2号才发送
案例:
1 /* 2 以下信号集相关的函数都是对 自定义 的信号集进行操作 3 #include <signal.h> 4 int sigemptyset(sigset_t* set); 5 - 功能:清空信号集中的数据, 将信号集中的左右标志位置为0 6 - 参数: set, 传出参数,需要操作的信号集 7 - 返回值:成功返回 0,失败返回 -1 8 9 int sigfillset(sigset_t* set); 10 - 功能:将信号集中的所有的标志位置为 1 11 - 参数: set, 传出参数,需要操作的信号集 12 - 返回值:成功返回 0,失败返回 -1 13 14 int sigaddset(sigset_t* set, int signum); 15 - 功能:设置信号集中的某一个信号对应标志位为1,表示阻塞这个信号. 16 - 参数: 17 - set, 传出参数,需要操作的信号集 18 - signum,需要设置阻塞的信号 19 - 返回值:成功返回 0,失败返回 -1 20 int sigdelset(sigset_t* set, int signum); 21 - 功能:设置信号集中的某一个信号对应标志位为0,表示不阻塞这个信号. 22 - 参数: 23 - set, 传出参数,需要操作的信号集 24 - signum,需要设置不阻塞的信号 25 - 返回值:成功返回 0,失败返回 -1 26 int sigismember(const sigset_t* set, int signum); 27 - 功能:判断某个信号是否阻塞 28 - 参数: 29 - set,需要操作的信号集 30 - signum,需要判断的信号 31 - 返回值: 32 1: signum被阻塞 33 0: signum不阻塞 34 -1: 调用失败 35 */ 36 #include <stdio.h> 37 #include <signal.h> 38 int main() 39 { 40 //创建一个信号集 41 sigset_t set;//某些位可能为1 42 //清空信号集的内容 43 sigemptyset(&set);//传入信号集的地址 44 //判断 SIGINT 是否在信号集 set里 45 int ret = sigismember(&set, SIGINT); 46 if(ret == 0) 47 { 48 printf("SIGINT 不阻塞\n");//不阻塞 49 } 50 else if(ret == 1) 51 { 52 printf("SIGINT 阻塞\n"); 53 } 54 //添加几个信号到信号集中 55 sigaddset(&set, SIGINT); 56 sigaddset(&set, SIGQUIT); 57 //判断SIGINT是否在信号集中 58 ret = sigismember(&set, SIGQUIT); 59 if(ret == 0) 60 { 61 printf("SIGQUIT 不阻塞\n"); 62 } 63 else if(ret == 1) 64 { 65 printf("SIGQUIT 阻塞\n"); 66 } 67 //从信号集删除一个信号 68 sigdelset(&set, SIGQUIT); 69 //查询SIGQUIT是否在信号集中 70 ret = sigismember(&set, SIGQUIT); 71 if(ret == 0) 72 { 73 printf("SIGQUIT 不阻塞\n"); 74 } 75 else if(ret == 1) 76 { 77 printf("SIGQUIT 阻塞\n"); 78 } 79 return 0; 80 }
标红函数,都是对自定义信号集进行操作,且改变的是自定义的信号集,之后调用 sigprocmask函数 将自定义的信号集设置到内核当中。未决信号集和阻塞信号集都是通过 位图机制 实现的。
sigprocmask:
1 /* 2 man 2 sigprocmask 3 int sigprocmask(int how, const sigset_t* set, sigset_t* oldset); 4 - 功能: 将自定义信号集中的数据设置到内核中(设置阻塞、接触阻塞、替换) 5 - 参数: 6 - how:如何对内核阻塞信号集进行处理 7 SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变. 8 或操作 假设内核中默认的阻塞信号集是 mask, mask | set 9 SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞 10 与操作 mask &= ~set 11 SIG_SETMASK: 覆盖内核中原来的值 12 - set:已经初始化好的用户自定义的信号集 13 - oldset:保存设置之前的内核中的阻塞信号集的状态,可以是 NULL 14 - 返回值: 15 成功: 0 16 失败: -1 17 设置错误号:EFAULT、EINVAL 18 int sigpending(sigset_t* set); 19 - 功能:获取内核中的未决信号集 20 - 参数:set,传出参数,保存的是内核中的未决信号集中的信息. 21 */ 22 //编写一个程序, 把所有的常规信号,(1-31) 未决状态打印到屏幕 23 //设置某些信号是阻塞的, 通过键盘产生这些信号 24 #include <stdio.h> 25 #include <signal.h> 26 #include <unistd.h> 27 int main() 28 { 29 //设置2号、3号信号阻塞 30 sigset_t set; 31 sigemptyset(&set); 32 //将2号3号信号添加到信号集中 33 sigaddset(&set, SIGINT);//Ctrl + C 2号信号 仅将信号传递到后台 需要用 kill -9 进程号杀死 34 sigaddset(&set, SIGQUIT);//Ctrl + \ 3号信号 35 // ./可执行程序 & 变为后台操作 输入 fg 变为 前台 36 //修改内核中的阻塞信号集 37 sigprocmask(SIG_BLOCK,&set, NULL); 38 int num = 0;//解除阻塞 39 while(1) 40 { 41 num++; 42 //获取当前的未决信号集的数据 43 sigset_t pendingset; 44 sigemptyset(&pendingset); 45 sigpending(&pendingset); 46 //遍历前32位 47 for(int i = 1; i <= 32; i++) 48 { 49 if(sigismember(&pendingset, i) == 1) 50 { 51 printf("1"); 52 } 53 else if(sigismember(&pendingset, i) == 0) 54 { 55 printf("0"); 56 } 57 else 58 { 59 perror("sigismember"); 60 exit(0); 61 } 62 } 63 printf("\n"); 64 sleep(1); 65 if(num == 10) 66 { 67 //解除阻塞 68 sigprocmask(SIG_UNBLOCK, &set, NULL); 69 } 70 } 71 return 0; 72 }
信号捕捉函数 sigaction:
1 /* 2 #include <signal.h> 3 int sigaction(int signum, const struct sigaction* act,struct sigaction* oldact); 4 - 功能: 检查或者改变信号的处理, 信号捕捉 5 - 参数: 6 - signum : 需要捕捉的信号的编号或是宏值(信号的名称) 7 - act : 捕捉到信号之后的处理动作 8 - oldact : 上一次对信号捕捉相关的设置, 一般不适用, 传递NULL 9 - 返回值: 10 成功 0 11 失败 -1 12 struct sigaction 13 { 14 // 函数指针,指向的函数就是信号捕捉到之后的处理函数 15 void (*sa_handler)(int); 16 // 不常用, 17 void (*sa_sigaction)(int, siginfo_t *, void *); 18 // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号 19 sigset_t sa_mask; 20 // 使用哪一个信号处理对捕捉到的信号进行处理 21 // 这个值可以是0,表示使用 sa_handler,也可以是 SA_SIGINFO表示使用sa_sigaction 22 int sa_flags; 23 // 被废弃掉了 NULL -.- 24 void (*sa_restorer)(void); 25 } 26 */ 27 #include <stdio.h> 28 #include <sys/time.h> 29 #include <stdlib.h> 30 #include <signal.h> 31 void myAlarm(int num){ 32 printf("捕捉到了信号的编号:%d\n", num); 33 printf("xxxxxxxx\n"); 34 } 35 //过3s以后,每隔2秒定时一次 36 int main() 37 { 38 struct sigaction act; 39 act.sa_flags = 0; 40 act.sa_handler = myAlarm; 41 //清空临时阻塞信号集 42 sigemptyset(&act.sa_mask); 43 //注册信号捕捉 44 sigaction(SIGALRM, &act, NULL); 45 46 struct itimerval new_value; 47 //设置间隔额时间 48 new_value.it_interval.tv_sec = 2;//秒 49 new_value.it_interval.tv_usec = 0;//微秒 50 //设置延迟时间,3秒之后开始第一次定时 51 new_value.it_value.tv_sec = 3;//秒 52 new_value.it_value.tv_usec = 0;//微秒 53 int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞的 54 printf("定时器开始了...\n"); 55 if(ret == -1) 56 { 57 perror("setitimer"); 58 exit(0); 59 } 60 //getchar(); //获取键盘录入 61 while(1){} //捕捉到了信号的编号是14 xxxxxx 62 return 0; 63 }
内核实现信号捕捉的过程:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)