進程間(jian)通(tong)信之信號
時(shi)間(jian)(jian):2018-03-13 來源:進程間(jian)(jian)通信講解(jie)
一:信號的基本介紹
信號是(shi)在軟件層次上對(dui)中(zhong)斷(duan)機制(zhi)的一種(zhong)模擬(ni),是(shi)一種(zhong)異(yi)步通(tong)信方式(進(jin)程在運行過程中(zhong),隨時(shi)可(ke)能被各(ge)種(zhong)信號打斷(duan))。
信號可(ke)以直接進行用戶(hu)空(kong)間進程(cheng)和內核(he)進程(cheng)之間的交互,內核(he)進程(cheng)也可(ke)以利用它來通(tong)知用戶(hu)空(kong)間進程(cheng)發生了那(nei)些系(xi)統事件。
如(ru)果(guo)該(gai)進程(cheng)當(dang)前并未處于執行(xing)態(tai),則該(gai)信號就由(you)內核保存起來,直(zhi)到該(gai)進
程恢復執行(xing)再傳(chuan)遞個它;如(ru)果一個信(xin)號(hao)被進(jin)程設置為阻塞,則(ze)該信(xin)號(hao)的傳(chuan)遞被延(yan)遲,直到其阻塞取消時才(cai)被傳(chuan)遞給進(jin)程。
二:信號的產生
A.用戶在終(zhong)(zhong)端(duan)按下某些鍵時,終(zhong)(zhong)端(duan)驅動程(cheng)序會發送信號(hao)給(gei)前臺進程(cheng),例如ctr+c產(chan)生(sheng)SIGINT, ctr + \產(chan)生(sheng)SIGQUI信號(hao),ctr + z產(chan)生(sheng)SIGTSTP。
B.硬(ying)(ying)件(jian)異(yi)常(chang)產生信(xin)號,這(zhe)些(xie)條(tiao)件(jian)由硬(ying)(ying)件(jian)檢測(ce)到并(bing)通(tong)知內(nei)核(he)(he)(he),然(ran)后(hou)內(nei)核(he)(he)(he)向當(dang)前(qian)進(jin)(jin)程(cheng)(cheng)發送適當(dang)的信(xin)號。例(li)如(ru)當(dang)前(qian)進(jin)(jin)程(cheng)(cheng)執行了除以(yi)0的指(zhi)令,CPU的運算單元會產生異(yi)常(chang),內(nei)核(he)(he)(he)將(jiang)這(zhe)個異(yi)常(chang)解(jie)釋(shi)為SIGFPE信(xin)號發送給(gei)進(jin)(jin)程(cheng)(cheng)。再比如(ru)當(dang)前(qian)進(jin)(jin)程(cheng)(cheng)訪問了非法內(nei)存地址,MMU會產生異(yi)常(chang),內(nei)核(he)(he)(he)將(jiang)這(zhe)個異(yi)常(chang)解(jie)釋(shi)為SIGSEGV信(xin)號發送給(gei)當(dang)前(qian)進(jin)(jin)程(cheng)(cheng) 。我們(men)常(chang)見的段錯誤。
C.一(yi)個進程(cheng)調用(yong)int kill(pid_t pid,int sig)函(han)數可以給另一(yi)個進程(cheng)發送(song)信號。
D.可(ke)以用kill命(ming)令給某個(ge)進(jin)程(cheng)發(fa)送(song)(song)信號,如果不明確(que)指定信號則發(fa)送(song)(song)SIGTERM信號,該信號的默認(ren)處理動作是終止進(jin)程(cheng)。
E.當內(nei)核檢測到某種軟件條件發生時也(ye)可以通過信(xin)號通知進(jin)程,例(li)如鬧鐘(zhong)超時產生
SIGALRM信(xin)號(hao),向讀(du)端(duan)已(yi)關閉(bi)的管道寫數(shu)據(ju)時產生SIGPIPE信(xin)號(hao)。
三:linux操作系統支持的信號
A. kill -l命令查看當前(qian)系(xi)統支持的(de)所(suo)有的(de)信號

