信号是與一定的程序相聯系的。也就是說,一個程序可以決定在程序中對哪些信号進行什 麼樣的處理。例如,一個程序可以忽略某些信号而隻處理其他一些信号;另外,一個程序還可以選擇如何處理信号。總之,這些總與特定的程序相聯系的。是以,首 先要建立其信号和程序的對應關系,這就是信号的安裝登記。
Linux 主要有兩個函數實作信号的安裝登記:signal和sigaction。其中signal在系統調用的基礎上實作,是庫函數。它隻有兩個參數,不支援信号 傳遞資訊,主要是用于前32個非實時信号的安裝;而sigaction是較新的函數(由兩個系統調用實作:sys_signal以及 sys_rt_sigaction),有三個參數,支援信号傳遞資訊,主要用來與sigqueue系統調用配合使用。當然,sigaction同樣支援非 實時信号的安裝,sigaction優于signal主要展現在支援信号帶有參數。
對 于應用程式自行處理的信号來說,信号的生命周期要經過信号的安裝登記、信号集操作、信号的發送和信号的處理四個階段。信号的安裝登記指的是在應用程式中, 安裝對此信号的處理方法。信号集操作的作用是用于對指定的一個或多個信号進行信号屏蔽,此階段對有些應用程式來說并不需要。信号的發送指的是發送信号,可 以通過硬體(如在終端上按下Ctrl-C)發送的信号和軟體(如通過kill函數)發送的信号。信号的處理指的是作業系統對接收信号程序的處理,處理方法 是先檢查信号集操作函數是否對此信号進行屏蔽,如果沒有屏蔽,作業系統将按信号安裝函數中登記注冊的處理函數完成對此程序的處理。
1. signal函數
(1)函數說明
在signal函數中,有兩個形參,分别代表需要處理的信号編号值和處理信号函數的指針。它主要是用于前32種非實時信号的處理,不支援信号的傳遞資訊。但是由于使用簡單,易于了解,是以在許多場合被程式員使用。
對 于Unix系統來說,使用signal函數時,自定義處理信号函數執行一次後失效,對該信号的處理回到預設處理方式。下面以一個例子進行說明,例如一程式 中使用signal(SIGQUIT, my_func)函數調用,其中my_func是自定義函數。應用程序收到SIGQUIT信号時,會跳轉到自定義處理信号函數my_func處執行,執行 後信号注冊函數my_func失效,對SIGQUIT信号的處理回到作業系統的預設處理方式,當應用程序再次收到SIGQUIT信号時,會按作業系統預設 的處理方式進行處理(即不再執行my_func處理函數)。而在Linux系統中,signal函數已被改寫,由sigaction函數封裝實作,則不存 在上述問題。
(2)signal函數原型
signal(設定信号處理方式) | ||
所需頭檔案 | #include | |
函數說明 | 設定信号處理方式。signal()會依參數signum指定的信号編号來設定該信号的處理函數。當指定的信号到達時就會跳轉到參數handler指定的函數執行 | |
函數原型 | void (*signal(int signum,void(* handler)(int)))(int) | |
函數傳入值 | signum | 指定信号編号 |
handle | SIG_IGN:忽略參數signum指定的信号 | |
SIG_DFL:将參數signum指定的信号重設為核心預設的信号處理方式,即采用系統預設方式處理信号 | ||
自定義信号函數處理指針 | ||
函數傳回值 | 成功 | 傳回先前的信号處理函數指針 |
出錯 | SIG_ERR(-1) | |
附加說明 | 在Unix環境中,在信号發生跳轉到自定的handler處理函數執行後,系統會自動将此處理函數換回原來系統預設的處理方式,如果要改變此情形請改用sigaction函數。在Linux環境中不存在此問題 |
signal函數原型比較複雜,如果使用下面的typedef,則可使其簡化。
typedef void sign(int);
sign *signal(int, handler *);
可見,該函數原型首先整體指向一個無傳回值帶一個整型參數的函數指針,也就是信号的原始配置函數。接着該原型又帶有兩個參數,其中的第二個參數可以是使用者自定義的信号處理函數的函數指針。對這個函數格式可以不了解,但需要學會模仿使用。
(3) signal函數使用執行個體
該示例表明了如何使用signal函數進行安裝登記信号處理函數。當該信号發生時,登記的信号處理函數會捕捉到相應的信号,并做出給定的處理。這裡,my_func就是信号處理的函數指針。讀者還可以将my_func改為SIG_IGN或SIG_DFL檢視運作結果。
signal.c源代碼如下:
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n ");
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
pause();
exit(0);
}
編譯 gcc signal.c –o signal。
執行 ./signal,執行結果如下:
Waiting for signal SIGINT or SIGQUIT
I have get SIGINT
I have get SIGQUIT
2. sigaction函數
(1)sigaction函數原型
sigaction函數用來查詢和設定信号處理方式,它是用來替換早期的signal函數。sigaction函數原型及說明如下:
sigaction(查詢和設定信号處理方式) |
所需頭檔案 | #include | |
函數說明 | sigaction()會依參數signum指定的信号編号來設定該信号的處理函數 | |
函數原型 | int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact) | |
函數傳入值 | signum | 可以指定SIGKILL和SIGSTOP以外的所有信号 |
act | 參數結構sigaction定義如下 struct sigaction { void (*sa_handler) (int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); } ① sa_handler:此參數和signal()的參數handler相同,此參數主要用來對信号舊的安裝函數signal()處理形式的支援 ② sa_sigaction:新的信号安裝機制,處理函數被調用的時候,不但可以得到信号編号,而且可以獲悉被調用的原因以及産生問題的上下文的相關資訊。 ③ sa_mask:用來設定在處理該信号時暫時将sa_mask指定的信号擱置 ④ sa_restorer: 此參數沒有使用 ⑤ sa_flags:用來設定信号處理的其他相關操作,下列的數值可用。可用OR 運算(|)組合 ? A_NOCLDSTOP:如果參數signum為SIGCHLD,則當子程序暫停時并不會通知父程序 ? SA_ONESHOT/SA_RESETHAND:當調用新的信号處理函數前,将此信号處理方式改為系統預設的方式 ? SA_RESTART:被信号中斷的系統調用會自行重新開機 ? SA_NOMASK/SA_NODEFER:在處理此信号未結束前不理會此信号的再次到來 ? SA_SIGINFO:信号處理函數是帶有三個參數的sa_sigaction | |
oldact | 如果參數oldact不是NULL指針,則原來的信号處理方式會由此結構sigaction傳回 | |
函數傳回值 | 成功:0 | |
出錯:-1,錯誤原因存于error中 | ||
附加說明 | 信号處理安裝的新舊兩種機制: ① 使用舊的處理機制:struct sigaction act; act.sa_handler=handler_old; ② 使用新的處理機制:struct sigaction act; act.sa_sigaction=handler_new; 并設定sa_flags的SA_SIGINFO位 | |
錯誤代碼 | EINVAL:參數signum不合法,或是企圖攔截SIGKILL/SIGSTOP信号 EFAULT:參數act,oldact指針位址無法存取 EINTR:此調用被中斷 |
(2)sigaction函數使用執行個體
sigaction.c源代碼如下:
#include
#include
#include
#include
#include
void new_op(int, siginfo_t *, void *);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
perror("install sigal error");
return -1 ;
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
return 0 ;
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n", signum);
sleep(5);
}
編譯 gcc sigaction.c -o sigaction。
執行 ./sigaction 2,執行結果如下:
wait for the signal
receive signal 2
退出
3. 信号集操作函數
由 于有時需要把多個信号當作一個集合進行處理,這樣信号集就産生了,信号集用來描述一類信号的集合,Linux所支援的信号可以全部或部分的出現在信号集 中。信号集操作函數最常用的地方就是用于信号屏蔽。比如有時候希望某個程序正确執行,而不想程序受到一些信号的影響,此時就需要用到信号集操作函數完成對 這些信号的屏蔽。
信号集操作函數按照功能和使用順序分為三類,分别為建立信号集函數,設定信号屏蔽位函數和查詢被擱置(未決)的信号函數。建立信号集函數隻是建立一個信号 的集合,設定信号屏蔽位函數對指定信号集中的信号進行屏蔽,查詢被擱置的信号函數是用來查詢目前“未決”的信号集。信号集函數組并不能完成信号的安裝登記 工作,信号的安裝登記需要通過sigaction函數或signal函數來完成。
查 詢被擱置的信号是信号處理的後續步驟,但不是必需的。由于有時程序在某時間段内要求阻塞一些信号,程式完成特定工作後解除對該信号阻塞,這個時間段内被阻 塞的信号稱為“未決”信号。這些信号已經産生,但沒有被處理,sigpending函數用來檢測程序的這些“未決”信号,并進一步決定對它們做何種處理 (包括不處理)。
(1) 建立信号集函數
建立信号集函數有如下5個:
① sigemptyset:初始化信号集合為空。
② sigfillset:把所有信号加入到集合中,信号集中将包含Linux支援的64種信号。
③ sigaddset:将指定信号加入到信号集合中去。
④ sigdelset:将指定信号從信号集中删去。
⑤ sigismember:查詢指定信号是否在信号集合之中。
建立信号集合函數原型 | |
所需頭檔案 | #include |
函數原型 | int sigemptyset(sigset_t *set) |
int sigfillset(sigset_t *set) | |
int sigaddset(sigset_t *set,int signum) | |
int sigdelset(sigset_t *set,int signum) | |
int sigismember(sigset_t *set,int signum) | |
函數傳入值 | set:信号集 |
signum:指定信号值 | |
函數傳回值 | 成功:0(sigismember函數例外,成功傳回1,失敗傳回 0) |
出錯:-1,錯誤原因存于error中 |
(2) 設定信号屏蔽位函數
每個程序都有一個用來描述哪些信号遞送到程序時将被阻塞的信号集,該信号集中的所有信号在遞送到程序後都将被阻塞。調用函數sigprocmask可設定信号集内的信号阻塞或不阻塞。其函數原型及說明如下:
sigprocmask(設定信号屏蔽位) |
所需頭檔案 | #include | |
函數原型 | int sigprocmask(int how,const sigset_t *set,sigset_t *oset) | |
函數傳入值 | how(決定函數的操作方式) | SIG_BLOCK:增加一個信号集合到目前程序的阻塞集合之中 |
SIG_UNBLOCK:從目前的阻塞集合之中删除一個信号集合 | ||
SIG_SETMASK:将目前的信号集合設定為信号阻塞集合 | ||
set:指定信号集 | ||
oset:信号屏蔽字 | ||
函數傳回值 | 成功:0 | |
出錯:-1,錯誤原因存于error中 |
(3) 查詢被擱置(未決)信号函數
sigpending函數用來查詢“未決”信号。其函數原型及說明如下:
sigpending(查詢未決信号) |
所需頭檔案 | #include |
函數說明 | 将被擱置的信号集合由參數set指針傳回 |
函數原型 | int sigpending(sigset_t *set) |
函數傳入值 | set:要檢測信号集 |
函數傳回值 | 成功:0 |
出錯:-1,錯誤原因存于error中 | |
錯誤代碼 | EFAULT:參數set指針位址無法存取 EINTR:此調用被中斷 |
(4) 對信号集操作函數的使用方法
對信号集操作函數的使用方法和順序如下:
① 使用signal或sigaction函數安裝和登記信号的處理。
② 使用sigemptyset等定義信号集函數完成對信号集的定義。
③ 使用sigprocmask函數設定信号屏蔽位。
④ 使用sigpending函數檢測未決信号,非必需步驟。
(5) 信号集操作函數使用執行個體
該 執行個體首先使用sigaction函數對SIGINT信号進行安裝登記,安裝登記使用了新舊兩種機制,其中#if 0進行注釋掉的部分為信号安裝的新機制。接着程式把SIGQUIT、SIGINT兩個信号加入信号集,并把該信号集設為阻塞狀态。程式開始睡眠30秒,此 時使用者按下Ctrl+C,程式将測試到此未決信号(SIGINT);随後程式再睡眠30秒後對SIGINT信号解除阻塞,此時将處理SIGINT登記的信 号函數my_func。最後可以用SIGQUIT(Ctrl+\)信号結束程序執行。
sigset.c源代碼如下:
#include
#include
#include
#include
#include
#if 0
void my_funcnew(int signum, siginfo_t *info,void *myact)
#endif
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;
sigemptyset(&action1.sa_mask);
#if 0
action1.sa_flags= SA_SIGINFO;
action1.sa_sigaction=my_funcnew;
#endif
action1.sa_flags= 0;
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);
if(sigemptyset(&set)<0)
{
perror("sigemptyset");
return -1 ;
}
if(sigaddset(&set,SIGQUIT)<0)
{
perror("sigaddset");
return -1 ;
}
if(sigaddset(&set,SIGINT)<0)
{
perror("sigaddset");
return -1 ;
}
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
{
perror("sigprocmask");
return -1 ;
}
else
{
printf("blocked\n");
}
if(sigismember(&set,SIGINT)){
printf("SIGINT in set\n") ;
}
sleep( 30 ) ;
if ( sigpending(&pendset) <0 )
{
perror("get pending mask error");
}
if(sigismember(&pendset, SIGINT) )
{
printf("signal SIGINT is pending\n");
}
sleep(30) ;
if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)
{
perror("sigprocmask");
return -1 ;
}
else
printf("unblock\n");
while(1)
{
sleep(1) ;
}
return 0 ;
}
編譯 gcc sigset.c -o sigset。
執行 ./sigset,執行結果如下:
blocked
SIGINT in set
signal SIGINT is pending
If you want to quit,please try SIGQUIT
退出
摘錄自《深入淺出Linux工具與程式設計》