Linux下IPC方式之信號2

上接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返回前,至少將其中一個信號遞達。

在這裏插入圖片描述

3.3 sigpending函數

讀取當前進程的未決信號集

int sigpending(sigset_t *set);	
set傳出參數。   
返回值:成功:0;失敗:-1,設置errno

3.4 把所有常規信號的未決信號集打到屏幕上

將2號信號(ctrl+c)放在阻塞信號集上,這樣該信號在未決信號集上會進行保留

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
 
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;
}

在這裏插入圖片描述

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<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>
 
//定義捕捉函數
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;
}

運行結果:
在這裏插入圖片描述

4.3 信號捕捉特性

  1. 進程正常運行時,默認PCB中有一個信號屏蔽字,假定爲☆,它決定了進程自動屏蔽哪些信號。當註冊了某個信號捕捉函數,捕捉到該信號以後,要調用該函數。而該函數有可能執行很長時間,在這期間所屏蔽的信號不由☆來指定。而是用sa_mask來指定。調用完信號處理函數,再恢復爲☆。
  2. XXX信號捕捉函數執行期間,XXX信號自動被屏蔽。
  3. 阻塞的常規信號不支持排隊,產生多次只記錄一次。(後32個實時信號支持排隊)

4.4 內核實現信號捕捉過程

在這裏插入圖片描述

5. SIGCHLD信號(重要)

5.1 SIGCHLD的產生條件

  1. 子進程終止時
  2. 子進程接收到SIGSTOP信號停止時
  3. 子進程處在停止態,接受到SIGCONT後喚醒時

5.2 藉助SIGCHLD信號回收子進程

子進程暫停或者結束運行,其父進程會收到SIGCHLD信號。該信號的默認處理動作是忽略。可以捕捉該信號,在捕捉函數中完成子進程狀態的回收。
我們可以通過捕捉SIGCHLD信號來回收子進程。

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
 
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<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
 
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 <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
 
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章