1. 程式人生 > 其它 >Linux常見訊號介紹

Linux常見訊號介紹

1、訊號

首先訊號我們要和訊號量區分開來,雖然兩者都是作業系統程序通訊的方式。可以簡單的理解,訊號是用來通知程序發生了什麼需要做什麼,訊號量一般是用作程序同步(pv操作)

2、常見訊號量

(以下數字標號代表訊號再bitmap中的位置)

SIGINT 可能使我們最常用的訊號之一。一般在我們想程序中斷,鍵盤輸入Ctrl + C 即可實現,這是一個程序終止訊號。

SIGQUIT程式異常退出訊號和2 類似, 輸入Ctrl + \ 實現

SIGILL 執行了非法指令. 通常是因為可執行檔案本身出現錯誤, 或者試圖執行資料段. 堆疊溢位時也有可能產生這個訊號。

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;  
}  

這段程式你會發現,只有一個子程序傳送的訊號,被捕獲處理了。

以上就是全部內容,歡迎各位大佬,糾錯指正。