B:常用(yong)信號的含義
| 信號名 | 含義 | 默認操作 |
|
SIGHUP |
該信號在用戶終端連接(正常或非正常)結束時發出,通常是在終端的控制進程結束時,通知同一會話內的各個作業與控制終端不再關聯。 |
終止 |
|
SIGINT |
該信號在用戶鍵入INTR字符(通常是Ctrl-C)時發出,終端驅動程序發送此信號并送到前臺進程中的每一個進程。 | 終止 |
|
SIGQUIT |
該信號和SIGINT類似,但由QUIT字符(通常是Ctrl-\)來控制。 | 終止 |
|
SIGILL |
該信號在一個進程企圖執行一條非法指令時(可執行文件本身出現錯誤,或者試圖執行數據段、堆棧溢出時)發 出。 |
終止 |
|
SIGFPE |
該信號在發生致命的算術運算錯誤時發出。這里不僅包括浮點運算錯誤,還包括溢出及除數為0等其它所有的算術的錯誤。 |
終止 |
| 信號名 | 含義 | 默認操作 |
|
SIGKILL |
該信號用來立即結束程序的運行,并且不能被阻塞、處理和忽略。 | 終止 |
| SIGALRM | 該信號當一個定時器到時的時候發出。 | 終止 |
| SIGSTOP | 該信號用于暫停一個進程,且不能被阻塞、處理或忽略。 | 暫停進程 |
|
SIGTSTP |
該信號用于暫停交互進程,用戶可鍵入SUSP字符(通常是Ctrl-Z)發出這個信號。 | 暫停進程 |
| SIGCHLD | 子進程改變狀態時,父進程會收到這個信號 | 忽略 |
| SIGABORT | 該信號用于結束進程 | 終止 |
四:linux中進程對信號處理
忽(hu)略信(xin)(xin)號,即(ji)對信(xin)(xin)號不做任何處理,但是有(you)兩(liang)個信(xin)(xin)號不能忽(hu)略:即(ji)SIGKILL及
SIGSTOP。
捕捉信號(hao),定義并注冊信號(hao)處(chu)(chu)理函(han)數,當(dang)信號(hao)發生時(shi),執行(xing)相應的處(chu)(chu)理函(han)數。
【重點】。
執行(xing)缺省操(cao)作(zuo),Linux對每(mei)種信(xin)號都(dou)規定了默(mo)認操(cao)作(zuo)
五:相關API
1:信號的發送(kill和raise)
#include <sys/types.h>
#include <signal.h>
函數(shu)原型:int kill(pid_t pid, int sig); 函數(shu)功能:給進程 id 為 pid 的進程發送信號
函數(shu)參(can)數(shu):@param pid : 發送信號的(de)目標進程的(de) id
@param sig : 發送的信號編號,例如:9(SIGKILL) 返回值:成功調用返回 0 ,失敗返回 -1 ,并設置 errno
#include <signal.h>
函(han)數(shu)原(yuan)型:int raise(int sig); 函(han)數(shu)功能:給當前(qian)進程自己發(fa)送信號(hao)
函(han)數(shu)參數(shu):@param sig : 發送的信號編號
返(fan)回值:成功調用返(fan)回 0 ,失敗返(fan)回 -1 ,并設置 errno
.*練習
我(wo)們(men)通過(guo)終端(duan)kill -9 某個(ge)進程終止過(guo)一(yi)個(ge)進程,現在我(wo)們(men)使用kill函(han)數來終止一(yi)下。過(guo)程:
父(fu)進(jin)(jin)程(cheng)創(chuang)建一(yi)個子(zi)進(jin)(jin)程(cheng),父(fu)進(jin)(jin)程(cheng)拿到(dao)子(zi)進(jin)(jin)程(cheng)的進(jin)(jin)程(cheng)ID;子(zi)進(jin)(jin)程(cheng)中while循環(huan)打印hello,sleep(1);
父進(jin)程sleep(5)之后,給子進(jin)程發送9這個(ge)信號來終止(zhi)子進(jin)程。
2:信號的捕捉(zhuo)(signal)
知識點回顧
void func(int);//函(han)數(shu)的聲明
void (*func)(int);//定義(yi)函(han)數指針,指向void (int)類型的函(han)數typedef int a;//給int類型的a起別名
typedef void (*funcp)(int);//給類型為(wei)void (*)(int);的函數指(zhi)針起別(bie)名(ming)funcp 捕(bu)捉信號的處理過程:

