一 信號的種類
可靠信號與不可靠信號, 實時信號與非實時信號
可靠信號就是實時信號, 那些從UNIX系統(tǒng)繼承過來的信號都是非可靠信號, 表現(xiàn)在信號
不支持排隊,信號可能會丟失, 比如發(fā)送多次相同的信號, 進(jìn)程只能收到一次. 信號值小于
SIGRTMIN的都是非可靠信號.
非可靠信號就是非實時信號, 后來, Linux改進(jìn)了信號機制, 增加了32種新的信號, 這些信
號都是可靠信號, 表現(xiàn)在信號支持排隊, 不會丟失, 發(fā)多少次, 就可以收到多少次. 信號值
位于 [SIGRTMIN, SIGRTMAX] 區(qū)間的都是可靠信號.
關(guān)于可靠信號, 還可以參考WIKI的一段話:
Text代碼

- The real-time signals, ranging from SIGRTMIN to SIGRTMAX, are a set of signals that can be used for application-defined purposes.
- Because SIGRTMIN may have different values on different Unix-like systems, applications should always refer to the signals in the form SIGRTMIN+n, where n is a constant integer expression.
- The real-time signals have a number of properties that differentiate them from other signals and make them suitable for application-defined purposes:
- * Multiple instances of a real-time signal can be sent to a process and all will be delivered.
- * Real-time signals can be accompanied by an integer or pointer value (see sigqueue[2]).
- * Real-time signals are guaranteed to be delivered in the order they were emitted.
命令行輸入 kill -l, 可以列出系統(tǒng)支持的所有信號:
C代碼

- ~> kill -l
- 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- 63) SIGRTMAX-1 64) SIGRTMAX
非可靠信號一般都有確定的用途及含義, 可靠信號則可以讓用戶自定義使用
二 信號的安裝
早期的Linux使用系統(tǒng)調(diào)用 signal 來安裝信號
#include <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
該函數(shù)有兩個參數(shù), signum指定要安裝的信號, handler指定信號的處理函數(shù).
該函數(shù)的返回值是一個函數(shù)指針, 指向上次安裝的handler
經(jīng)典安裝方式:
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
signal(SIGINT, sig_handler);
}
先獲得上次的handler, 如果不是忽略信號, 就安裝此信號的handler
由于信號被交付后, 系統(tǒng)自動的重置handler為默認(rèn)動作, 為了使信號在handler
處理期間, 仍能對后繼信號做出反應(yīng), 往往在handler的第一條語句再次調(diào)用 signal
sig_handler(ing signum)
{
/* 重新安裝信號 */
signal(signum, sig_handler);
......
}
我們知道在程序的任意執(zhí)行點上, 信號隨時可能發(fā)生, 如果信號在sig_handler重新安裝
信號之前產(chǎn)生, 這次信號就會執(zhí)行默認(rèn)動作, 而不是sig_handler. 這種問題是不可預(yù)料的.
使用庫函數(shù) sigaction 來安裝信號
為了克服非可靠信號并同一SVR4和BSD之間的差異, 產(chǎn)生了 POSIX 信號安裝方式, 使用
sigaction安裝信號的動作后, 該動作就一直保持, 直到另一次調(diào)用 sigaction建立另一個
動作為止. 這就克服了古老的 signal 調(diào)用存在的問題
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
經(jīng)典安裝方式:
struct sigaction action, old_action;
/* 設(shè)置SIGINT */
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGTERM);
action.sa_flags = 0;
/* 獲取上次的handler, 如果不是忽略動作, 則安裝信號 */
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
基于 sigaction 實現(xiàn)的庫函數(shù): signal
sigaction 自然強大, 但安裝信號很繁瑣, 目前l(fā)inux中的signal()是通過sigation()函數(shù)
實現(xiàn)的,因此,即使通過signal()安裝的信號,在信號處理函數(shù)的結(jié)尾也不必
再調(diào)用一次信號安裝函數(shù)。
三 如何屏蔽信號
所謂屏蔽, 并不是禁止遞送信號, 而是暫時阻塞信號的遞送,
解除屏蔽后, 信號將被遞送, 不會丟失. 相關(guān)API為
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(const sigset_t *set, int signum);
int sigsuspend(const sigset_t *mask);
int sigpending(sigset_t *set);
-----------------------------------------------------------------
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
sigprocmask()函數(shù)能夠根據(jù)參數(shù)how來實現(xiàn)對信號集的操作,操作主要有三種:
* SIG_BLOCK 在進(jìn)程當(dāng)前阻塞信號集中添加set指向信號集中的信號
* SIG_UNBLOCK 如果進(jìn)程阻塞信號集中包含set指向信號集中的信號,則解除
對該信號的阻塞
* SIG_SETMASK 更新進(jìn)程阻塞信號集為set指向的信號集
屏蔽整個進(jìn)程的信號:
C代碼

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
void sig_handler(int signum)
{
printf("catch SIGINT\n");
}
int main(int argc, char **argv)
{
sigset_t block;
struct sigaction action, old_action;
/* 安裝信號 */
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
/* 屏蔽信號 */
sigemptyset(&block);
sigaddset(&block, SIGINT);
printf("block SIGINT\n");
sigprocmask(SIG_BLOCK, &block, NULL);
printf("--> send SIGINT -->\n");
kill(getpid(), SIGINT);
printf("--> send SIGINT -->\n");
kill(getpid(), SIGINT);
sleep(1);
/* 解除信號后, 之前觸發(fā)的信號將被遞送,
* 但SIGINT是非可靠信號, 只會遞送一次
*/
printf("unblock SIGINT\n");
sigprocmask(SIG_UNBLOCK, &block, NULL);
sleep(2);
return 0;
}
運行結(jié)果:
C代碼

