linux進程間通信(三)-------信號

#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void my_func(int signum){
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror("sigemptyset");
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprcmask");
esle{
printf("blocked\n");
sleep(5);
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)
perror("sigprocmask");
else
printf("unblock\n");
while(1){
if(sigismember(&set,SIGINT)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
}else if(sigismember(&set,SIGQUIT)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DEL;
sigaction(SIGTERM,&action2,NULL);
}
}
}

信號概述
信號是軟件中斷。信號(signal)機制是Unix系統中最爲古老的進程之間的能信機制。它用於在一個或多個進程之間傳遞異步信號。很多條件可以產生一個信號。
A、當用戶按某些終端鍵時,產生信號。在終端上按DELETE鍵通常產生中斷信號(SIGINT)。這是停止一個已失去控制程序的方法。
B、硬件異常產生信號:除數爲0、無效的存儲訪問等等。這些條件通常由硬件檢測到,並將其通知內核。然後內核爲該條件發生時正在運行的進程產生適當的信號。例如,對於執行一個無效存儲訪問的進程產生一個SIGSEGV。
C、進程用kill(2)函數可將信號發送給另一個進程或進程組。自然,有些限制:接收信號進和發送信號進程的所有都必須相同,或發送信號進程的的所有者必須是超級用戶。
D、用戶可用Kill(ID 值)命令將信號發送給其它進程。此程序是Kill函數的界面。常用此命令終止一個失控的後臺進程。
E、當檢測到某種軟件條件已經發生,並將其通知有關進程時也產生信號。這裏並不是指硬件產生條件(如被0除),而是軟件條件。例如SIGURG(在網絡連接上傳來非規定波特率的數據)、SIGPIPE(在管道的讀進程已終止後一個進程寫此管道),以及SIGALRM(進程所設置的鬧鐘時間已經超時)。
內核爲進程生產信號,來響應不同的事件,這些事件就是信號源。主要信號源如下:
(1)異常:進程運行過程中出現異常;
(2)其它進程:一個進程可以向另一個或一組進程發送信號;
(3)終端中斷:Ctrl-c,Ctro-\等;
(4)作業控制:前臺、後臺進程的管理;
(5)分配額:CPU超時或文件大小突破限制;
(6)通知:通知進程某事件發生,如I/O就緒等;
(7)報警:計時器到期;
Linux中的信號
1、SIGHUP 2、SIGINT(終止) 3、SIGQUIT(退出) 4、SIGILL 5、SIGTRAP 6、SIGIOT  7、SIGBUS   8、SIGFPE   9、SIGKILL 10、SIGUSER 11、 SIGSEGV SIGUSER 12、 SIGPIPE 13、SIGALRM 14、SIGTERM 15、SIGCHLD 16、SIGCONT 17、SIGSTOP 18、SIGTSTP 19、SIGTTIN 20、SIGTTOU 21、SIGURG 22、SIGXCPU 23、SIGXFSZ 24、SIGVTALRM 25、SIGPROF 26、SIGWINCH 27、SIGIO 28、SIGPWR
常用的信號:
SIGHUP:從終端上發出的結束信號;
SIGINT:來自鍵盤的中斷信號(Ctrl+c)
SIGQUIT:來自鍵盤的退出信號;
SIGFPE:浮點異常信號(例如浮點運算溢出);
SIGKILL:該信號結束接收信號的進程;
SIGALRM:進程的定時器到期時,發送該信號;
SIGTERM:kill命令生出的信號;
SIGCHLD:標識子進程停止或結束的信號;
SIGSTOP:來自鍵盤(Ctrl-Z)或調試程序的停止掃行信號
可以要求系統在某個信號出現時按照下列三種方式中的一種進行操作。
(1)忽略此信號。大多數信號都可使用這種方式進行處理,但有兩種信號卻決不能被忽略。它們是:SIGKILL和SIGSTOP。這兩種信號不能被忽略的,原因是:它們向超級用戶提供一種使進程終止或停止的可靠方法。另外,如果忽略某些由硬件異常產生的信號(例如非法存儲訪問或除以0),則進程的行爲是示定義的。
(2)捕捉信號。爲了做到這一點要通知內核在某種信號發生時,調用一個用戶函數。在用戶函數中,可執行用戶希望對這種事件進行的處理。如果捕捉到SIGCHLD信號,則表示子進程已經終止,所以此信號的捕捉函數可以調用waitpid以取得該子進程的進程ID以及它的終止狀態。
(3)執行系統默認動作。對大多數信號的系統默認動作是終止該進程。每一個信號都有一個缺省動作,它是當進程沒有給這個信號指定處理程序時,內核對信號的處理。有5種缺省的動作:
(1)異常終止(abort):在進程的當前目錄下,把進程的地址空間內容、寄存器內容保存到一個叫做core的文件中,而後終止進程。
(2)退出(exit):不產生core文件,直接終止進程。
(3)忽略(ignore):忽略該信號。
(4)停止(stop):掛起該進程。
(5)繼續(contiune):如果進程被掛起,剛恢復進程的動行。否則,忽略信號。
信號的發送與捕捉
kill()和raise()
kill()不僅可以中止進程,也可以向進程發送其他信號。
與kill函數不同的是,raise()函數運行向進程自身發送信號
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
兩個函數返回:若成功則爲0,若出錯則爲-1。
kill的pid參數有四種不同的情況:
(1)pid>0將信號發送給進程ID爲pid的進程。
(2)pid==0將信號發送給其進程組ID等於發送進程的進程組ID,而且發送進程有許可權向其發送信號的所有進程。
(3)pid<0將信號發送給其進程組ID等於pid絕對值,而且發送進程有許可權向其發送信號的所有進程。如上所述一樣,“所有進程”並不包括系統進程集中的進程。
(4)pid==-1 POSIX.1未定義種情況
kill.c 
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0){
perro("fork");
exit(1);
}
if(pid==0){
raise(SIGSTOP);
exit(0);
}else {
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0){
if((ret=kill(pid,SIGKILL))==0)
printf("kill %d\n",pid);
else{
perror("kill");
}
}
}
}

