上接Linux下IPC方式之信号1
内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。
sigset_t set; // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 将某个信号集清0 成功:0;失败:-1
int sigfillset(sigset_t *set); 将某个信号集置1 成功:0;失败:-1
int sigaddset(sigset_t *set, int signum); 将某个信号加入信号集 成功:0;失败:-1
int sigdelset(sigset_t *set, int signum); 将某个信号清出信号集 成功:0;失败:-1
int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中 返回值:在集合:1;不在:0;出错:-1
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);——用来设置或者解除阻塞信号集
int sigpending(sigset_t *set);——获取未决信号集
sigset_t
类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
对比认知select
函数。
用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
成功:0;失败:-1,设置errno
参数:
set: 传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
oldset: 传出参数,保存旧的信号屏蔽集。
how参数取值: 假设当前的信号屏蔽字为mask
1.SIG_BLOCK: 设置阻塞。 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
2.SIG_UNBLOCK: 解除阻塞。 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
3.SIG_SETMASK: 设置set为新的阻塞信号集。 当how设置为此,set表示用于替代原始屏蔽集的新屏蔽集。相当于 mask = set,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
读取当前进程的未决信号集
int sigpending(sigset_t *set);
set传出参数。
返回值:成功:0;失败:-1,设置errno
将2号信号(ctrl+c)放在阻塞信号集上,这样该信号在未决信号集上会进行保留
#include
#include
#include
int main(){
sigset_t pend, sigproc;
//设置阻塞信号,等待按键产生信号
//先清空
sigemptyset(&sigproc);
//将2号信号(ctrl+c)放在集合里
sigaddset(&sigproc, SIGINT);
//设置阻塞信号集
sigprocmask(SIG_BLOCK, &sigproc, NULL);
//循环取未决信号集中的信号
while(1){
//循环读取未决信号,打印
sigpending(&pend);
for(int i=0; i<32; i++){
//存在信号集中
if(sigismember(&pend, i)==1){
printf("1");
}else{
printf("0");
}
}
printf("\n");
sleep(1);
}
return 0;
}
作用主要是为了防止进程意外死亡
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum 要捕捉的信号
handler 要执行的捕捉函数指针,函数应该声明 void func(int);//函数名可变
修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
成功:0;失败:-1,设置errno
signum: 捕捉的信号
act: 传入参数,新的处理方式。
oldact: 传出参数,旧的处理方式。
struct sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)
重点掌握:
① sa_handler
:函数指针。指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN
表忽略 或 SIG_DFL
表执行默认动作
② sa_mask
: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags
:通常设置为0
,表使用默认属性。
捕捉我们定时给自己发送的14号自杀信号
#include
#include
#include
#include
//定义捕捉函数
void catch_sig(int num){
printf("catch %d sig\n", num);
}
int main(){
//注册捕捉函数
struct sigaction act;
//说明为你使用的是sigaction结构体中的第一个捕捉函数
act.sa_flags=0;
//那个捕捉函数的函数指针指向我们上面自己写的捕捉函数catch_sig
act.sa_handler=catch_sig;
//清空信号集
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
//setitimer 5秒之后每隔3秒来一次信号
struct itimerval myit={{3,0},{5,0}};
setitimer(ITIMER_REAL, &myit, NULL);
while(1){
printf("Who can kill me!\n");
sleep(1);
}
return 0;
}
子进程暂停或者结束运行,其父进程会收到SIGCHLD
信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
我们可以通过捕捉SIGCHLD
信号来回收子进程。
#include
#include
#include
#include
void catch_sig(int num){
pid_t pid=waitpid(-1, NULL, WNOHANG);
if(pid>0){
printf("wait child %d ok\n", pid);
}
}
int main(){
int i=0;
pid_t pid;
for(i=0; i<10; i++){
pid=fork();
if(pid==0){
break;
}
}
if(i==10){
//father
struct sigaction act;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
act.sa_handler=catch_sig;
sigaction(SIGCHLD, &act, NULL);
while(1){
sleep(1);
}
}else if(i<10){
printf("I am %d child, pid=%d\n", i, getpid());
sleep(i);
}
return 0;
}
上面的程序是有问题的,如果十个子进程不是依次死去,而是一起死,就会出现僵尸进程,因为信号不排队的。
把上面程序sleep(i);
注释掉
else if(i<10){
printf("I am %d child, pid=%d\n", i, getpid());
//sleep(i);
}
改进一下上面的代码,让他不出僵尸进程。我们即将信号收集函数catch_sig
里面用一个while循环来循环处理pid收集到的各个SIGCHLD
信号。
#include
#include
#include
#include
void catch_sig(int num){
pid_t pid;
//此时,如果多个子进程一起死,pid对获得多个信号
while((pid=waitpid(-1, NULL, WNOHANG))>0) {
if(pid>0){
printf("wait child %d ok\n", pid);
}
}
}
int main(){
int i=0;
pid_t pid;
for(i=0; i<10; i++){
pid=fork();
if(pid==0){
break;
}
}
if(i==10){
//father
struct sigaction act;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
act.sa_handler=catch_sig;
sigaction(SIGCHLD, &act, NULL);
while(1){
sleep(1);
}
}else if(i<10){
printf("I am %d child, pid=%d\n", i, getpid());
}
return 0;
}
如果注册sigaction
之前,子进程就死了,那么还是会产生僵尸进程。如何避免?
子进程死之前,如果main
函数还没注册sigaction
,就把SIGCHLD
信号屏蔽即可。屏蔽信号的工作,应该在创建子进程之前就开始去做,如此可以避免极端情况下出现差错。
#include
#include
#include
#include
void catch_sig(int num)
{
pid_t wpid ;
while( (wpid = waitpid(-1,NULL,WNOHANG)) > 0 ){
printf("wait child %d ok\n",wpid);
}
}
int main()
{
int i =0;
pid_t pid ;
//在创建子进程之前屏蔽SIGCHLD信号
sigset_t myset,oldset;
sigemptyset(&myset);
sigaddset(&myset,SIGCHLD);
//oldset 保留现场,设置了SIGCHLD到阻塞信号集
sigprocmask(SIG_BLOCK,&myset,&oldset);
for(i = 0 ; i < 10 ; i ++){
pid =fork();
if(pid == 0 ){
break;
}
}
if(i == 10 ){
//parent
//模拟注册晚于子进程死亡
sleep(2);
struct sigaction act ;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = catch_sig;
sigaction(SIGCHLD,&act,NULL);
//解除屏蔽现场
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1){
sleep(1);
}
}else if(i < 10){
printf("I am %d child,pid = %d\n",i,getpid());
}
return 0;
}