work> ./a.out
block SIGINT
--> send SIGINT -->
--> send SIGINT -->
unblock SIGINT
catch SIGINT
這里發(fā)送了兩次SIGINT信號 可以看到, 屏蔽掉SIGINT后,
信號無法遞送, 解除屏蔽后, 才遞送信號, 但只被遞送一次,
因為SIGINT是非可靠信號, 不支持排隊.
只在信號處理期間, 屏蔽其它信號
在信號的handler執(zhí)行期間, 系統(tǒng)將自動屏蔽此信號, 但如果
還想屏蔽其它信號怎么辦? 可以利用 struct sigaction 結(jié)構(gòu)體
的 sa_mask 屬性.
C代碼

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
void sig_handler(int signum)
{
printf("in handle, SIGTERM is blocked\n");
/* 在此handler內(nèi)將屏蔽掉SIGTERM, 直到此handler返回 */
printf("--> send SIGTERM -->\n");
kill(getpid(), SIGTERM);
sleep(5);
printf("handle done\n");
}
void handle_term(int signum)
{
printf("catch sigterm and exit..\n");
exit(0);
}
int main(int argc, char **argv)
{
struct sigaction action, old_action;
/* 設(shè)置SIGINT */
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
/* 安裝handler的時候, 設(shè)置在handler
* 執(zhí)行期間, 屏蔽掉SIGTERM信號 */
sigaddset(&action.sa_mask, SIGTERM);
action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
/* 設(shè)置SIGTERM */
action.sa_handler = handle_term;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGTERM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGTERM, &action, NULL);
}
printf("--> send SIGINT -->\n");
kill(getpid(), SIGINT);
while (1) {
sleep(1);
}
return 0;
}
運行結(jié)果:
C代碼

work> ./a.out
--> send SIGINT -->
in handle, SIGTERM is blocked
--> send SIGTERM -->
handle done
catch sigterm and exit..
收到SIGINT后, 進(jìn)入sig_handler,此時發(fā)送SIGTERM信號將被屏蔽,
等sig_handler返回后, 才收到SIGTERM信號, 然后退出程序
四 如何獲取未決信號
所謂未決信號, 是指被阻塞的信號, 等待被遞送的信號.
int sigsuspend(const sigset_t *mask));
sigpending(sigset_t *set))獲得當(dāng)前已遞送到進(jìn)程,
卻被阻塞的所有信號,在set指向的信號集中返回結(jié)果。
C代碼

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
/* 版本1, 可靠信號將被遞送多次 */
//#define MYSIGNAL SIGRTMIN+5
/* 版本2, 不可靠信號只被遞送一次 */
#define MYSIGNAL SIGTERM
void sig_handler(int signum)
{
psignal(signum, "catch a signal");
}
int main(int argc, char **argv)
{
sigset_t block, pending;
int sig, flag;
/* 設(shè)置信號的handler */
signal(MYSIGNAL, sig_handler);
/* 屏蔽此信號 */
sigemptyset(&block);
sigaddset(&block, MYSIGNAL);
printf("block signal\n");
sigprocmask(SIG_BLOCK, &block, NULL);
/* 發(fā)兩次信號, 看信號將會被觸發(fā)多少次 */
printf("---> send a signal --->\n");
kill(getpid(), MYSIGNAL);
printf("---> send a signal --->\n");
kill(getpid(), MYSIGNAL);
/* 檢查當(dāng)前的未決信號 */
flag = 0;
sigpending(&pending);
for (sig = 1; sig < NSIG; sig++) {
if (sigismember(&pending, sig)) {
flag = 1;
psignal(sig, "this signal is pending");
}
}
if (flag == 0) {
printf("no pending signal\n");
}
/* 解除此信號的屏蔽, 未決信號將被遞送 */
printf("unblock signal\n");
sigprocmask(SIG_UNBLOCK, &block, NULL);
/* 再次檢查未決信號 */
flag = 0;
sigpending(&pending);
for (sig = 1; sig < NSIG; sig++) {
if (sigismember(&pending, sig)) {
flag = 1;
psignal(sig, "a pending signal");
}
}
if (flag == 0) {
printf("no pending signal\n");
}
return 0;
}
這個程序有兩個版本:
可靠信號版本, 運行結(jié)果:
C代碼

