Linux下IPC方式之信号2

Linux下IPC方式之信号2

  • 3. 信号集操作函数
    • 3.1 信号集设定
    • 3.2 sigprocmask函数
    • 3.3 sigpending函数
    • 3.4 把所有常规信号的未决信号集打到屏幕上
  • 4. 信号捕捉(重要)
    • 4.1 signal函数
    • 4.2 sigaction函数
    • 4.3 信号捕捉特性
    • 4.4 内核实现信号捕捉过程
  • 5. SIGCHLD信号(重要)
    • 5.1 SIGCHLD的产生条件
    • 5.2 借助SIGCHLD信号回收子进程

上接Linux下IPC方式之信号1

3. 信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。

3.1 信号集设定

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 函数。

3.2 sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(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返回前,至少将其中一个信号递达。

Linux下IPC方式之信号2_第1张图片

3.3 sigpending函数

读取当前进程的未决信号集

int sigpending(sigset_t *set);	
set传出参数。   
返回值:成功:0;失败:-1,设置errno

3.4 把所有常规信号的未决信号集打到屏幕上

将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;
}

Linux下IPC方式之信号2_第2张图片

4. 信号捕捉(重要)

作用主要是为了防止进程意外死亡

4.1 signal函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signum 		要捕捉的信号
handler 	要执行的捕捉函数指针,函数应该声明 void func(int);//函数名可变

4.2 sigaction函数

修改信号处理动作(通常在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;
}

运行结果:
Linux下IPC方式之信号2_第3张图片

4.3 信号捕捉特性

  1. 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
  2. XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
  3. 阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)

4.4 内核实现信号捕捉过程

Linux下IPC方式之信号2_第4张图片

5. SIGCHLD信号(重要)

5.1 SIGCHLD的产生条件

  1. 子进程终止时
  2. 子进程接收到SIGSTOP信号停止时
  3. 子进程处在停止态,接受到SIGCONT后唤醒时

5.2 借助SIGCHLD信号回收子进程

子进程暂停或者结束运行,其父进程会收到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;
}

Linux下IPC方式之信号2_第5张图片
上面的程序是有问题的,如果十个子进程不是依次死去,而是一起死,就会出现僵尸进程,因为信号不排队的。
把上面程序sleep(i);注释掉

else if(i<10){
		printf("I am %d child, pid=%d\n", i, getpid());
		//sleep(i);
	}

运行结果:
Linux下IPC方式之信号2_第6张图片

改进一下上面的代码,让他不出僵尸进程。我们即将信号收集函数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;
}

Linux下IPC方式之信号2_第7张图片
如果注册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;
}

你可能感兴趣的:(C++系统编程)