一、信号集操作函数
只能通过阻塞信号集mask才能去改变未决信号集,而阻塞信号又需要通过自定义一个信号集去改变,通过以下函数就可以创建一个信号集set
sigset_t set;
//自定义信号集
int sigemptyset(sigset_t *set);
//清空信号集
int sigfillset(sigset_t *set);
//全部置1
int sigaddset(sigset_t *set, int signum);
//将一个信号添加到集合中
int sigdelset(sigset_t *set,int signum);
//将一个信号从集合中移除
int sigismember(const sigset_t *set, int signum);
//查看某一个信号是否在集合中,在返回1,不在返回0
再用以下函数将set与mask进行操作:
一、sigprocmask函数,用来屏蔽信号、解除屏蔽,其本质是读取或修改进程的信号屏蔽字(PCB中)
【注意:】屏蔽信号,只是将信号处理延后执行(延至解除屏蔽),而忽略则表示将信号丢处理
int sigprocmask(int how, const sigset_t *set, sigset_t *oldest);
成功返回0,失败返回-1,设置errno
set参数:是一个位图,set中哪个位置1,就表示当前进程屏蔽哪个信号
oldest参数:传出参数,保存旧的信号屏蔽集
how参数取值:
1,SIG_BLOCK:set表示需要屏蔽的信号,mask = mask|set
2,SIG_UNBLOCK:set表示需要解除屏蔽的信号,mask = mask&~set
3,SIG_SETMASK:set用于替代原始屏蔽集的新屏蔽集,mask = set
信号是否被处理,是反应在未决信号集上的,所以我们还得需要函数去读未决信号
集
二、sigpending函数,读取当前进程未决信号集
int sigpending(sigset_t *set);
set传出参数,返回值成功为0,失败为-1设置errno
三、信号集操作函数练习👇
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
void print_set(sigset_t *set)
{
int i;
for(i = 1; i<32; i++)
{
if(sigmember(set,i))
putchar("1");
else
putchar("0");
}
printf("\n");
}
int main(int argc, char *argv[])
{
sigset_t set, oldest,pedset;
int ret = 0;
sigemptyset(&set);//设立set
sigaddset(&set,SIGINT);//加一个阻塞信号,2号信号
ret = sigprocmask(SIG_BLOCK,&set, &oldset);//set作用到mask上
if(ret == -1)
sys_err("sigprocmask error");
//查看未决信号集
ret = sigpending(&pedset);
if(ret == -1)
sys_err("sigpending error");
print_set(&pedset);
return 0;
}
如果ctrl+c就会2号阻塞的。如果要增加其他的比如ctrl+/之类的只要sigaddset(&set,对应信号名)
即可。
二、signal实现捕捉函数
signal函数:注册一个信号捕捉函数
typedef void (*sighandler_t )(int);
//函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
//捕捉到signum函数,让这个信号去做handler指的函数
signal数实现捕捉函数实例👇
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
void sig_catch(int signo)
{
printf("catch you! %d\n",signo);
return;
}
int main(int argc, char *argv[])
{
signal(SIGINT, sig_catch);.//如果产生INIT信号,就会去做sig_catch函数
//
//
//
//
return 0;
}
三、sigaction实现信号捕捉(一般用sigaction
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
,成功返回0,失败返回-1;第一个参数是类型,第二个参数是动作,一个结构体类型
struct sigaction
{
void (*sa_handler)(int);//一旦捕捉到信号要做的函数
void (*sa_sigaction)(int, siginfo_t*, void*);//一般不用
sigset sa_mask;
int sa_flags;
void (*sa_restorer)(void);//废弃
};
例子👇
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
void sys_err(const char*str)
{
perror(str);
exit(1);
}
void sig_catch(int signo)//捕捉函数/回调函数---内核是调动者
{
if(signo == SIGINT)
printf("catch you %d\n", signo);
else if(signo == SIGQUIT)
printf("------catch you %d\n",signo);//一个回调函数两个捕捉
return ;
}
//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int main(int argc, char *argv[])
{
struct sigaction act, oldact;//构造了这个结构体类型,结构体内的就都有了
act.sa_handler = sig_catch;//设置回调参数
sigemptyset(&(act.sa_mask));//清空sa_mask屏蔽字,只在sig_catch工作时有效
act.sa_flags = 0;//默认处理动作
int ret = sigaction(SIGINT, &act, &oldact);//注册信号捕捉函数
if(ret == -1)
sys_err("sigaction error");
ret = sigaction(SIGAUIT, &act, &oldact);//注册第二个信号捕捉函数
return 0;
}
所以上述代码的ctrl+c和ctrl+/都不能结束程序,要是ctrl+c或ctrl+/都会被捕捉,只能kill才能结束
四、信号捕捉特性
1. 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用回调函数,而该函数又可能执行很长时间。在这期间所屏蔽的信号不由☆来指定。而是sa_mask来指定。调用完信号处理函数(即回调函数),再恢复为☆
2. XXX信号捕捉函数执行期间,XXX信号自动被屏蔽
3. 阻塞的常规信号不支持排队,产生多次只记录一次(后32各实时信号支持排队)
五、内核实现信号捕捉过程:用户态和内核态的切换
注意第四步,信号处理函数返回时执行特殊的系统调用sigreturn
再次进内核
六、⭐熟练掌握使用信号完成子进程的回收
1. SIGCHILD信号
- SIGCHILD的产生条件:
子进程终止时;
子进程接收到SIGSTOP信号停止时;
子进程处在停止态,接受到SIGCONT后唤醒时
就是发生变化就会产生这个信号
2.借助SIGCHLD信号回收子进程
背景:在有些地方是没有办法设置回收的,比如exec函数,exec函数成功运行就不会再返回,就只能你通过系统的隐式回收来达到回收目的。但通过信号可以实现:给信号SIGCHLD设置捕捉,一旦子进程状态发生变化,内核就回调函数,还是该干啥干啥,就可以进行回收了
——即:
子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认动作是忽略,可以捕捉到该信号,在捕捉函数中完成子进程状态的回收
例子👇
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include<sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
//创建捕捉函数
void catch_child(int signo)
{
pid_t wpid;
wpid = wait(NULL);
if(wpid == -1)
sys_err("wait error");
printf("catch child id %d\n", wpid);
return;
}
int main(int argc, char *argv[])
{
//⭐循环创建多个子进程⭐
pid_t pid;
int i;
for(i = 0; i<5; ++i)
{
if((pid = fork()) == 0)
break;
}
if(i == 5)
{
struct sigaction act;
act.st_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
printf("I am parent = %d\n", getpid());
}else
{
printf("I am child = %d\n", getpid());
}
return 0;
}
但是上述代码会出现多个进程同时死亡,即同时发送信号,而信号是不排队的,再阻塞后只能处理一个信号即回收一个子进程,这样就会出现僵尸现象。(僵尸:复习就是子进程死了父进程没给回收)
解决方法:
在回调函数中,用循环实现一次回调多次回收
void catch_child(in signo)
{
pid_t wpid;
while(wpid = wait(NULL) != -1)
{
printf("-------catch child id = %d\n",wpid);
}
return;
}
循环停止条件是wait(NULL)!=-1,处理一个进程之后,会继续去调用wait函数,如果还有进程死亡,就继续回收
七、 慢速系统调用中断⭐⭐,先有个概念就行
系统调用可以分为两类:慢速系统调用和其他系统调用
- 慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行,也可以设定系统调用是否重启,如read、write、pause、wait等
- 其他系统调用:getpid、getppid、fork等
用sa_flags参数设置来选择中断后是否重启,sa_interrupt
不重启(默认)、sa_restart
重启