alarm和pause函數
使用alarm函數可以設置一個時間值(鬧鐘時間),在將來的某個時刻時間值會被超過。當所設置的時間被超過後,產生SIGALRM信號。如果不忽略或不捕捉引信號,則其默認動作是終止該進程。
#include<unistd.h>
unsigned int alarm(unsigned int secondss);
返回:0或以前設置的鬧鐘時間的餘留秒數。
參數seconds的值是秒數,經過了指定的seconds秒後產生信號SIGALRM。每個進程只能有一個鬧鐘時間。如果在調用alarm時,以前已爲該進程設置過鬧鐘時間,而且它還沒有超時,則該鬧鐘時間的餘留值作爲本次alarm函數調用的值返回。以前登記的鬧鐘時間則被新值代換。
如果有以前登記的尚未超過的鬧鐘時間,而且seconds值是0,則取消以前的鬧鐘時間,其餘留值仍作爲函數的返回值。

pause函數使用調用進程掛起直至捕捉到一個信號
#include<unistd.h>
int pause(void);
返回:-1,errno設置爲EINTR
只有執行了一信號處理程序並從其返回時,pause才返回。
alarm.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int ret;
ret=alarm(5);
pause();
printf("I have been waken up.\n",ret);
}

信號的處理
當系統捕捉到某個信號時,可以忽略誰信號或是使用指定的處理函數來處理該信號,或者使用系統默認的方式。信號處理的主要方式有兩種,一種是使用簡單的signal函數,別一種是使用信號集函數組。
signal()
#include<signal.h>
void (*signal (int signo,void (*func)(int)))(int)
返回:成功則爲以前的信號處理配置,若出錯則爲SIG_ERR
func的值是:(a)常數SIGIGN,或(b)常數SIGDFL,或(c)當接到此信號後要調用的的函數的地址。如果指定SIGIGN,則向內核表示忽略此信號(有兩個信號SIGKILL和SIGSTOP不能忽略)。如果指定SIGDFL,則表示接到此信號後的動作是系統默認動作。當指定函數地址時,我們稱此爲捕捉此信號。我們稱此函數爲信號處理程序(signal handler)或信號捕捉函數(signal-catching funcgion).signal函數原型太複雜了,如果使用下面的typedef,則可以使其簡化。
type void sign(int);
sign *signal(int,handler *);
實例見:mysignal.c
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
 printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
 printf("I have get SIGQUIT\n");
}
int main()
{
 printf("Waiting for signal SIGINT or SIGQUTI\n");
 signal(SIGINT,my_func);
 signal(SIGQUIT,my_func);
 pasue();
 exit(0);
}