work> ./a.out
block signal
---> send a signal --->
---> send a signal --->
this signal is pending: Unknown signal 39
unblock signal
catch a signal: Unknown signal 39
catch a signal: Unknown signal 39
no pending signal
發(fā)送兩次可靠信號, 最終收到兩次信號
非可靠信號版本, 運行結(jié)果:
C代碼

work> ./a.out
block signal
---> send a signal --->
---> send a signal --->
this signal is pending: Terminated
unblock signal
catch a signal: Terminated
no pending signal
發(fā)送兩次非可靠信號, 最終只收到一次
五 被中斷了的系統(tǒng)調(diào)用
一些IO系統(tǒng)調(diào)用執(zhí)行時, 如 read 等待輸入期間, 如果收到一個信號,
系統(tǒng)將中斷read, 轉(zhuǎn)而執(zhí)行信號處理函數(shù). 當(dāng)信號處理返回后, 系統(tǒng)
遇到了一個問題: 是重新開始這個系統(tǒng)調(diào)用, 還是讓系統(tǒng)調(diào)用失敗?
早期UNIX系統(tǒng)的做法是, 中斷系統(tǒng)調(diào)用, 并讓系統(tǒng)調(diào)用失敗, 比如read
返回 -1, 同時設(shè)置 errno 為 EINTR
中斷了的系統(tǒng)調(diào)用是沒有完成的調(diào)用, 它的失敗是臨時性的, 如果再次調(diào)用
則可能成功, 這并不是真正的失敗, 所以要對這種情況進(jìn)行處理, 典型的方式為:
while (1) {
n = read(fd, buf, BUFSIZ);
if (n == -1 && errno != EINTR) {
printf("read error\n");
break;
}
if (n == 0) {
printf("read done\n");
break;
}
}
這樣做邏輯比較繁瑣, 事實上, 我們可以從信號的角度
來解決這個問題, 安裝信號的時候, 設(shè)置 SA_RESTART
屬性, 那么當(dāng)信號處理函數(shù)返回后, 被該信號中斷的系統(tǒng)
調(diào)用將自動恢復(fù).
C代碼

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
void sig_handler(int signum)
{
printf("in handler\n");
sleep(1);
printf("handler return\n");
}
int main(int argc, char **argv)
{
char buf[100];
int ret;
struct sigaction action, old_action;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
/* 版本1:不設(shè)置SA_RESTART屬性
* 版本2:設(shè)置SA_RESTART屬性 */
//action.sa_flags |= SA_RESTART;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
bzero(buf, 100);
ret = read(0, buf, 100);
if (ret == -1) {
perror("read");
}
printf("read %d bytes:\n", ret);
printf("%s\n", buf);
return 0;
}
版本1, 不設(shè)置 SA_RESTART 屬性 :
C代碼

work> gcc signal.c
work> ./a.out
^Cin handler
handler return
read: Interrupted system call
read -1 bytes:
在 read 等待數(shù)據(jù)期間, 按下ctrl + c, 觸發(fā) SIGINT 信號,
handler 返回后, read 被中斷, 返回 -1
版本2, 設(shè)置 SA_RESTART 屬性:
C代碼