#include <signal.h>
typedef void (*sighandler_t)(int);//指(zhi)向函(han)數的指(zhi)針,表示信號處理函(han)數的形式sighandler_t signal(int signum, sighandler_t handler);
函數功(gong)能 : 將(jiang)信(xin)(xin)號(hao)與(yu)信(xin)(xin)號(hao)處理函數進(jin)行關聯函數參(can)數:@param signum : 信(xin)(xin)號(hao)的編(bian)號(hao)
@param handler : 信號處理函數的指針SIG_DFL : 表(biao)示默認(ren)操(cao)作
SIG_IGN : 表示忽略信號(SIGKILL和SIGSTOP時(shi)不能被忽略的) 返(fan)回值:成功(gong)調用返(fan)回信號處理函數的指針,否則,返(fan)回SIG_ERR

注意:sighandler_t handler中的int保(bao)存的是調(diao)用這個函數(shu)是因為哪個信(xin)號觸發的,帶過來對(dui)應的信(xin)號值。
.* 練習
(1)忽略(lve)ctrl+c對進程的(de)終止信(xin)號。signal(SIGINT, SIG_IGN);
(2)在信號處理函數中將對應的信號的描述信息(xi)進行打印。
.* 練習:
fork前采用(yong)signal信號(hao)處理函(han)數(shu)(shu)不阻塞,不輪詢(xun)的方式回收(shou)僵尸態子進(jin)程(cheng)[waitpid()函(han)數(shu)(shu)]。 在信號(hao)處理函(han)數(shu)(shu)signal_handler()中對(dui)信號(hao)進(jin)行收(shou)尸操作。然后利用(yong)fork函(han)數(shu)(shu)創建一(yi)個子進(jin)程(cheng)。休眠10s后退出。父進(jin)程(cheng)是一(yi)個死循環(huan),每秒輸出"father do something…"的字(zi)符(fu)串。
提示:
子(zi)進(jin)(jin)程(cheng)在終止時會給父(fu)進(jin)(jin)程(cheng)發SIGCHLD,該信(xin)號的(de)(de)默認(ren)處理(li)動作(zuo)是(shi)忽略,父(fu)進(jin)(jin)程(cheng)可(ke)以自定義SIGCHLD信(xin)號的(de)(de)處理(li)函(han)數。我(wo)們這(zhe)里調(diao)用waitpid非阻塞的(de)(de)回(hui)收僵尸態子(zi)進(jin)(jin)程(cheng)。這(zhe)樣父(fu)進(jin)(jin)程(cheng)只(zhi)需要專心處理(li)自己的(de)(de)工作(zuo),不必關(guan)心子(zi)進(jin)(jin)程(cheng)了,子(zi)進(jin)(jin)程(cheng)終止時會通知父(fu)進(jin)(jin)程(cheng),父(fu)進(jin)(jin)程(cheng)在信(xin)號處理(li)函(han)數中調(diao)用waitpid函(han)數清理(li)子(zi)進(jin)(jin)程(cheng)即可(ke)。
一般(ban)信號(hao)對僵(jiang)尸(shi)態子進(jin)程的(de)處理方法:
<1>父進程(cheng)采用(yong)signal(SIGCHLD, hand_signal),采用(yong)信號處理(li)函(han)(han)數(shu),對(dui)接(jie)收到的(de)SIGCHLD進行(xing)進行(xing)處理(li)。在接(jie)收到SIGCHLD信號的(de)時候,采用(yong)waitpid利用(yong)非阻塞的(de)方(fang)式的(de)釋(shi)放它們的(de)資源。若是(shi)使(shi)用(yong)wait()函(han)(han)數(shu)的(de)話,父進程(cheng)會(hui)阻塞。 [推薦使(shi)用(yong)]
<2>父進(jin)程采用signal(SIGCHLD, SIG_IGN),忽略SIGCHLD信號(hao),這(zhe)樣子進(jin)程結束(shu)后,就不需要(yao)父進(jin)程來wait和(he)釋放資源。它會(hui)自動(dong)被過(guo)繼給老(lao)祖宗(zong)init進(jin)程,int進(jin)程會(hui)負責釋放他的(de)資源,這(zhe)樣就不會(hui)產生僵尸態子進(jin)程。
3:定(ding)時鬧鐘函數(alarm)
unsigned int alarm(unsigned int seconds);
函數功能:給(gei)進(jin)程(cheng)啟動(dong)一個定時(shi)器,經過seconds秒(miao)后把SIGALRM信(xin)號(hao)發送給(gei)當前進(jin)程(cheng)。函數參(can)數:@seconds 秒(miao)
返回值:成功返回0,失(shi)敗返回 -1
注意:一個(ge)進程只能有一個(ge)鬧鐘(zhong)事件,若是多次使用(yong)alarm函(han)數(shu),則鬧鐘(zhong)時間被刷新(xin)。
.*練習
(1)main函數中設置(zhi)2s定時(shi)器(qi),然(ran)后注(zhu)冊(ce)SIGALRM信(xin)號(hao)的處理(li)函數,處理(li)函數中打印當(dang)前(qian)時(shi)間到(dao)屏幕(mu)上
(2)我們現實中經常有這樣的需求,需要每隔2s執行某個函數,這樣怎么處理呢? 答案:在定時器處理函數里邊,再次刷新鬧鐘alarm(2);
#include <stdio.h>
#include <time.h>
#include <signal.h>
void handler(int sig)
{
time_t tim = time(NULL);
struct tm *ptm = localtime(&tim);
printf("
d-
02d-
02d
02d:
02d:
02d\n", ptm->tm_year+1900,
ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
alarm(1);
return;
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, handler); alarm(1);
int n = 0; while(1)
{
printf("n =
d\n", ++n); usleep(200);
}
return 0;
}
4:信號(hao)的等待(pause)
int pause(void);
特點:掛起一個進程(cheng)(cheng),直到進程(cheng)(cheng)收到一個信(xin)號,進程(cheng)(cheng)會繼續執(zhi)行
上(shang)邊的練習,在while循環中pause()一下。現象:不加(jia)pause()之前printf("n = d\n",
++n);會200ms打印一(yi)次(ci)(ci),然(ran)后(hou)1秒打印一(yi)次(ci)(ci)時間(jian),加上pause()之后(hou),現象則(ze)是1s打印一(yi)次(ci)(ci)printf("n = d\n", ++n),時間(jian)也是1s一(yi)次(ci)(ci)。因為(wei)pause()會將(jiang)進(jin)程掛起,接收到信號之后(hou)會繼續,進(jin)程1s接收一(yi)次(ci)(ci)ALARM信號,則(ze)進(jin)程會1s會被喚醒一(yi)次(ci)(ci)。
time_t tim = time(NULL);
struct tm *ptm = localtime(&tim);
printf("
d-
02d-
02d
02d:
02d:
02d\n", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour,
ptm->tm_min, ptm->tm_sec); alarm(1);
return;
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, handler); alarm(1);
int n = 0; while(1)
{
pause();
printf("n =
d\n", ++n); usleep(200);
}
return 0;
}

