一:信號的基本介紹
信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式(進程在運行過程中,隨時可能被各種信號打斷)。
信號可以直接進行用戶空間進程和內核進程之間的交互,內核進程也可以利用它來通知用戶空間進程發生了那些系統事件。
如果該進程當前并未處于執行態,則該信號就由內核保存起來,直到該進
程恢復執行再傳遞個它;如果一個信號被進程設置為阻塞,則該信號的傳遞被延遲,直到其阻塞取消時才被傳遞給進程。
二:信號的產生
A.用戶在終端按下某些鍵時,終端驅動程序會發送信號給前臺進程,例如ctr+c產生SIGINT, ctr + \產生SIGQUI信號,ctr + z產生SIGTSTP。
B.硬件異常產生信號,這些條件由硬件檢測到并通知內核,然后內核向當前進程發送適當的信號。例如當前進程執行了除以0的指令,CPU的運算單元會產生異常,內核將這個異常解釋為SIGFPE信號發送給進程。再比如當前進程訪問了非法內存地址,MMU會產生異常,內核將這個異常解釋為SIGSEGV信號發送給當前進程 。我們常見的段錯誤。
C.一個進程調用int kill(pid_t pid,int sig)函數可以給另一個進程發送信號。
D.可以用kill命令給某個進程發送信號,如果不明確指定信號則發送SIGTERM信號,該信號的默認處理動作是終止進程。
E.當內核檢測到某種軟件條件發生時也可以通過信號通知進程,例如鬧鐘超時產生
SIGALRM信號,向讀端已關閉的管道寫數據時產生SIGPIPE信號。
三:linux操作系統支持的信號
A. kill -l命令查看當前系統支持的所有的信號
B:常用信號的含義
信號名 | 含義 | 默認操作 |
SIGHUP |
該信號在用戶終端連接(正常或非正常)結束時發出,通常是在終端的控制進程結束時,通知同一會話內的各個作業與控制終端不再關聯。 |
終止 |
SIGINT |
該信號在用戶鍵入INTR字符(通常是Ctrl-C)時發出,終端驅動程序發送此信號并送到前臺進程中的每一個進程。 | 終止 |
SIGQUIT |
該信號和SIGINT類似,但由QUIT字符(通常是Ctrl-\)來控制。 | 終止 |
SIGILL |
該信號在一個進程企圖執行一條非法指令時(可執行文件本身出現錯誤,或者試圖執行數據段、堆棧溢出時)發 出。 |
終止 |
SIGFPE |
該信號在發生致命的算術運算錯誤時發出。這里不僅包括浮點運算錯誤,還包括溢出及除數為0等其它所有的算術的錯誤。 |
終止 |
信號名 | 含義 | 默認操作 |
SIGKILL |
該信號用來立即結束程序的運行,并且不能被阻塞、處理和忽略。 | 終止 |
SIGALRM | 該信號當一個定時器到時的時候發出。 | 終止 |
SIGSTOP | 該信號用于暫停一個進程,且不能被阻塞、處理或忽略。 | 暫停進程 |
SIGTSTP |
該信號用于暫停交互進程,用戶可鍵入SUSP字符(通常是Ctrl-Z)發出這個信號。 | 暫停進程 |
SIGCHLD | 子進程改變狀態時,父進程會收到這個信號 | 忽略 |
SIGABORT | 該信號用于結束進程 | 終止 |
四:linux中進程對信號處理
忽略信號,即對信號不做任何處理,但是有兩個信號不能忽略:即SIGKILL及
SIGSTOP。
捕捉信號,定義并注冊信號處理函數,當信號發生時,執行相應的處理函數。
【重點】。
執行缺省操作,Linux對每種信號都規定了默認操作
五:相關API
1:信號的發送(kill和raise)
#include <sys/types.h>
#include <signal.h>
函數原型:int kill(pid_t pid, int sig); 函數功能:給進程 id 為 pid 的進程發送信號
函數參數:@param pid : 發送信號的目標進程的 id
@param sig : 發送的信號編號,例如:9(SIGKILL) 返回值:成功調用返回 0 ,失敗返回 -1 ,并設置 errno
#include <signal.h>
函數原型:int raise(int sig); 函數功能:給當前進程自己發送信號
函數參數:@param sig : 發送的信號編號
返回值:成功調用返回 0 ,失敗返回 -1 ,并設置 errno
.*練習
我們通過終端kill -9 某個進程終止過一個進程,現在我們使用kill函數來終止一下。過程:
父進程創建一個子進程,父進程拿到子進程的進程ID;子進程中while循環打印hello,sleep(1);
父進程sleep(5)之后,給子進程發送9這個信號來終止子進程。
2:信號的捕捉(signal)
知識點回顧
void func(int);//函數的聲明
void (*func)(int);//定義函數指針,指向void (int)類型的函數typedef int a;//給int類型的a起別名
typedef void (*funcp)(int);//給類型為void (*)(int);的函數指針起別名funcp 捕捉信號的處理過程:
#include <signal.h>
typedef void (*sighandler_t)(int);//指向函數的指針,表示信號處理函數的形式sighandler_t signal(int signum, sighandler_t handler);
函數功能 : 將信號與信號處理函數進行關聯函數參數:@param signum : 信號的編號
@param handler : 信號處理函數的指針SIG_DFL : 表示默認操作
SIG_IGN : 表示忽略信號(SIGKILL和SIGSTOP時不能被忽略的) 返回值:成功調用返回信號處理函數的指針,否則,返回SIG_ERR
注意:sighandler_t handler中的int保存的是調用這個函數是因為哪個信號觸發的,帶過來對應的信號值。
.* 練習
(1)忽略ctrl+c對進程的終止信號。signal(SIGINT, SIG_IGN);
(2)在信號處理函數中將對應的信號的描述信息進行打印。
.* 練習:
fork前采用signal信號處理函數不阻塞,不輪詢的方式回收僵尸態子進程[waitpid()函數]。 在信號處理函數signal_handler()中對信號進行收尸操作。然后利用fork函數創建一個子進程。休眠10s后退出。父進程是一個死循環,每秒輸出"father do something…"的字符串。
提示:
子進程在終止時會給父進程發SIGCHLD,該信號的默認處理動作是忽略,父進程可以自定義SIGCHLD信號的處理函數。我們這里調用waitpid非阻塞的回收僵尸態子進程。這樣父進程只需要專心處理自己的工作,不必關心子進程了,子進程終止時會通知父進程,父進程在信號處理函數中調用waitpid函數清理子進程即可。
一般信號對僵尸態子進程的處理方法:
<1>父進程采用signal(SIGCHLD, hand_signal),采用信號處理函數,對接收到的SIGCHLD進行進行處理。在接收到SIGCHLD信號的時候,采用waitpid利用非阻塞的方式的釋放它們的資源。若是使用wait()函數的話,父進程會阻塞。 [推薦使用]
<2>父進程采用signal(SIGCHLD, SIG_IGN),忽略SIGCHLD信號,這樣子進程結束后,就不需要父進程來wait和釋放資源。它會自動被過繼給老祖宗init進程,int進程會負責釋放他的資源,這樣就不會產生僵尸態子進程。
3:定時鬧鐘函數(alarm)
unsigned int alarm(unsigned int seconds);
函數功能:給進程啟動一個定時器,經過seconds秒后把SIGALRM信號發送給當前進程。函數參數:@seconds 秒
返回值:成功返回0,失敗返回 -1
注意:一個進程只能有一個鬧鐘事件,若是多次使用alarm函數,則鬧鐘時間被刷新。
.*練習
(1)main函數中設置2s定時器,然后注冊SIGALRM信號的處理函數,處理函數中打印當前時間到屏幕上
(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:信號的等待(pause)
int pause(void);
特點:掛起一個進程,直到進程收到一個信號,進程會繼續執行
上邊的練習,在while循環中pause()一下。現象:不加pause()之前printf("n = d\n",
++n);會200ms打印一次,然后1秒打印一次時間,加上pause()之后,現象則是1s打印一次printf("n = d\n", ++n),時間也是1s一次。因為pause()會將進程掛起,接收到信號之后會繼續,進程1s接收一次ALARM信號,則進程會1s會被喚醒一次。
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;
}