Linux系統程式設計(21)——訊號的產生

尹成發表於2014-07-26

1、通過終端按鍵產生訊號

通過上一篇我們知道了SIGINT的預設處理動作是終止程式,SIGQUIT的預設處理動作是終止程式並且Core Dump,現在我們來驗證一下。

 

首先解釋什麼是Core Dump。當一個程式要異常終止時,可以選擇把程式的使用者空間記憶體資料全部儲存到磁碟上,檔名通常是core,這叫做Core Dump。程式異常終止通常是因為有Bug,比如非法記憶體訪問導致段錯誤,事後可以用偵錯程式檢查core檔案以查清錯誤原因,這叫做Post-mortem Debug。一個程式允許產生多大的core檔案取決於程式的Resource Limit(這個資訊儲存在PCB中)。預設是不允許產生core檔案的,因為core檔案中可能包含使用者密碼等敏感資訊,不安全。在開發除錯階段可以用ulimit命令改變這個限制,允許產生core檔案。

 

首先用ulimit命令改變Shell程式的Resource Limit,允許core檔案最大為1024K:

 

$ ulimit -c 1024

然後寫一個死迴圈程式:

#include <unistd.h>
 
int main(void)
{
         while(1);
         return0;
}

前臺執行這個程式,然後在終端鍵入Ctrl-C或Ctrl-\:

coreulimit命令改變了Shell程式的ResourceLimit,test程式的PCB由Shell程式複製而來,所以也具有和Shell程式相同的Resource Limit值,這樣就可以產生Core Dump了。

 

2、呼叫系統函式向程式發訊號

仍以上面的死迴圈程式為例,首先在後臺執行這個程式,然後用kill命令給它發SIGSEGV訊號。

$ ./ test &
[1] 7940
$ kill -SIGSEGV 7940
$(再次回車)
[1]+ Segmentation fault      (core dumped) ./ test

7940是test程式的id。之所以要再次回車才顯示Segmentationfault,是因為在7940程式終止掉之前已經回到了Shell提示符等待使用者輸入下一條命令,Shell不希望Segmentation fault資訊和使用者的輸入交錯在一起,所以等使用者輸入命令之後才顯示。指定某種訊號的kill命令可以有多種寫法,上面的命令還可以寫成kill -SEGV 7940或kill -11 7940,11是訊號SIGSEGV的編號。以往遇到的段錯誤都是由非法記憶體訪問產生的,而這個程式本身沒錯,給它發SIGSEGV也能產生段錯誤。

 

kill命令是呼叫kill函式實現的。kill函式可以給一個指定的程式傳送指定的訊號。raise函式可以給當前程式傳送指定的訊號(自己給自己發訊號)。

#include <signal.h>
 
int kill(pid_t pid, int signo);
int raise(int signo);


這兩個函式都是成功返回0,錯誤返回-1。

 

abort函式使當前程式接收到SIGABRT訊號而異常終止。

 

#include <stdlib.h>
void abort(void);

就像exit函式一樣,abort函式總是會成功的,所以沒有返回值。

 

3、 由軟體條件產生訊號

傳送訊號的主要函式有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

1、kill()

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)


引數pid的值  訊號的接收程式

pid>0        程式ID為pid的程式

pid=0        同一個程式組的程式

pid<0 pid!=-1   程式組ID為 -pid的所有程式

pid=-1       除傳送程式自身外,所有程式ID大於1的程式

Sinno是訊號值,當為0時(即空訊號),實際不傳送任何訊號,但照常進行錯誤檢查,因此,可用於檢查目標程式是否存在,以及當前程式是否具有向目標傳送訊號的許可權(root許可權的程式可以向任何程式傳送訊號,非root許可權的程式只能向屬於同一個session或者同一個使用者的程式傳送訊號)。

Kill()最常用於pid>0時的訊號傳送,呼叫成功返回0; 否則,返回 -1。 注:對於pid<0時的情況,對於哪些程式將接受訊號,各種版本說法不一,其實很簡單,參閱核心原始碼kernal/signal.c即可,上表中的規則是參考red hat 7.2。

2、raise()

#include <signal.h>
int raise(int signo)

向程式本身傳送訊號,引數為即將傳送的訊號值。呼叫成功返回 0;否則,返回 -1。

3、sigqueue()

#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, constunion sigval val)


