20191227甘濘與第六章學習筆記
第6章 訊號和訊號處理
6.1訊號和中斷
訊號:發給程序的請求,將程序從正常執行轉移到中斷處理。
中斷:是從I/O裝置或協處理器傳送到CPU的外部請求,它將CPU從正常執行轉移到中斷處理。
“中斷”是傳送給“程序”的事件,它將“程序”從正常活動轉移到其他活動,稱為“中斷處理”。“程序”可在完成“中斷”處理後恢復正常活動。
終端主要有以下幾種型別:
- 1、人員中斷
- 2、程序中斷
- 3、硬體中斷
- 4、程序的陷阱錯誤
Unix/Linux的訊號處理
(1)按Ctrl+C組合鍵通常會導致當前執行的程序終止。原因如下:Ctrl+C組合鍵會生成一個鍵盤硬體中斷。鍵盤中斷處理程式將Ctrl+C組合鍵轉換為SIGINT(2)訊號,傳送給終端上的所有程序,並喚醒等待鍵盤輸入的程序。在核心模式下,每個程序都要檢查和處理未完成的訊號。程序對大多數訊號的預設操作是呼叫核心的kexit(exitValue)函式來終止。在Linux中,exitValue的低位位元組是導致程序終止的訊號編號。
(2)使用者可使用nohup a.out &命令在後臺執行一個程式。即使在使用者退出後,程序仍將繼續執行。
(3)使用者再次登入時也許會發現(通過ps-u LTD)後臺程序仍在執行。使用者可以使用sh命令kill pid (or kill -s 9 pid)殺死該程序。
訊號處理函式
每一個程序塊都有一個訊號處理陣列int sig【32】。sig[32]陣列的每個條目都指定了如何處理相應的訊號,其中0表示DEFault(預設),1表示 IGNore(忽略)。下圖給出了訊號位向量、遮蔽位向量和訊號處理函式。
如果訊號位向量中的位I為1,則會生成一個訊號I或將其傳送給程序。如果遮蔽位向量的位I為1,則訊號會被阻塞或遮蔽。否則,訊號未被阻塞。只有當訊號存在並且未被阻塞時,訊號才會生效或傳遞給程序。
訊號的傳送
傳送訊號的主要函式有: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, const union 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。
5、setitimer()
#include <sys/time.h>
int setitimer(int which, const struct itimerval *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仍然能被程序接收。該函式無返回值。
訊號捕捉函式
程序可使用系統呼叫:int r =signal(int signal_numberr void *handler);來修改選定訊號編號的處理函式,SIGKILL(19)除外,它們不能修改。signal()系統呼叫在所有類Unix系統中均可用,但它有一些缺點:
- 在執行已安裝的訊號捕捉函式之前,通常將訊號處理函式重置為DEFault。為捕捉下一次出現相同的訊號,必須重新安裝捕捉函式。
- signal()不能阻塞其他訊號
- 不同Unix版本的signal。可能會有所不通不同。
所以現在signal()已經被sigaciton()所代替,它的原型是int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);,sigaction結構體的定義為
其中重要的欄位如下:
sa_handler :該欄位是指向處理函式的指標,該函式與signal()的處理函式有相同的原型。
sa_sigaction:該欄位是執行訊號處理函式的另一種方法。它的訊號編號旁邊有兩個額外引數,其中siginfo t *提供關於所接收訊號的更多資訊。
sa_mask:可在處理函式執行期間設定要阻塞的訊號。
sa_flags :可修改訊號處理程序的行為。若要使用sa_sigaction處理函式,必須將sa_flags設定為SA_SIGINFO。
實踐內容
/* sigset.c */
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
/* 自定義的訊號處理函式 */
void my_func(int signum)
{
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
/* 初始化訊號集為空 */
if (sigemptyset(&set) < 0)
{
perror("sigemptyset");
exit(1);
}
/* 將相應的訊號加入訊號集 */
if (sigaddset(&set, SIGQUIT) < 0)
{
perror("sigaddset");
exit(1);
}
if (sigaddset(&set, SIGINT) < 0)
{
perror("sigaddset");
exit(1);
}
if (sigismember(&set, SIGINT))
{
sigemptyset(&action1.sa_mask);
action1.sa_handler = my_func;
action1.sa_flags = 0;
sigaction(SIGINT, &action1, NULL);
}
if (sigismember(&set, SIGQUIT))
{
sigemptyset(&action2.sa_mask);
action2.sa_handler = SIG_DFL;
action2.sa_flags = 0;
sigaction(SIGQUIT, &action2,NULL);
}
/* 設定訊號集遮蔽字,此時set中的訊號不會被傳遞給程序,暫時進入待處理狀態 */
if (sigprocmask(SIG_BLOCK, &set, NULL) < 0)
{
perror("sigprocmask");
exit(1);
}
else
{
printf("Signal set was blocked, Press any key!");
getchar();
}
/* 在訊號遮蔽字中刪除set中的訊號 */
if (sigprocmask(SIG_UNBLOCK, &set, NULL) < 0)
{
perror("sigprocmask");
exit(1);
}
else
{
printf("Signal set is in unblock state\n");
}
while(1);
exit(0);
}
該程式的執行結果如下,可以看見,在訊號處於阻塞狀態時,所發出的訊號對程序不起作用,並且該訊號進入待處理狀態。讀者按任意鍵,並且訊號脫離了阻塞狀態後,使用者發出的訊號才能正常執行。這裡SIGINT已按照使用者自定義的函式執行,請讀者注意阻塞狀態下SIGINT的處理和非阻塞狀態下SIGINT的處理有何不同。