work> gcc signal.c
work> ./a.out
^Cin handler
handler return
hello, world
read 13 bytes:
hello, world
handler 返回后, read 系統(tǒng)調(diào)用被恢復(fù)執(zhí)行, 繼續(xù)等待數(shù)據(jù).
六 非局部控制轉(zhuǎn)移
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
void longjmp(jmp_buf env, int val);
void siglongjmp(sigjmp_buf env, int val);
--------------------------------------------------------
setjmp()會保存目前堆棧環(huán)境,然后將目前的地址作一個記號,
而在程序其他地方調(diào)用 longjmp 時便會直接跳到這個記號位置,
然后還原堆棧,繼續(xù)程序好執(zhí)行。
setjmp調(diào)用有點fork的味道, setjmp()return 0 if returning directly,
and non-zero when returning from longjmp using the saved context.
if (setjmp(jmpbuf)) {
printf("return from jmp\n");
} else {
printf("return directly\n");
}
setjmp 和 sigsetjmp 的唯一區(qū)別是: setjmp 不一定會恢復(fù)信號集合,
而sigsetjmp可以保證恢復(fù)信號集合
C代碼

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <setjmp.h>
void sig_alrm(int signum);
void sig_usr1(int signum);
void print_mask(const char *str);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjmp;
static int sigalrm_appear;
int main(int argc, char **argv)
{
struct sigaction action, old_action;
/* 設(shè)置SIGUSR1 */
action.sa_handler = sig_usr1;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGUSR1, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGUSR1, &action, NULL);
}
/* 設(shè)置SIGALRM */
action.sa_handler = sig_alrm;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGALRM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGALRM, &action, NULL);
}
print_mask("starting main:");
if (sigsetjmp(jmpbuf, 1) != 0) {
print_mask("exiting main:");
} else {
printf("sigsetjmp return directly\n");
canjmp = 1;
while (1) {
sleep(1);
}
}
return 0;
}
void sig_usr1(int signum)
{
time_t starttime;
if (canjmp == 0) {
printf("please set jmp first\n");
return;
}
print_mask("in sig_usr1:");
alarm(1);
while (!sigalrm_appear);
canjmp = 0;
siglongjmp(jmpbuf, 1);
}
void sig_alrm(int signum)
{
print_mask("in sig_alrm:");
sigalrm_appear = 1;
return;
}
void print_mask(const char *str)
{
sigset_t sigset;
int i, errno_save, flag = 0;
errno_save = errno;
if (sigprocmask(0, NULL, &sigset) < 0) {
printf("sigprocmask error\n");
exit(0);
}
printf("%s\n", str);
fflush(stdout);
for (i = 1; i < NSIG; i++) {
if (sigismember(&sigset, i)) {
flag = 1;
psignal(i, "a blocked signal");
}
}
if (!flag) {
printf("no blocked signal\n");
}
printf("\n");
errno = errno_save;
}
運行結(jié)果:
C代碼

