訊號章節 -- 訊號章節總體概要
訊號基本概念
訊號是非同步事件,傳送訊號的執行緒可以繼續向下執行而不阻塞。
訊號無優先順序。
1到31號訊號是非實時訊號,傳送的訊號可能會丟失,不支援訊號排隊。
31號訊號到64是實時訊號, 傳送的訊號都會被接收, 支援訊號排隊。
訊號在Linux核心標頭檔案中的巨集定義
訊號的處理
由於程式啟動時,SIGUSR1和SIGUSR2被忽略,一般我們可以在有需要時,去捕獲這兩個訊號,進而呼叫自己的處理函式。相應的,我們的程式其他地方去傳送相應的訊號。
signal函式原型 以及使用時所要包含的標頭檔案
和下面的是等價的:
實驗1 signal基本使用
實驗1.1
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
//定義訊號處理函式
//signo: 程式捕獲到的訊號
void sig_handler(int signo){
printf("%d, %d occured \n", getpid(), signo);
}
int main(){
#if 1 // 遮蔽這塊程式碼,就是不捕獲這倆訊號
//向核心登記訊號處理函式以及訊號值
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal error");
}
if(signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
}
#endif
while(1){
sleep(1);
printf("- hello -\n");
};
return 0;
}
編譯執行
同時,根據這裡的列印也可以看出,
SIGINT訊號就是2號訊號, 我們在鍵盤上按下CTRL+C就可以傳送該訊號了。
SIGTSTP訊號就是20號訊號,我們在鍵盤上按下CTRL+Z就可以傳送該訊號了。20號訊號的備註就是 Keyboard stop, 即通過鍵盤發訊號讓程式停止。
實驗1.2
如果遮蔽實驗1內捕獲這倆訊號的程式碼塊,執行效果如下
常用知識點補充:
19) SIGSTOP 20) SIGTSTP
19號訊號和29號訊號的相同點: 都可以使得程式暫停,並且收到SIGCONT訊號後可以讓程式重新執行。
19號訊號和29號訊號的不同點: SIGSTOP不可以捕獲(即使用訊號處理函式)。
那麼,我們來讓剛才停止的a.out繼續執行吧:
先檢視a.out的pid
可見a.out的pid是8349
我們通過kill來發SIGCONT訊號(18號訊號)讓a.out繼續執行
可見,a.out又繼續執行起來了,
然而,需要注意的是,通過18號訊號被繼續執行的程式:當終端內按下CTRL+C,則不能使得該程式終止了;且按下CTRL+Z,終端內也毫無跡象;但是可以通過kill -9被殺死。
根據實測,是這樣的,事實勝於雄辯。這個問題的原因以及背後隱藏的暫時我們還不知的相應知識點,可以留待以後探索,我們先暫且知道這麼一回事就行了。
實驗2 SIG_DFL 和 SIG_IGN 使用
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
//定義訊號處理函式
//signo: 程式捕獲到的訊號
void sig_handler(int signo){
printf("%d, %d occured \n", getpid(), signo);
}
int main(){
printf("pid: %d \n", getpid());
#if 1 // 遮蔽這塊程式碼,就是不捕獲這倆訊號
//向核心登記訊號處理函式以及訊號值
if(signal(SIGTSTP, SIG_IGN) == SIG_ERR){
perror("signal error");
}
if(signal(SIGINT, SIG_DFL) == SIG_ERR){
perror("signal sigint error");
}
#endif
while(1){
sleep(1);
printf("- hello -\n");
};
return 0;
}
此時,按下CTRL+Z對程式執行將毫無影響,而CTRL+C則採用預設方式,即結束程式。
實驗3 SIGUSR1 和 SIGUSR2 使用
注意,這兩個訊號在程式啟動時預設是被忽略的。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
//定義訊號處理函式
//signo: 程式捕獲到的訊號
void sig_handler(int signo){
printf("%d, %d occured \n", getpid(), signo);
}
int main(){
printf("pid: %d \n", getpid());
#if 1 // 遮蔽這塊程式碼,就是不捕獲這倆訊號
//向核心登記訊號處理函式以及訊號值
if(signal(SIGUSR1, sig_handler) == SIG_ERR){
perror("signal error");
}
if(signal(SIGUSR2, sig_handler) == SIG_ERR){
perror("signal sigint error");
}
#endif
while(1){
sleep(1);
printf("- hello -\n");
};
return 0;
}
編譯執行,同時在另一個終端內傳送訊號 kill -SIGUSR1 10452 、 kill -SIGUSR2 10452
實驗4
知識點:SIGKILL 和 SIGSTOP不能被忽略,也不能被捕獲。
本實驗將嘗試捕獲SIGKILL和SIGSTOP,並以SIG_IGN的方式程式處理。
核心程式碼展示:
編譯執行將返回SIG_ERR,如下圖所示
實驗5 SIGCHLD
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//定義訊號處理函式
//signo: 程式捕獲到的訊號
void sig_handler(int signo){
printf("%d, %d occured \n", getpid(), signo);
wait(NULL);
}
int main(void)
{
pid_t pid;
if(signal(SIGCHLD, sig_handler) == SIG_ERR){
perror("signal error");
}
pid = fork();
if (pid < 0)
{
printf("fork error");
}
else if (pid == 0) /* first child : 子程式 */
{
sleep(2);
printf("pid: child =%ld\n", (long)getpid());
exit(0);
}
// 使用訊號的方式,父程式不必在此處阻塞呼叫wait,可以繼續向下執行自己的任務。
while(1){
sleep(1);
printf("father can does his own things \n");
}
}
編譯執行:
實驗中可見,父程式收到了子程式的17號訊號,17號訊號就是SIGCHLD訊號(或寫作SIGCLD)
使用訊號來回收子程式後,父程式不必在阻塞呼叫wait,可以繼續向下執行自己的任務。這個例子充分體現出了訊號是一個非同步事件。
在父程式還存活的期間,子程式退出將不會產生殭屍程式。
PS:父程式死後,肯定不會有其子程式還仍然是殭屍程式,因為一個子程式們會在其父程式死後被1號程式領養,進而被1號程式回收掉所佔用的系統資源。
.