呼叫成功返回 0;否則,返回 -1。

sigqueue()是比較新的傳送訊號系統呼叫,主要是針對實時訊號提出的(當然也支援前32種),支援訊號帶有引數,與函式sigaction()配合使用。

sigqueue的第一個引數是指定接收訊號的程式ID,第二個引數確定即將傳送的訊號,第三個引數是一個聯合資料結構union sigval,指定了訊號傳遞的引數,即通常所說的4位元組值。

        typedef union sigval {
                 int  sival_int;
                 void *sival_ptr;
        }sigval_t;


sigqueue()比kill()傳遞了更多的附加資訊,但sigqueue()只能向一個程式傳送訊號,而不能傳送訊號給一個程式組。如果signo=0,將會執行錯誤檢查,但實際上不傳送任何訊號,0值訊號可用於檢查pid的有效性以及當前程式是否有許可權向目標程式傳送訊號。

在呼叫sigqueue時,sigval_t指定的資訊會拷貝到3引數訊號處理函式(3引數訊號處理函式指的是訊號處理函式由sigaction安裝,並設定了sa_sigaction指標,稍後將闡述)的siginfo_t結構中,這樣訊號處理函式就可以處理這些資訊了。由於sigqueue系統呼叫支援傳送帶引數訊號,所以比kill()系統呼叫的功能要靈活和強大得多。

注:sigqueue()傳送非實時訊號時,第三個引數包含的資訊仍然能夠傳遞給訊號處理函式; sigqueue()傳送非實時訊號時,仍然不支援排隊,即在訊號處理函式執行過程中到來的所有相同訊號,都被合併為一個訊號。

4、alarm()

#include <unistd.h>
unsigned int alarm(unsigned int seconds)


專門為SIGALRM訊號而設,在指定的時間seconds秒後,將向程式本身傳送SIGALRM訊號,又稱為鬧鐘時間。程式呼叫alarm後,任何以前的alarm()呼叫都將無效。如果引數seconds為零,那麼程式內將不再包含任何鬧鐘時間。

返回值,如果呼叫alarm()前,程式中已經設定了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0。

 

呼叫alarm函式可以設定一個鬧鐘,也就是告訴核心在seconds秒之後給當前程式發SIGALRM訊號,該訊號的預設處理動作是終止當前程式。這個函式的返回值是0或者是以前設定的鬧鐘時間還餘下的秒數。打個比方,某人要小睡一覺,設定鬧鐘為30分鐘之後響,20分鐘後被人吵醒了,還想多睡一會兒,於是重新設定鬧鐘為15分鐘之後響,“以前設定的鬧鐘時間還餘下的時間”就是10分鐘。如果seconds值為0,表示取消以前設定的鬧鐘,函式的返回值仍然是以前設定的鬧鐘時間還餘下的秒數。

 

#include <unistd.h>
#include <stdio.h>
 
int main(void)
{
         intcounter;
         alarm(1);
         for(counter=0;1; counter++)
                   printf("counter=%d", counter);
         return0;
}

這個程式的作用是1秒鐘之內不停地數數,1秒鐘到了就被SIGALRM訊號終止。

 

 

5、setitimer()

#include <sys/time.h>
int setitimer(int which, const structitimerval *value, struct itimerval *ovalue));


setitimer()比alarm功能強大,支援3種型別的定時器:

ITIMER_REAL:       設定絕對時間;經過指定的時間後,核心將傳送SIGALRM訊號給本程式;

ITIMER_VIRTUAL 設定程式執行時間;經過指定的時間後,核心將傳送SIGVTALRM訊號給本程式;

ITIMER_PROF 設定程式執行以及核心因本程式而消耗的時間和,經過指定的時間後,核心將傳送ITIMER_VIRTUAL訊號給本程式;

Setitimer()第一個引數which指定定時器型別(上面三種之一);第二個引數是結構itimerval的一個例項,結構itimerval形式見附錄1。第三個引數可不做處理。

Setitimer()呼叫成功返回0,否則返回-1。

6、abort()

#include <stdlib.h>
void abort(void);


向程式傳送SIGABORT訊號,預設情況下程式會異常退出,當然可定義自己的訊號處理函式。即使SIGABORT被程式設定為阻塞訊號,呼叫abort()後,SIGABORT仍然能被程式接收。該函式無返回值。

 

 

 

 

 

相關文章