work> ./a.out &
[4] 28483
starting main:
no blocked signal
sigsetjmp return directly
kill -USR1 28483
in sig_usr1:
a blocked signal: User defined signal 1
in sig_alrm:
a blocked signal: User defined signal 1
a blocked signal: Alarm clock
exiting main:
no blocked signal
七 信號的生命周期
從信號發(fā)送到信號處理函數(shù)的執(zhí)行完畢
對于一個完整的信號生命周期(從信號發(fā)送到相應(yīng)的處理函數(shù)執(zhí)行完畢)來說,
可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:
信號誕生;信號在進(jìn)程中注冊完畢;信號在進(jìn)程中的注銷完畢;信號處理函數(shù)執(zhí)行完畢。
下面闡述四個事件的實際意義:
信號"誕生"。信號的誕生指的是觸發(fā)信號的事件發(fā)生
(如檢測到硬件異常、定時器超時以及調(diào)用信號發(fā)送函數(shù)
kill()或sigqueue()等)。信號在目標(biāo)進(jìn)程中"注冊";
進(jìn)程的task_struct結(jié)構(gòu)中有關(guān)于本進(jìn)程中未決信號的數(shù)據(jù)成員:
struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
第三個成員是進(jìn)程中所有未決信號集,第一、第二個成員分別指向一個
sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號鏈表")的首尾,鏈表中
的每個sigqueue結(jié)構(gòu)刻畫一個特定信號所攜帶的信息,并指向下一個
sigqueue結(jié)構(gòu):
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信號的注冊
信號在進(jìn)程中注冊指的就是信號值加入到進(jìn)程的未決信號集中
(sigpending結(jié)構(gòu)的第二個成員sigset_t signal),
并且加入未決信號鏈表的末尾。 只要信號在進(jìn)程的未決信號集中,
表明進(jìn)程已經(jīng)知道這些信號的存在,但還沒來得及處理,或者該信號被進(jìn)程阻塞。
當(dāng)一個實時信號發(fā)送給一個進(jìn)程時,不管該信號是否已經(jīng)在進(jìn)程中注冊,
都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。
這意味著同一個實時信號可以在同一個進(jìn)程的未決信號鏈表中添加多次.
當(dāng)一個非實時信號發(fā)送給一個進(jìn)程時,如果該信號已經(jīng)在進(jìn)程中注冊,
則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。
這意味著同一個非實時信號在進(jìn)程的未決信號鏈表中,至多占有一個sigqueue結(jié)構(gòu).
一個非實時信號誕生后,
(1)、如果發(fā)現(xiàn)相同的信號已經(jīng)在目標(biāo)結(jié)構(gòu)中注冊,則不再注冊,對于進(jìn)程來說,
相當(dāng)于不知道本次信號發(fā)生,信號丟失.
(2)、如果進(jìn)程的未決信號中沒有相同信號,則在進(jìn)程中注冊自己。
信號的注銷。
在進(jìn)程執(zhí)行過程中,會檢測是否有信號等待處理
(每次從系統(tǒng)空間返回到用戶空間時都做這樣的檢查)。如果存在未決
信號等待處理且該信號沒有被進(jìn)程阻塞,則在運行相應(yīng)的信號處理函數(shù)前,
進(jìn)程會把信號在未決信號鏈中占有的結(jié)構(gòu)卸掉。是否將信號從進(jìn)程未決信號集
中刪除對于實時與非實時信號是不同的。對于非實時信號來說,由于在未決信
號信息鏈中最多只占用一個sigqueue結(jié)構(gòu),因此該結(jié)構(gòu)被釋放后,應(yīng)該把信
號在進(jìn)程未決信號集中刪除(信號注銷完畢);而對于實時信號來說,可能在
未決信號信息鏈中占用多個sigqueue結(jié)構(gòu),因此應(yīng)該針對占用sigqueue結(jié)構(gòu)
的數(shù)目區(qū)別對待:如果只占用一個sigqueue結(jié)構(gòu)(進(jìn)程只收到該信號一次),
則應(yīng)該把信號在進(jìn)程的未決信號集中刪除(信號注銷完畢)。否則,不應(yīng)該在進(jìn)程
的未決信號集中刪除該信號(信號注銷完畢)。
進(jìn)程在執(zhí)行信號相應(yīng)處理函數(shù)之前,首先要把信號在進(jìn)程中注銷。
信號生命終止。
進(jìn)程注銷信號后,立即執(zhí)行相應(yīng)的信號處理函數(shù),執(zhí)行完畢后,
信號的本次發(fā)送對進(jìn)程的影響徹底結(jié)束。
八 關(guān)于可重入函數(shù)
在信號處理函數(shù)中應(yīng)使用可重入函數(shù)。
信號處理程序中應(yīng)當(dāng)使用可重入函數(shù)
(注:所謂可重入函數(shù)是指一個可以被多個任務(wù)調(diào)用的過程,
任務(wù)在調(diào)用時不必?fù)?dān)心數(shù)據(jù)是否會出錯)。因為進(jìn)程在收到信號
后,就將跳轉(zhuǎn)到信號處理函數(shù)去接著執(zhí)行。如果信號處理函數(shù)中
使用了不可重入函數(shù),那么信號處理函數(shù)可能會修改原來進(jìn)程中
不應(yīng)該被修改的數(shù)據(jù),這樣進(jìn)程從信號處理函數(shù)中返回接著執(zhí)行時,
可能會出現(xiàn)不可預(yù)料的后果。不可再入函數(shù)在信號處理函數(shù)中被視為
不安全函數(shù)。滿足下列條件的函數(shù)多數(shù)是不可再入的:
(1)使用靜態(tài)的數(shù)據(jù)結(jié)構(gòu),如getlogin(),gmtime(),getgrgid(),
getgrnam(),getpwuid()以及getpwnam()等等;
(2)函數(shù)實現(xiàn)時,調(diào)用了malloc()或者free()函數(shù);
(3)實現(xiàn)時使用了標(biāo)準(zhǔn)I/O函數(shù)的。The Open Group視下列函數(shù)為可再入的:
_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、
cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、
close()、creat()、dup()、dup2()、execle()、execve()、
fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、
geteuid()、getgid()、getgroups()、getpgrp()、getpid()、
getppid()、getuid()、kill()、link()、lseek()、mkdir()、
mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、
read()、rename()、rmdir()、setgid()、setpgid()、setsid()、
setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、
sigfillset()、sigismember()、signal()、sigpending()、
sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、
tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、
tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、
umask()、uname()、unlink()、utime()、wait()、waitpid()、
write()。
即使信號處理函數(shù)使用的都是"安全函數(shù)",同樣要注意進(jìn)入處理函數(shù)時,
首先要保存errno的值,結(jié)束時,再恢復(fù)原值。因為,信號處理過程中,
errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可重入函數(shù),
因為不能保證緊接著兩個函數(shù)的其它調(diào)用是安全的。