信號集函數組
我們需要有一個能表示多個信號——信號集(signal set)的數據類型。將在sigprocmask()這樣的函數中使用這種數據類型,以告訴內核不允許發生該信號集中的信號。信號集函數組包含水量幾大模塊:創建函數集、登記信號集、檢測信號集。
圖見附件。
創建函數集
#include<signal.h>
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set,int signo );
int sigdelset(sigset_t* set,int signo);
四個函數返回:若成功則爲0,若出錯則爲-1
int sigismember(const sigset_t* set,int signo);
返回:若真則爲1,若假則爲0;
signemptyset:初始化信號集合爲空。
sigfillset:初始化信號集合爲所有的信號集合。
sigaddset:將指定信號添加到現存集中。
sigdelset:從信號集中刪除指定信號。
sigismember:查詢指定信號是否在信號集中。
登記信號集
登記信號處理機主要用於決定進程如何處理信號。首先要判斷出當前進程阻塞能不能傳遞給該信號的信號集。這首先使用sigprocmask函數判斷檢測或更改信號屏蔽字,然後使用sigaction函數改變進程接受到特定信號之後的行爲。
一個進程的信號屏蔽字可以規定當前阻塞而不能遞送給該進程的信號集。調用函數sigprocmask可以檢測或更改(或兩者)進程的信號屏蔽字。
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功則爲0,若出錯則爲-1
oset是非空指針,進程是當前信號屏蔽字通過oset返回。其次,若set是一個非空指針,則參數how指示如何修改當前信號屏蔽字。
用sigprocmask更改當前信號屏蔽字的方法。how參數設定:
SIG_BLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set指向信號集的並集。set包含了我們希望阻塞的附加信號。
SIG_NUBLOCK該進程新的信號屏蔽字是其當前信號屏蔽字和set所指向信號集的交集。set包含了我們希望解除阻塞的信號。
SIG_SETMASK該進程新的信號屏蔽是set指向的值。如果set是個空指針,則不改變該進程的信號屏蔽字,how的值也無意義。
sigaction函數的功能是檢查或修改(或兩者)與指定信號相關聯的處理動作。此函數取代了UNIX早期版本使用的signal函數。
#include<signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
返回:若成功則爲0,若出錯則爲-1
參數signo是要檢測或修改具體動作的信號的編號數。若act指針非空,則要修改其動作。如果oact指針爲空,則系統返回該信號的原先動作。此函數使用下列結構:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
sa_handler是一個函數指針,指定信號關聯函數,可以是自定義處理函數,還可以SIG_DEF或SIG_IGN;
sa_mask是一個信號集,它可以指定在信號處理程序執行過程中哪些信號應當被阻塞。
sa_flags中包含許多標誌位,是對信號進行處理的各種選項。具體如下:
SA_NODEFER\SA_NOMASK:當捕捉到此信號時,在執行其信號捕捉函數時,系統不會自動阻塞此信號。
SA_NOCLDSTOP:進程忽略子進程產生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTOU信號
SA_RESTART:可讓重啓的系統調用重新起作用。
SA_ONESHOT\SA_RESETHAND:自定義信號只執行一次,在執行完畢後恢復信號的系統默認動作。
檢測信號是信號處理的後續步驟,但不是必須的。sigpending函數運行進程檢測“未決“信號(進程不清楚他的存在),並進一步決定對他們做何處理。
sigpending返回對於調用進程被阻塞不能遞送和當前未決的信號集。
#include<signal.h>
int sigpending(sigset_t * set);
返回:若成功則爲0,若出錯則爲-1
信號集實例見:sigaction.c
發佈了13 篇原創文章 · 獲贊 2 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章