UNIX訊號(signal)程式設計 - UNIX高階環境程式設計第10章讀書筆記

ATField發表於2007-03-18

其他章節

使用pthread庫進行多執行緒程式設計1 - UNIX環境高階程式設計第11章讀書筆記
使用pthread庫進行多執行緒程式設計2 - UNIX高階環境程式設計第12章讀書筆記

10 Signals

1 Introduction & Concepts

Signals是一種軟體中斷,通知程式某種事件的發生。常見的SignalSIGABRT(當程式呼叫abort函式的時候自動傳送), SIGALRM(當timer被觸發的時候自動傳送),等等。

下面的情況可以產生Signal

1.     按下CTRL+C產生SIGINT

2.     硬體中斷,如除0,非法記憶體訪問(SIGSEV)等等

3.     Kill函式可以對程式傳送Signal

4.     Kill命令。實際上是對Kill函式的一個包裝

5.     軟體中斷。如當Alarm Clock超時(SIGURG),當Reader中止之後又向管道寫資料(SIGPIPE),等等

Signal發生的時候,可以有三種選擇(稱之為Disposition of the signal或者Action associated with a signal

1.     忽略Signal。大部分Signal都可以Ignore,除了SIGKILLSIGSTOP,這是為了提供一個確定的方法來Stop或者Kill一個Process。此外,如果我們忽略部分硬體異常產生的Signal,程式的行為未定義。

2.     捕捉Signal。可以讓核心來呼叫我們所指定的函式。SIGKILLSIGSTOP無法捕捉。

3.     執行預設行為。如果不做任何處理,則執行預設動作。大部分Signal的預設行為都是中止程式。

部分Signal的預設行為不僅中止程式,同時還會產生core dump,也就是生成一個名為core的檔案,其中儲存了退出時程式記憶體的映象,可以用來除錯。在下面情況,不會生成core檔案:

1.     當前程式不屬於當前user

2.     當前程式不屬於當前group

3.     使用者在當前目錄下無寫許可權

4.     Core檔案已存在,使用者無寫許可權

5.     檔案過大,超過RLIMIT_CORE

2 Signals

 

Signal

Description

SIGABRT

由呼叫abort函式產生,程式非正常退出

SIGALRM

alarm函式設定的timer超時或setitimer函式設定的interval timer超時

SIGBUS

某種特定的硬體異常,通常由記憶體訪問引起

SIGCANCEL

Solaris Thread Library內部使用,通常不會使用

SIGCHLD

程式TerminateStop的時候,SIGCHLD會傳送給它的父程式。預設情況下該Signal會被忽略

SIGCONT

當被stop的程式恢復執行的時候,自動傳送

SIGEMT

和實現相關的硬體異常

SIGFPE

數學相關的異常,如被0除,浮點溢位,等等

SIGFREEZE

Solaris專用,Hiberate或者Suspended時候傳送

SIGHUP

傳送給具有TerminalControlling Process,當terminaldisconnect時候傳送

SIGILL

非法指令異常

SIGINFO

BSD signal。由Status Key產生,通常是CTRL+T。傳送給所有Foreground Group的程式

SIGINT

Interrupt Key產生,通常是CTRL+C或者DELETE。傳送給所有ForeGround Group的程式

SIGIO

非同步IO事件

SIGIOT

實現相關的硬體異常,一般對應SIGABRT

SIGKILL

無法處理和忽略。中止某個程式

SIGLWP

Solaris Thread Libray內部使用

SIGPIPE

reader中止之後寫Pipe的時候傳送

SIGPOLL

當某個事件傳送給Pollable Device的時候傳送

SIGPROF

Setitimer指定的Profiling Interval Timer所產生

SIGPWR

和系統相關。和UPS相關。

SIGQUIT

輸入Quit Key的時候(CTRL+/)傳送給所有Foreground Group的程式

SIGSEGV

非法記憶體訪問

SIGSTKFLT

Linux專用,數學協處理器的棧異常

SIGSTOP

中止程式。無法處理和忽略。

SIGSYS

非法系統呼叫

SIGTERM

請求中止程式,kill命令預設傳送

SIGTHAW

Solaris專用,從Suspend恢復時候傳送

SIGTRAP

實現相關的硬體異常。一般是除錯異常

SIGTSTP

Suspend Key,一般是Ctrl+Z。傳送給所有Foreground Group的程式

SIGTTIN

Background Group的程式嘗試讀取Terminal的時候傳送

SIGTTOU

Background Group的程式嘗試寫Terminal的時候傳送

SIGURG

out-of-band data接收的時候可能傳送

SIGUSR1

使用者自定義signal 1

SIGUSR2

使用者自定義signal 2

SIGVTALRM

setitimer函式設定的Virtual Interval Timer超時的時候

SIGWAITING

Solaris Thread Library內部實現專用

SIGWINCH

Terminal的視窗大小改變的時候,傳送給Foreground Group的所有程式

SIGXCPU

CPU時間限制超時的時候

SIGXFSZ

程式超過檔案大小限制

SIGXRES

Solaris專用,程式超過資源限制的時候傳送

 

3 Signal Function

 

#include <signal.h>

 

Void (*signal(int signo, void (*func)(int)))(int);

 

返回之前的signal處理函式,錯誤返回SIG_ERR

 

1.     Signosignal的名稱

2.     Func:函式地址,或者是SIG_IGN(忽略Signal)或SIG_DFL(預設行為)。原型為:void (*)(int)。不過很多UNIX的實現也會傳入一些和實現相關的引數

如果用exec建立子程式,那麼子程式的所有Signal狀態是預設或者忽略:

1.     如果父程式忽略了某個Signal,那麼父程式用exec建立子程式時子程式的這個Signal的狀態也是忽略

2.     如果在父程式的某個Signal註冊了Signal處理函式的話,那麼這個子程式的該Signal的狀態會恢復成預設狀態,因為註冊的函式地址肯定在另外一個程式中無法呼叫。

如果用fork建立子程式,那麼子程式會繼承所有父程式的Signal狀態。

4 Unreliable Signals

早期的UNIX系統的Signal是不穩定的:

1.     Signal可能會Lost

2.     signal處理函式中,需要重複註冊signal,否則下次收不到signal

3.     不支援pending signal

5 Interrupted System Calls

1.     早期UNIX中,一個程式在呼叫系統呼叫被阻賽的時候, 有可能被一個Signal中斷。此時系統呼叫返回錯誤並且errno被設定為EINTR

2.     系統呼叫也被分為兩類:slow和一般。slow系統呼叫是那些可能永遠block的系統呼叫(disk IO是例外,一般情況下只會暫時Block,但也歸入Slow一類)

3.     為了處理這個問題,在早期UNIX系統中對於這些系統呼叫需要作額外的檢查和判斷,如果錯誤並且errno=EINTR,需要重新呼叫

4.     Free BSD  4.2對於部分系統呼叫ioctl, read, readv, write, writev, wait實現了自動重新恢復功能,也就是當函式被中斷的時候會自動恢復到原來的執行情況

5.     Free BSD 5.2.1, Linux 2.4.22, Mac OS X 10.3支援此功能,但POSIX.1不作強制要求

6 Reentrant Functions

Signal處理函式中,有可能在執行某個函式的時候,signal產生並呼叫了signal處理函式,然後再signal處理函式中再次呼叫了此函式,如果此函式呼叫了malloc/free,或者使用了static變數等技巧,會造成此函式不可再次被呼叫,此函式就是不可重入,反之就是可以重入的。UNIX標準定義了一個表,表中的函式都是可重入的。

7 Reliable Signal Terminology and Semantics

1.     在一個SignalGenerate之後,而被Deliver之前,該Signal稱為正處於pending狀態

2.     程式可以Block某個Signal。如果某個被Blocksignal產生的話,該signal一直會處於pending狀態,直到程式

a.     Unblock這個Signal

b.     改變Signal的狀態為Ignore

3.     如果一個被BlockSignal產生多次,POSIX.1允許deliversignal一次或者多次

4.     每個程式有一個signal mask,表示那些signalBlock,那些被允許

5.     sigset_t用來表示多種signal的集合

8 kill and raise Functions

Kill函式對某個程式傳送signal,而raise對本程式傳送signal

#include <signal.h>

 

int kill(pit_t pid, int signo);

int raise(int signo)

 

成功返回0,錯誤返回-1

 

對於pid可以有4種可能性:

1.     pid > 0:傳送給某個程式

2.     pid == 0:傳送給所有group ID=當前傳送程式的group id,並且有許可權傳送。注意部分系統程式(具體是那些的話和具體實現相關)不包括在內

3.     pid < 0:傳送給所有group ID= abs(pid)的程式,不包括部分系統程式

4.     pid == -1:傳送給所有有許可權傳送signal的程式,不包括部分系統程式

順便說一句,我認為這個design不太好,最好設計成四個不同函式。

Superuser可以傳送signal給任何程式,而對於一般使用者來說,傳送者的user id必須等於接收者的user id

如果signo=0,則是告訴kill函式檢查該程式是否存在。如果不存在,則返回錯誤-1errno設定為ESRCH

9 alarm and pause Functions

Alarm函式設定一個timer,到固定時間之後會expire,傳送一個SIGALRM signal

#include <signal.h>

 

unsigned int alarm(unsigned int second);

 

返回前一個alarm的剩餘時間值,單位為秒

 

SIGALRM的預設行為是中止程式。同一個程式只能有一個alarm。呼叫alarm函式會取消前一個alarm,並返回前一個alarm的剩餘時間。如果second值為0,則只是取消前一個alarm

 

Pause函式暫停當前程式的執行,直到某個signalcatch

#include <signal.h>

 

int pause(void);

 

總是返回-1errno總是設定為EINTR

 

10 signal sets

多個signal的集合用signal set表示,型別為sigset_t,下面函式用於操作sigset_t

#include <signal.h>

 

int sigemptyset(sigset_t *set);

 

int sigfillset(sigset_t *set);

 

int sigaddset(sigset_t *set, int signo);

 

int sigdelset(sigset_t *set, int signo);

 

成功返回0,錯誤返回-1

 

int sigismember(sigset_t *set, int signo);

 

返回1如果是,不是則返回0

 

1.     sigemptyset:初始化sigset_t,所有signal都不包括在內

2.     sigfillset:初始化sigset_t,包括所有signal

3.     sigaddset, sigdelset:新增/刪除signal

4.     sigismember:檢查signal set中是否包括該signal

11 sigprocmask Function

sigprocmask函式設定程式的signal mask,可以block/unblock signal

#include <signal.h>

 

int sigprocmask(int how, const sigset_t *restrict set, sig_set *restrict oset);

 

 

成功返回0,錯誤返回-1

1.     how引數的意義如下:

How

Description

SIG_BLOCK

在原來的mask基礎上Block signal set

SIG_UNBLOCK

在原來的mask基礎上Unblock signal set

SIG_SETMASK

設定mask為指定的signal set

 

2.     setsignal set,如果=NULL則忽略how引數(again,又是一個不太合適的設計,不利於差錯和記憶)

3.     oset:返回當前processsignal mask

 

12 sigpending Function

Sigpending函式返回當前被block而沒有deliversignal的集合:

#include <signal.h>

 

int sigpending(sigset_t *set);

 

 

成功返回0,錯誤返回-1

 

13 sigaction Function

sigaction用於修改或者獲取某個signal所對應的actionSigaction可以取代Signal函式,實際上,signal函式可以用sigaction函式來實現。。sigaction函式原型如下:

#include <signal.h>

 

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

 

 

成功返回0,錯誤返回-1

1.     signosignal

2.     act:對應的action

3.     oact:之前的action,可以為NULL

sigaction如下:

struct sigaction {

      void        (*sa_handler)(int);

      sigset_t    sa_mask;

      int         sa_flags;

      void        (*sa_sigaction)(int, siginfo_t *, void *)

};

1.     sa_handlersignal處理函式

2.     sa_mask:當signal函式被呼叫的時候,自動block這些signal。當signal處理函式返回的時候,mask會恢復到初始值。同時,作業系統也會自動把該signal加到mask中,防止重入

3.     sa_flags:定義如下:

Option

Description

SA_INTERRUPT

被該signal中斷的系統呼叫不會自動重啟

SA_NOCLDSTOP

如果signoSIGCHLD,當子程式stop的時候不產生該signal

SA_NOCLDWAIT

如果signoSIGCHLD,子程式不會變成zombie。當呼叫wait的時候,呼叫程式會一直等待直到所有子程式結束

SA_NODEFER

signal處理函式中系統不會自動blocksignal

SA_ONSTACK

如果呼叫了sigaltstack指定一個額外的stack,那麼該signal傳送的時候,程式處於此stack

SA_RESETHAND

恢復為初始化值,SA_SIGINFO flag也會被清除

SA_RESTART

被該signal中斷的系統呼叫會自動重啟

SA_SIGINFO

signal處理函式提供額外資訊

 

4.     sa_sigaction:當sa_flagsSA_SIGINFO時,signal處理函式為sa_sigaction,接收額外兩個引數:siginfo_tvoid *

siginfo_t表示該signal為何產生:

struct siginfo_t {

      int   si_signo;   /* signal */

      int   si_errno;   /* errrno */

      int   si_code;    /* 額外資訊,和具體signal相關 */

      pid_t si_pid;     /* 傳送signal程式的pid */

      uid_t si_uid;     /* 傳送signal的程式的uid */

      void  *si_addr;   /* 產生fault(SIG_SEGV)的地址 */

      int   si_status;  /* 退出值或者signal數值 */

      long  si_band;    /* SIGPOLLband數值 */

};

 

void *則應該被轉換成ucontext_t結構,表示當signal產生時候的程式上下文資訊

 

14 sigsetjmp and siglongjmp Functions

1.     setjmp,longjmp的函式的作用是,setjmp記住位置,然後longjmp可以自動跳轉到該位置,並且從setjmp函式返回

2.     當呼叫signal處理函式的時候,該signal會自動被block,如果這個時候我們呼叫longjmp跳出signal處理函式,那麼signal還會被Block嗎?答案是不確定,和具體實現相關

3.     為了解決這個問題,系統提供了sigsetjmpsiglongjmp。區別在於,如果sigsetjmpsavemask為非0,則sigsetjmp會在env引數中記住當前processsignal mask。一般情況下如果和signal打交道的話都需要呼叫這兩個函式。這兩個函式原型如下:

#include <signal.h>

 

int sigsetjmp(sigjmp_buf env, int savemask);

 

直接呼叫返回0,從siglongjmp返回的時候返回非0

 

void siglongjmp(sigjmp_buf env, int val);

 

15 sigsuspend Function

Sigsuspend函式可以在一個原子操作內設定mask然後pause,防止設定mask之後pause之前,signal handler被呼叫,程式永遠等待。原型如下:

#include <signal.h>

 

int sigsuspend(sigset_t *sig);

 

總是返回-1errno設定為EINTR

 

16 abort Function

Abort函式等價於raise (SIGABRT)。注意就算是signal處理函式處理了SIGABRT,程式仍然會結束。

17 system Function

System函式除了會執行可執行檔案建立子程式之外,還會設定sigmask為忽略SIGINTSIGQUITBlock SIGCHLD

1.     忽略SIGINTSIGQUIT的原因是,只有子程式應該處理這兩個signal,而父程式無需處理

2.     Block SIGCHLD的原因是,System函式需要知道子程式的結束,而父程式不應該先提前知道,以擴音前呼叫wait函式使得System函式本身無法獲得程式的退出值

18 sleep Function

Sleep函式可以sleep某段時間,精確性無法保證。程式睡眠指定時間,直到時間到或者signal產生並被handler處理。注意sleep可能會由alarm函式實現,所以把alarm函式和sleep函式混著使用可能會造成無法預料的結果。

19 Job-Control Signals

SIGCHLD, SIGCONT, SIGSTOP, SITTSTP, SIGTTIN, SITTOU被認為是和Job Control有關的signal。大部分情況下,程式無需處理這些signal,除了SIGCHLD之外。

20 Additional Features

有些系統提供sys_siglist陣列,儲存著每個signal對應的字串描述。

extern char *sys_siglist[];

 

很多系統提供psignal函式輸出一個msg + ‘: ’ + signal string

#include <signal.h>

 

void psignal(int四個,const char *msg);

 

另一個常用函式是strsignal,返回signo對應的描述

#include <string.h>

 

char *strsignal(int signo);

 

返回對應的描述

 

Solaris提供把signal和名稱對應起來的函式:

#include <string.h>

 

int *sig2str(int signo, char *str);

 

int str2sig(const char *str, int *signop);

 

返回對應的描述

Sig2strsignal轉換為對應的描述字串,而str2sig則是將描述字串轉換為signal

 

相關文章