sigaction函数:
函数说明:注册一个信号处理函数
函数原型:int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
函数参数:
- signum:捕捉的信号
- act:传入参数,新的处理方式
- oldact:传出参数,旧的处理方式
The sigaction structure is defined as something like:
struct sigaction {
void (*sa_handler)(int);//信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//信号处理函数执行期间需要阻塞的信号,信号处理函数结束后,就不阻塞了
int sa_flags;//通常为0,表示默认标识
void (*sa_restorer)(void);
};
测试:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include <signal.h>
#include <sys/time.h>
void handler(int signo)
{
printf("signo==[%d]\n",signo);
sleep(5);
}
int main()
{
//int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
struct sigaction act;
act.sa_handler=handler;
sigemptyset(&act.sa_mask);把set信号集全部置0,不阻塞任何信号
act.sa_flags=0;
sigaction(SIGINT,&act,NULL);
while(1)
{
sleep(1);
}
return 0;
}
结果:
由结果我们可以知道在XXX信号处理函数执行期间,当XXX信号产生多次,该信号进入未决信号集中(被阻塞),在信号处理函数执行结束后,只会执行一次(信号不支持排队)。
若在sa_mask中阻塞了YYY信号,那么在XXX信号处理函数执行时,产生的YYY信号也会进入未决信号集中(被阻塞),等到XXX信号处理函数结束后执行一次。
SIGCHLD信号:
产生SIGCHLD信号的条件:
- 子进程结束的时候
- 子进程收到SIGSTOP信号
- 当子进程停止时,收到SIGCONT信号
SIGCHLLD信号的作用:
子进程退出后,内核会给它的父进程发送SIGCHLD信号,父进程收到这个信号后可以使用wait/waitpid函数对子进程进行回收。
父进程创建两个子进程,然后让父进程捕获SIGCHLD信号完成对子进程的回收
注意点:
有可能还未完成注册信号处理函数,两个子进程已经都退出了
解决方法:可以在fork之前先将SIGCHLD阻塞,当完成信号处理函数的注册后解除阻塞
当SIGCHLD信号函数处理期间,SIGCHLD信号若再次产生是被阻塞的,而且产生多次,最后只会被处理一次,这样就会产生僵尸进程
解决方法:可以在信号处理函数里面使用while(1)循环回收,这样就有可能出现捕获一次SIGCHLD信号但是回收了多个子进程的情况,从而避免产生僵尸进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
void handler(int signo)//信号处理函数
{
//pid_t waitpid(pid_t pid, int *wstatus, int options);
pid_t wpid;
while(1)//一直死循环,防止信号处理函数执行的时候,有多个子进程终止(只会接收一次SIGCHLD信号),产生僵尸进程
{
wpid=waitpid(-1,NULL,WNOHANG);//设为非阻塞,因为子进程收到SIGSTOP,SIGCONT信号,也会发出SIGCHLD信号,这样一来并没有子进程终止,就会一直阻塞
if(wpid>0)
{
printf("[%d]child is quit\n",wpid);
}
else if(wpid==0)//还有子进程运行,break,等下一个SIGCHLD信号
{
break;
}
else if(wpid==-1)//没有子进程,已经全部回收,break
{
printf("no child is living\n");
break;
}
}
}
int main()
{
//先把SIGCHLD信号阻塞,防止还没有注册信号处理函数时,就已经有SIGCHLD信号产生,被忽略
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGCHLD);
// int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask(SIG_BLOCK,&set,NULL);
int i=0;
for(;i<2;i++)//循环产生两个兄弟子进程
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)
{
printf("father:pid=[%d]\n",getpid());
}
else if(pid==0)
{
break;
}
}
if(i==0)
{
printf("[%d]child:pid=[%d],fpid=[%d]\n",i,getpid(),getppid());
}
else if(i==1)
{
printf("[%d]child:pid=[%d],fpid=[%d]\n",i,getpid(),getppid());
}
else if(i==2)
{
struct sigaction act;
act.sa_handler=handler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD,&act,NULL);//注册信号处理函数
sigprocmask(SIG_UNBLOCK,&set,NULL);//把SIGCHLD信号设置为非阻塞
while(1)
{
sleep(1);
}
}
return 0;
}
结果:
使用信号传递让父子进程来回计数
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
//定义成全局变量,不然回调函数无法辨认
int num=0;//从0开始计数
int flag=0;//开关变量
void func1(int signo)//父进程的信号处理函数
{
printf("f:[%d]\n",num);
num+=2;
flag=0;
sleep(1);
}
void func2(int signo)//子进程的信号处理函数
{
printf("c:[%d]\n",num);
num+=2;
flag=0;
sleep(1);
}
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)
{
signal(SIGUSR1,func1);//注册信号处理函数
flag=1;//开关初始为1,让子进程先发出SIGUSR2信号给父进程
while(1)
{
if(flag==0)
{
kill(pid,SIGUSR2);
flag=1;
}
}
}
else if(pid==0)
{
num=1;//子进程从一开始计数
signal(SIGUSR2,func2);
while(1)
{
if(flag==0)
{
kill(getppid(),SIGUSR1);
flag=1;
}
}
}
}
结果: