Linux常見訊號介紹
1、訊號
首先訊號我們要和訊號量區分開來,雖然兩者都是作業系統程序通訊的方式。可以簡單的理解,訊號是用來通知程序發生了什麼需要做什麼,訊號量一般是用作程序同步(pv操作)
2、常見訊號量
(以下數字標號代表訊號再bitmap中的位置)
2SIGINT 可能使我們最常用的訊號之一。一般在我們想程序中斷,鍵盤輸入Ctrl + C 即可實現,這是一個程序終止訊號。
3 SIGQUIT程式異常退出訊號和2 類似, 輸入Ctrl + \ 實現
4SIGILL 執行了非法指令. 通常是因為可執行檔案本身出現錯誤, 或者試圖執行資料段. 堆疊溢位時也有可能產生這個訊號。
11SIGSEGV試圖訪問未分配給自己的記憶體, 或試圖往沒有寫許可權的記憶體地址寫資料.和SIGILL一樣,程式出BUG時,我們經常見到。
17 SIGCHLD 子程序結束時, 父程序會收到這個訊號。如果父程序沒有處理這個訊號,也沒有等待(wait)子程序,子程序雖然終止,但是還會在核心程序表中佔有表項,這時的子程序稱為殭屍程序。
這種情況我們應該避免(父程序或者忽略SIGCHILD訊號,或者捕捉它,或者wait它派生的子程序,或者父程序先終止,這時子程序的終止自動由init程序來接管)。後面會詳細介紹。
19 SIGSTOP 停止(stopped)程序的執行. 注意它和terminate以及interrupt的區別:該程序還未結束, 只是暫停執行. 本訊號不能被阻塞, 處理或忽略. GDB中就使用了該訊號。
20 SIGIO 檔案描述符準備就緒, 可以開始進行輸入/輸出操作.
3、訊號常見操作
我們從程序說起,在程序的控制塊中(PCB)有兩個表一個叫做訊號未決表,一個叫做訊號阻塞表(都是64點陣圖儲存)。核心首先根據訊號阻塞表中遮蔽狀態字判斷是否需要阻塞,如果該訊號被設為為了阻塞的,那麼訊號未決表中對應 狀態字(pending)相應位製成1;若該訊號阻塞解除,訊號未
決狀態字(pending)相應位製成0;表示訊號此時可以抵達了,也就是可以接收該訊號了。其實由這個地方我們可以看到,同一個時刻,如果一個訊號為阻塞了,那麼無論你到來了多少次,在解除阻塞的時候程序只會處理一次。
備註: 阻塞意味著,訊號到了,我暫時不處理,放著就是。 訊號忽略,程序看到了,但是什麼都不會做。
由此可見,對於訊號而言,要麼直接處理,要麼一會處理(也有可能一直不處理), 要麼壓根就不會處理。
我們看下系統中內建的API介面:
int sigemptyset(sigset_t *set);//將訊號集清空,共64bits
int sigfillset(sigset_t *set);//將訊號集置1
int sigaddset(sigset_t *set, int signum);//將signum對應的位置為1
int sigdelset(sigset_t *set, int signum);//將signum對應的位置為0
int sigismember(const sigset_t *set, int signum);//判斷signum是否在該訊號集合中,如果集合中該位為1,則返回1,表示位於在集合中
// 讀取更改遮蔽狀態字的API函式
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
引數how有下面三種取值:
SIG_BLOCK: 將引數set指向的訊號集中設定的訊號新增到現在的遮蔽狀態字中,設定為阻塞;
SIG_UNBLOCK:將引數set指向的訊號集中設定的訊號新增到現在的遮蔽狀態字中,設定為非阻塞, 也就是解除阻塞;
SIG_SETMASK:將引數set指向的訊號集直接覆蓋現在的遮蔽狀態字的值;
如果oset是非空指標,則讀取程序的當前訊號遮蔽字通過oset引數傳出。
若成功則為0,若出錯則為-1
*/
#include <signal.h>
#include <unistd.h>
using namespace std;
void Handle(int sig) {
printf("sig = %d\n", sig);
abort();
}
void Prints(sigset_t* set) {
for (int i = 1; i <= 31; ++i) {
if (sigismember(set, i)) {
printf("1");
} else {
printf("0");
}
}
printf("\n");
}
int main(){
signal(SIGINT, Handle);
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigprocmask(SIG_BLOCK, &set, NULL);
// signal(SIGINT, SIG_IGN); //忽略訊號操作。
int i = 5;
while(i) {
i--;
sigset_t p;
sigpending(&p); // 獲得當前訊號未決表的資料。
Prints(&p);
sleep(2);
}
sigprocmask(SIG_UNBLOCK, &set, NULL);
//sigprocmask(SIG_UNBLOCK, &set, NULL);
//sigset_t p;
//sigpending(&p);
//Prints(&p);
printf("-1----------------------");
printf("-2----------------------");
printf("-3----------------------");
printf("-4----------------------");
return 0;
}
在上面程式碼中,signal(SIGINT, Handle),我們自己註冊了處理訊號的函式。先展示結果:
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
sig = 2
簡單解釋一下,按下ctrl + c 給程序傳入了中斷的訊號,在map中位置為2的地方置為了1,因為我們阻塞了訊號,所以他不做處理。
等在迴圈結束以後我們解除了遮蔽,系統呼叫了我們註冊的函式,進行了訊號處理,在處理函式中我們呼叫了abort,所以剩下的程式碼並沒有執行。
如果我們把程式碼中的註釋開啟再編譯執行,發現是下面的結果:
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0000000000000000000000000000000
-1-----------------------2-----------------------3-----------------------4----------------------
可以看到,SIGINT訊號,到的時候,不是先忽略,是先判定阻塞,放在了未決訊號表中。
然後等訊號解阻塞以後,可以看到,程序忽略了該訊號。不做處理。
4、SIGCHLD訊號
這個訊號,我們上面解釋說明過。子程序退出時,會給父程序傳送這個命令,讓父程序回收資源。如果父程序不做處理,那麼就成了子程序就成了我們說的殭屍程序,造成記憶體洩漏(敲黑板,這玩意也會造成記憶體洩漏)。
如果子程序退出的時候,父程序早就沒了,那麼回收資源的工作交給init程序。一般情況下,如果父程序,不關心子程序的資源回收,也不期待從兒子那邊獲得什麼,可以對這個訊號進行忽略,像上面程式碼中那樣操作即可
signal(SIGCHLD, SIG_IGN)
。這樣也不會產生殭屍程序,子程序退出的時候,發個訊號給父程序。父程序處理方式是忽略,子程序就自己退出了。
當然也可以自己建立處理函式,處理該訊號。
在引入程式碼之前我們先說兩個函式:wait 和 waitpid
標頭檔案sys/wait.h
pid_t wait(int *status);
程序一旦呼叫了wait,就立即阻塞自己,由wait自動分析是否當前程序的某個子程序已經退出,如果讓它找到了這樣一個已經變成殭屍的子程序,wait就會收集這個子程序的資訊,並把它徹底銷燬後返回;如果沒有找到這樣一個子程序,wait就會一直阻塞在這裡,直到有一個出現為止。
引數status用來儲存被收集程序退出時的一些狀態,它是一個指向int型別的指標。但如果我們對這個子程序是如何死掉的毫不在意,只想把這個殭屍程序消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個引數為NULL,就象下面這樣: pid = wait(NULL);
pid_t waitpid(pid_t pid, int *status, int options);
引數:
status:如果不是空,會把狀態資訊寫到它指向的位置,與wait一樣
options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把呼叫者的執行掛起
The value of options is an OR of zero or more of the following con-
stants:
WNOHANG return immediately if no child has exited.
wait與waitpid區別:
在一個子程序終止前, wait 使其呼叫者阻塞,而waitpid 有一選擇項,可使呼叫者不阻塞。
waitpid並不等待第一個終止的子程序—它有若干個選擇項,可以控制它所等待的特定程序。
實際上wait函式是waitpid函式的一個特例。waitpid(-1, &status, 0);
瞭解這些以後我們看程式碼例子:
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void waitchildren(int signo) {
int status;
wait(&status);
//while(1)waitpid(-1, &status, WNOHANG); //註釋3
//while(1)wait(&status) //註釋4
}
int main() {
int i;
pid_t pid;
//signal(SIGCHLD, waitchildren); //註釋1
//signal(SIGCHLD, SIG_IGN); //註釋2
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
}
return 0;
}
如果 我們編譯執行,發現在不退出的情況下,重新開一個終端執行top命令,可以看到zombie欄位為100.即是100個殭屍程序。
如果開啟註釋1 發現還是有殭屍程序,但是數量不為100,同一時刻,很多子程序退出,但是可惜只處理一部分。
關閉註釋1開啟註釋2.發現一個殭屍程序都沒了。
如果只打開註釋3 你也會發現一個殭屍程序都沒了。
如果你只打開註釋4,你會發現程式只處理了一個,而且卡死了。
wait 我們可以稱其為同步介面,而waitpid為非同步介面。
再看下面這段程式碼:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
int count = 0;
void waitchildren(int signo) {
count++;
std::cout << count << "------------" << std::endl;
int status;
wait(&status);
}
int main() {
int i;
pid_t pid;
signal(SIGCHLD, waitchildren);
//signal(SIGCHLD, SIG_IGN);
sigset_t set;
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
for(i=0; i<100; i++) {
pid = fork();
if(pid == 0)
break;
}
if(pid>0) {
printf("press Enter to exit...");
getchar();
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
return 0;
}
這段程式你會發現,只有一個子程序傳送的訊號,被捕獲處理了。
以上就是全部內容,歡迎各位大佬,糾錯指正。