|
信號(hào)(signal)是一種進(jìn)程間通信機(jī)制,它給應(yīng)用程序提供一種異步的軟件中斷,使應(yīng)用程序有機(jī)會(huì)接受其他程序活終端發(fā)送的命令(即信號(hào))。應(yīng)用程序收到信號(hào)后,有三種處理方式:忽略,默認(rèn),或捕捉。進(jìn)程收到一個(gè)信號(hào)后,會(huì)檢查對(duì)該信號(hào)的處理機(jī)制。如果是SIG_IGN,就忽略該信號(hào);如果是SIG_DFT,則會(huì)采用系統(tǒng)默認(rèn)的處理動(dòng)作,通常是終止進(jìn)程或忽略該信號(hào);如果給該信號(hào)指定了一個(gè)處理函數(shù)(捕捉),則會(huì)中斷當(dāng)前進(jìn)程正在執(zhí)行的任務(wù),轉(zhuǎn)而去執(zhí)行該信號(hào)的處理函數(shù),返回后再繼續(xù)執(zhí)行被中斷的任務(wù)。
下面就來(lái)說(shuō)說(shuō)與信號(hào)有關(guān)的函數(shù)吧。 最簡(jiǎn)單signal函數(shù) typedef void (*sighandler_t) (int) sighandler_t signal(int signum, sighandler_t handler);
返回原信號(hào)處理函數(shù),或SIG_ERR
signal()是最簡(jiǎn)單的給進(jìn)程安裝信號(hào)處理器的函數(shù),第一個(gè)參數(shù)指定信號(hào),第二個(gè)參數(shù)為該信號(hào)指定一個(gè)處理函數(shù)。 如下是一個(gè)最簡(jiǎn)單的處理信號(hào)的程序,它捕捉SIGUSR1,忽略SIGUSR2,按系統(tǒng)默認(rèn)處理SIGINT,SIGUSR1和SIGUSR2是Linux提供的用戶定義信號(hào),可用于任何應(yīng)用程序。主程序什么都不干,只用pause()循環(huán)等待信號(hào)。 例程1 最簡(jiǎn)單的信號(hào)處理 static void pr_mask(const char * string) {
sigset_t procmask; sigprocmask(SIG_SETMASK, NULL, &procmask);
printf("%s: ", string);
if(sigismember(&procmask, SIGINT)) printf("SIGINT "); if(sigismember(&procmask, SIGUSR1)) printf("SIGUSR1 "); if(sigismember(&procmask, SIGUSR2)) printf("SIGUSR2 "); if(sigismember(&procmask, SIGTERM)) printf("SIGTERM "); if(sigismember(&procmask, SIGQUIT)) printf("SIGQUIT "); printf("\n"); } static void sigusr(int signum)
{ pr_mask(“int sigusr”); if(signum == SIGUSR1)
printf(“SIGUSR1 received\n”); else if(signum == SIGUSR2) printf(“SIGUSR2 received\n”); else printf(“signal %d received\n”, signum); } int main(void)
{ if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf(“error catching SIGUSR1\n”); exit(1); } if(signal(SIGUSR2, SIG_IGN) == SIG_ERR) {
printf(“error ignoring SIGUSR2\n”); exit(1); } if(signal(SIGINT, SIG_DFT) == SIG_ERR) {
printf(“error setting SIGINT to default\n”); exit(1); } while(1)
pause(); exit(0);
} 后臺(tái)運(yùn)行該程序,并用kill發(fā)送信號(hào)給它。 $./a.out & [1] 3725
$kill -USR1 3725
in sigusr: SIGUSR1
SIGUSR1 received
$kill -USR2 3725
[1]+ User defined signal 2 ./a.out
我們可以看到,Linux系統(tǒng)對(duì)SIGUSR2的默認(rèn)動(dòng)作是終止進(jìn)程。 中斷與自動(dòng)重啟動(dòng) 前面說(shuō)過(guò),信號(hào)是一種軟件中斷機(jī)制,這就產(chǎn)生了一個(gè)問(wèn)題:如果信號(hào)到來(lái)時(shí)進(jìn)城正在執(zhí)行某個(gè)低速系統(tǒng)調(diào)用,系統(tǒng)應(yīng)該怎么處理?是暫時(shí)阻塞系統(tǒng)調(diào)用返回,在信號(hào)處理程序完成后繼續(xù)沒(méi)完成的系統(tǒng)調(diào)用呢,還是讓系統(tǒng)調(diào)用出錯(cuò)返回,同時(shí)把errno設(shè)置為EINTR,讓調(diào)用者去做進(jìn)一步的出錯(cuò)檢查呢?用事實(shí)說(shuō)話,讓我們做一個(gè)試驗(yàn)先吧。 下面的程序讀取標(biāo)準(zhǔn)輸入并把它輸出到標(biāo)準(zhǔn)輸出,在此期間,我們給進(jìn)程發(fā)送SIGUSR1信號(hào),以此來(lái)確定Linux在收到信號(hào)后是如何對(duì)處理系統(tǒng)調(diào)用的。 例程2 信號(hào)與自動(dòng)重啟動(dòng)的signal版本 int main(void)
{ char buf[BUFSIZ]; int n; signal(SIGUSR1, sig_usr);
while(1) {
if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) { if(errno == EINTR) printf(“read is interrupted\n”); } else { write(STDOUT_FILENO, buf, n); } } exit(0);
} 運(yùn)行該程序,并從另一個(gè)終端給該進(jìn)程發(fā)送信號(hào)SIGUSR1。 $./a.out first line
first line
in sigusr: SIGUSR1
SIGUSR1 received
second line
second line
in sigusr: SIGUSR1
SIGUSR1 received
^C
可見(jiàn)對(duì)由signal()函數(shù)安裝的信號(hào)處理程序,系統(tǒng)默認(rèn)會(huì)自動(dòng)重啟動(dòng)被中斷的系統(tǒng)調(diào)用,而不是讓它出錯(cuò)返回,所以應(yīng)用程序不必針對(duì)慢速系統(tǒng)調(diào)用的errno,做EINTR檢查,這就是自動(dòng)重啟動(dòng)機(jī)制。 我們?cè)賮?lái)看另外一個(gè)例子,它使用另一個(gè)函數(shù)sigaction()來(lái)安裝信號(hào)處理程序。sigaction()允許進(jìn)程對(duì)信號(hào)進(jìn)行更多的控制: 例程3 信號(hào)與自動(dòng)重啟動(dòng)的sigaction版本 int main(void)
{ char buf[BUFSIZ]; int n; struct sigaction sa_usr;
sa_usr.flags = 0; //SA_RESART sa_usr.sa_handler = sig_usr; sigaction(SIGUSR1, &sa_usr, NULL); //signal(SIGUSR1, sig_usr);
while(1) {
if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) { if(errno == EINTR) printf(“read is interrupted\n”); } else { write(STDOUT_FILENO, buf, n); } } exit(0);
} 此時(shí)再運(yùn)行這個(gè)程序,并從另一終端給該進(jìn)程發(fā)送信號(hào)SIGUSR1,我們會(huì)得到如下結(jié)果。
$./a.out first line
first line
in sigusr: SIGUSR1
SIGUSR1 received
read is interrupted
second line
second line
in sigusr: SIGUSR1
SIGUSR1 received
read is interrupted
^C
由此我們可以得出,Linux對(duì)sigaction()的默認(rèn)動(dòng)作是不自動(dòng)重啟動(dòng)被中斷的系統(tǒng)調(diào)用,因此如果我們?cè)谑褂胹igaction()時(shí)需要自動(dòng)重啟動(dòng)被中斷的系統(tǒng)調(diào)用,就需要使用sigaction的SA_RESTART選項(xiàng),見(jiàn)上例注釋,關(guān)于sigaction(),下文會(huì)有更多的描述。這和《UNIX環(huán)境高級(jí)編程》中對(duì)Linux信號(hào)處理的描述是一致的。 可重入函數(shù) 如前所述,進(jìn)程在收到信號(hào)并對(duì)其進(jìn)行處理時(shí),會(huì)暫時(shí)中斷當(dāng)前正在執(zhí)行的指令序列,轉(zhuǎn)而去執(zhí)行信號(hào)處理程序。但是信號(hào)的到來(lái),往往是無(wú)法預(yù)測(cè)的,我們無(wú)法確定進(jìn)程會(huì)在何時(shí)收到信號(hào)。如果進(jìn)程在收到信號(hào)時(shí)正在執(zhí)行malloc()調(diào)用,而此時(shí)捕捉到信號(hào),進(jìn)城就會(huì)轉(zhuǎn)而去執(zhí)行信號(hào)處理程序,而信號(hào)處理程序中又再次調(diào)用了malloc()函數(shù),那結(jié)果將會(huì)怎樣呢?進(jìn)程的??臻g很可能就會(huì)受到破壞,從而產(chǎn)生無(wú)法預(yù)料的結(jié)果。所以有些函數(shù)是不能在信號(hào)處理程序中調(diào)用的,這些函數(shù)被稱為不可重入函數(shù),而那些允許在信號(hào)處理函數(shù)中調(diào)用的函數(shù),則稱為可重入函數(shù)。下表列出了Linux系統(tǒng)中的可重入函數(shù)(摘自《UNIX環(huán)境高級(jí)編程》),對(duì)不在該表中的函數(shù),信號(hào)處理函數(shù)中要慎用。 表1 可重入函數(shù) accept
access
aio_error
aio_return
aio_suspend
alarm
bind
cfgetispeed
cfgetospeed
cfsetispeed
cfsetospeed
chdir
chmod
chown
clock_gettime
close
connect
creat
dup
dup2
execle
execve
_Exit & _exit
fchmod fchown
fcntl
fdatasync
fork
fpathconf
fstat
fsync
ftruncate
getegid
geteuid
getgid
getgroups
getpeername
getpgrp
getpid
getppid
getsockname
getsockopt
getuid
kill
link
listen
lseek lstat
mkdir
mkfifo
open
pathconf
pause
pipe
poll
posix_trace_event
pselect
raise
read
readlink
recv
recvfrom
recvmsg
rename
rmdir
select
sem_post
send
sendmsg
sendto setgid
setpgid
setsid
setsockopt
setuid
shutdown
sigaction
sigaddset
sigdelset
sigemptyset
sigfillset
sigismember
signal
sigpause
sigpending
sigprocmask
sigqueue
sigset
sigsuspend
sleep
socket
socketpair
stat symlink
sysconf
tcdrain
tcflow
tcflush
tcgetattr
tcgetpgrp
tcsendbreak
tcsetattr
tcsetpgrp
time
timer_getoverrun
timer_gettime
timer_settime
times
umask
uname
unlink
utime
wait
waitpid
write
發(fā)送信號(hào)的kill和raise函數(shù)
int kill(pid_t pid, int sig); int raise(int sig);
kill()發(fā)送信號(hào)給指定進(jìn)程,raise()發(fā)送信號(hào)給進(jìn)程本身。對(duì)kill()的pid,有如下描述: pid > 0 將信號(hào)發(fā)送給ID為pid的進(jìn)程 pid == 0 將信號(hào)發(fā)送給與發(fā)送進(jìn)程屬于同意個(gè)進(jìn)程組的所有進(jìn)程
pid < 0 將信號(hào)發(fā)送給進(jìn)程組ID等于pid絕對(duì)值的所有進(jìn)程
pid == -1 將信號(hào)發(fā)送給該進(jìn)程有權(quán)限發(fā)送的系統(tǒng)里的所有進(jìn)程
所有信號(hào)的發(fā)送都要先經(jīng)過(guò)權(quán)限檢查,如果進(jìn)程沒(méi)有相應(yīng)發(fā)送的權(quán)限,kill()會(huì)出錯(cuò)返回,并把errno設(shè)為EPERM。但也有一個(gè)例外,對(duì)SIGCONT,進(jìn)程可以將它發(fā)送給當(dāng)前會(huì)話的所有進(jìn)程。 產(chǎn)生時(shí)鐘信號(hào)SIGALRM的alarm函數(shù) unsigned int alarm(unsigned int seconds); alarm()函數(shù)可設(shè)置一個(gè)計(jì)時(shí)器,計(jì)時(shí)器超時(shí)就產(chǎn)生SIGALRM信號(hào)。由于每個(gè)進(jìn)程只能有一個(gè)SIGALRM處理程序,所以只能為一個(gè)進(jìn)程設(shè)置一個(gè)計(jì)時(shí)器,所以alarm()和setitimer()會(huì)共享同一個(gè)SIGALRM信號(hào)和該信號(hào)的處理函數(shù)。也就是說(shuō),alarm()和setitimer()彼此會(huì)互相影響。調(diào)用alarm(),會(huì)使先前設(shè)置的計(jì)時(shí)器失效,并把沒(méi)有超時(shí)的時(shí)間作為當(dāng)前alarm的返回值。如先前設(shè)置的時(shí)鐘為100秒,當(dāng)前調(diào)用alarm()時(shí)才經(jīng)過(guò)30秒,剩余的70秒就作為alarm()的返回值,并用alarm()中指定的秒數(shù)重新設(shè)置計(jì)時(shí)器。如果seconds為0,則會(huì)取消先前設(shè)置的計(jì)時(shí)器,并將其余留值作為alarm()的返回值。 等待信號(hào)的pause函數(shù) int pause(void); pause()會(huì)使當(dāng)前進(jìn)程掛起,直到捕捉到一個(gè)信號(hào),對(duì)指定為忽略的信號(hào),pause()不會(huì)返回。只有執(zhí)行了一個(gè)信號(hào)處理函數(shù),并從其返回,puase()才返回-1,并將errno設(shè)為EINTR。詳見(jiàn)前面的第一個(gè)例子。 信號(hào)屏蔽字(process signal mask) 每個(gè)進(jìn)程都會(huì)有一個(gè)信號(hào)屏蔽字,它規(guī)定了當(dāng)前進(jìn)程要阻塞的信號(hào)集。對(duì)于每種可能的信號(hào),信號(hào)屏蔽字中都會(huì)有一位與之對(duì)應(yīng),如果該位被設(shè)置,該信號(hào)當(dāng)前就是阻塞的。進(jìn)程可以通過(guò)sigprocmask()來(lái)獲得和修改當(dāng)前進(jìn)程的信號(hào)屏蔽字。 信號(hào)集(signal set) 信號(hào)集是一種特殊的數(shù)據(jù)類型,由于無(wú)法確定信號(hào)的多少,所以不能用簡(jiǎn)單數(shù)據(jù)類型來(lái)包含所有可能的信號(hào),所以系統(tǒng)就定義了一個(gè)sigset_t的數(shù)據(jù)類型專門用于信號(hào)集。同時(shí)還定義了一族用于處理信號(hào)集的函數(shù)。這樣用戶可以不必關(guān)心信號(hào)集的實(shí)現(xiàn),只要使用這組函數(shù)來(lái)處理信號(hào)集就可以了。 信號(hào)集函數(shù) 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);
sigemptyset()和sigfillset()都用于初始化一個(gè)信號(hào)集,前者用于清空信號(hào)集中所有的信號(hào),后者則用于設(shè)置信號(hào)集中所有的信號(hào);信號(hào)集在使用前必須要經(jīng)過(guò)初始化,初始化后,就可以用sigaddset()和sigdelset()往信號(hào)集里添加刪除信號(hào)了。sigismember()用于判斷指定信號(hào)是否在信號(hào)集中。 修改信號(hào)屏蔽字的sigprocmask函數(shù) int sigprocmask(int how, const sigset_t * set, sigset_t * oldset); sigpromask()根據(jù)how指定的方式,設(shè)置進(jìn)程的當(dāng)前信號(hào)屏蔽字為set,并將舊的信號(hào)屏蔽字保存在oldset中返回。如果set為NULL,則不修改當(dāng)前信號(hào)屏蔽字,而將其通過(guò)oldset返回;如果oldset為NULL,則不會(huì)返回舊的信號(hào)屏蔽字。how支持三種方式,見(jiàn)下表。 表2 設(shè)置信號(hào)屏蔽字的方式 how
說(shuō)明 SIG_BLOCK SIG_UNBLOCK
SIG_SETMASK
設(shè)置阻塞set指定的信號(hào)集 設(shè)置解除阻塞set指定的信號(hào)集
設(shè)置當(dāng)前信號(hào)屏蔽字為set,在set中的信號(hào)都會(huì)被阻塞,不在set中的信號(hào)會(huì)被遞送
如果我們想阻塞SIGUSR1,有兩種方式。 // using SIG_BLOCK
sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGUSR1); sigprocmask(SIG_BLOCK, &sigset, NULL); // or using SIG_SETMASK
sigset_t set, oldset; // get current signal mask sigprocmask(SIG_SETMASK, NULL, &set); // add SIGUSR1 into the signal mask sigaddset(&set, SIGUSR1); sigprocmask(SIG_SETMASK, &set, &oldset); 同樣,如果要解除阻塞SIGUSR1,也有兩種方式。
// using SIG_UNBLOCK
sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGUSR1); sigprocmask(SIG_UNBLOCK, &sigset, NULL); // or using SIG_SETMASK
sigset_t set, oldset; // get current signal mask sigprocmask(SIG_SETMASK, NULL, &set); // delete SIGUSR1 from the signal mask sigdelset(&set, SIGUSR1); sigprocmask(SIG_SETMASK, &set, &oldset); 信號(hào)未決(pending) 信號(hào)是由某些事件產(chǎn)生的,這些事件可能是硬件異常(如被零除),軟件條件(如計(jì)時(shí)器超時(shí)),終端信號(hào)或調(diào)用kill()/raise()函數(shù)。信號(hào)產(chǎn)生時(shí),內(nèi)核通常會(huì)在進(jìn)程表中設(shè)置某種標(biāo)志,表示當(dāng)前信號(hào)的狀態(tài)。當(dāng)內(nèi)核對(duì)信號(hào)采取某種動(dòng)作時(shí),我們說(shuō)向進(jìn)程遞送(deliver)了一個(gè)信號(hào),而在信號(hào)產(chǎn)生和遞送之間的間隔內(nèi),該信號(hào)的狀態(tài)是未決的(pending)。 獲得未決的信號(hào)sigpending int sigpending(sigset_t * set); 該函數(shù)在set中返回進(jìn)程中當(dāng)前尚未遞送的信號(hào)集。 功能更全的sigaction函數(shù) int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact); struct sigaction { void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
siginfo_t { int si_signo; // Signal number
int si_errno; // An errno value
int si_code; // signal code
pid_t si_pid; // sending process ID
pid_t si_uid; // Real user ID of sending process
int si_status; // Exit value or signal
...
};
sigaction()的功能是為信號(hào)指定相關(guān)的處理程序,但是它在執(zhí)行信號(hào)處理程序時(shí),會(huì)把當(dāng)前信號(hào)加入到進(jìn)程的信號(hào)屏蔽字中,從而防止在進(jìn)行信號(hào)處理期間信號(hào)丟失。從前面的例子我們可以看到,簡(jiǎn)單的signal()函數(shù)也具有同樣的功能,這是由于signal()已經(jīng)被重新實(shí)現(xiàn)的緣故,所以如果不在乎對(duì)信號(hào)的更多的控制,我們盡可放心大膽的使用簡(jiǎn)單的signal()函數(shù)。signum指定將要改變處理行為的信號(hào);act指定該信號(hào)的處理動(dòng)作,oldact用于返回該信號(hào)先前的處理動(dòng)作。 在sigaction結(jié)構(gòu)中,sa_handler和sa_sigaction用于指定信號(hào)處理函數(shù),但要注意,二者只能用其一,因?yàn)樗鼈冊(cè)趦?nèi)部可能會(huì)實(shí)現(xiàn)為union結(jié)構(gòu)。除了在為sa_flags指定SA_SIGINFO標(biāo)志時(shí),會(huì)使用sa_sigaction字段外,其他情況下都應(yīng)該只用sa_handler字段。 sa_mask用于指定在當(dāng)前信號(hào)處理程序執(zhí)行期間,需要阻塞的信號(hào)集。如在處理SIGUSR1期間,我們希望暫時(shí)阻塞SIGUSR2,就應(yīng)該把SIGUSR2加到SIGUSR1的sa_mask中。信號(hào)處理程序返回后,會(huì)自動(dòng)解除對(duì)SIGUSR2的阻塞,詳見(jiàn)例程4。 sa_flags用于指定信號(hào)處理動(dòng)的選項(xiàng)標(biāo)志,詳見(jiàn)手冊(cè)。這里我想說(shuō)的是SA_RESTART和SA_SIGINFO。SA_RESTART用于控制信號(hào)的自動(dòng)重啟動(dòng)機(jī)制,如前面例子所示,對(duì)signal(),Linux默認(rèn)會(huì)自動(dòng)重啟動(dòng)被中斷的系統(tǒng)調(diào)用;而對(duì)于sigaction(),Linux默認(rèn)并不會(huì)自動(dòng)重啟動(dòng),所以如果希望執(zhí)行信號(hào)處理后自動(dòng)重啟動(dòng)先前中斷的系統(tǒng)調(diào)用,就需要為sa_flags指定SA_RESTART標(biāo)志。對(duì)于SA_SIGINFO,手冊(cè)上說(shuō)此標(biāo)志可能會(huì)導(dǎo)致對(duì)信號(hào)的可靠排隊(duì),但是從下面的例子我們將會(huì)看到,Linux并沒(méi)有對(duì)信號(hào)進(jìn)行排隊(duì)。 例程4 sigaction函數(shù) int main(void)
{ struct sigaction act_usr; act_usr.sa_flags = 0;
act_usr.sa_handler = sigusr; sigemptyset(&act_usr.sa_mask); // add the signal you want to block while SIGUSR1 is processing here sigaddset(&act_usr.sa_mask, SIGUSR2); // we dont care about the old action of SIGUSR1 sigaction (SIGUSR1, &act_usr, NULL); while(1)
pause(); } 運(yùn)行結(jié)果如下:
$./a.out & [1] 16385
$kill -USR1 16385
in sig_usr1: SIGUSR1 SIGUSR2
SIGUSR1 recieved
可見(jiàn)在SIGUSR1處理期間,SIGUSR2已經(jīng)被加入到進(jìn)程的屏蔽字中了,所以在此期間,SIGUSR2是被暫時(shí)阻塞的。 信號(hào)排隊(duì) 如果進(jìn)程阻塞了一個(gè)信號(hào),在沒(méi)有對(duì)其解除阻塞之前,該信號(hào)產(chǎn)生了多次,將會(huì)如何處理呢?Linux并不會(huì)對(duì)信號(hào)排隊(duì),當(dāng)信號(hào)解除阻塞后,內(nèi)核只向進(jìn)程遞送一個(gè)信號(hào),而不管在其阻塞期間有多少個(gè)信號(hào)產(chǎn)生。 下面是上例的改進(jìn)版。首先我們阻塞SIGUSR1,然后在SIGUSR2的處理函數(shù)里解除對(duì)SIGUSR1的阻塞,這樣我們就有機(jī)會(huì)在SIGUSR1阻塞期間,多發(fā)送幾個(gè)SIGUSR1來(lái)確定Linux內(nèi)核是怎樣處理的。我們期望能看到Linux對(duì)信號(hào)的排隊(duì)。 例程5 信號(hào)排隊(duì) static void sig_usr2(int sig)
{ sigset_t set; printf("SIGUSR2 recieved\n"); // unblock SIGUSR1 sigprocmask(SIG_SETMASK, NULL, &set); sigdelset(&set, SIGUSR1); sigprocmask(SIG_SETMASK, &set, NULL); } static void handler(int signum, siginfo_t * info, void * context)
{ // dump signal information printf("si_signo: %d\n", info->si_signo); printf("si_errno: %d\n", info->si_errno); printf("si_code: %d\n", info->si_code); printf("si_pid: %d\n", info->si_pid); printf("si_uid: %d\n", info->si_uid); } int main(void)
{ struct sigaction act_usr1; struct sigaction act_usr2; sigset_t mask; act_usr1.sa_flags = SA_SIGINFO;
//act_usr1.sa_handler = sigusr; sigemptyset(&act_usr1.sa_mask); act_usr1.sa_sigaction = handler; sigaction(SIGUSR1, &act_usr1, NULL); act_usr2.sa_flags = 0;
act_usr2.sa_handler = sig_usr2; sigemptyset(&act_usr2.sa_mask); sigaction(SIGUSR2, &act_usr2, NULL); // block SIGUSR1
sigprocmask(SIG_SETMASK, NULL, &mask); sigaddset(&mask, SIGUSR1); sigprocmask(SIG_SETMASK, &cmask, NULL); while(1)
pause(); exit(0);
} $ ./a.out & [1] 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR2 17165
SIGUSR2 recieved
si_signo: 10
si_errno: 0
si_code: 0
si_pid: 3945
si_uid: 500
$ ps PID TTY TIME CMD
3945 pts/1 00:00:00 bash
在SIGUSR1阻塞期間,我們向進(jìn)程發(fā)送了5個(gè)SIGUSR1,而解除阻塞后,內(nèi)核只遞送了一個(gè)SIGUSR1,說(shuō)明Linux并不支持信號(hào)排隊(duì)。另外我們還可以看到,si_signo是收到的信號(hào)的數(shù)值;si_pid是發(fā)送進(jìn)程的進(jìn)程ID,ps輸出我的終端進(jìn)程ID正是3945;si_uid是發(fā)送進(jìn)程的有效用戶ID,而我的用戶ID也正是500。對(duì)于siginfo結(jié)構(gòu)中的其它成員,我沒(méi)有打印,有興趣的可以自己研究。 信號(hào)跳轉(zhuǎn)函數(shù)sigsetjump和siglongjump int sigsetjmp(sigjmp_buf env, int savesigs); void siglongjmp(sigjmp_buf env, int val);
sigsetjmp()有多次返回,對(duì)于直接調(diào)用者(一般是主程序),它返回0;若從siglongjmp()調(diào)用(一般是信號(hào)處理程序),則返回返回siglongjmp()中的val值。所以為了避免混淆,最好不要在調(diào)用siglongjmp()時(shí),讓val=0。 另外需要說(shuō)明的是sigsetjmp()的第二個(gè)參數(shù),它用于告訴內(nèi)核,要不要保存進(jìn)程的信號(hào)屏蔽字。當(dāng)savesigs為非0時(shí),調(diào)用sigsetjmp()會(huì)在env中保存當(dāng)前的信號(hào)屏蔽字,然后在調(diào)用siglongjmp()時(shí)恢復(fù)之前保存的信號(hào)屏字。由于信號(hào)處理函數(shù)使用siglongjmp()跳轉(zhuǎn)時(shí)不屬于正常返回,所以在進(jìn)入信號(hào)處理函數(shù)時(shí)被阻塞的當(dāng)前信號(hào)就沒(méi)有機(jī)會(huì)在返回時(shí)恢復(fù)。sigsetjmp()的savesigs參數(shù)就用于是告訴系統(tǒng),在調(diào)用siglongjmp時(shí),是否需要恢復(fù)先前的信號(hào)屏蔽字。 下例向你展示了如何使用sigsetjmp()和siglongjmp(),注意這里引入了一個(gè)全局變量canjmp,它是一種同步保護(hù)機(jī)制,用于告訴信號(hào)處理程序,在進(jìn)程環(huán)境沒(méi)有準(zhǔn)備好之前,不要跳轉(zhuǎn),否則可能會(huì)導(dǎo)致混亂。 例程6 信號(hào)跳轉(zhuǎn) static sigjmp_buf jmpbuf;
// for synchronizing static volatile sig_atomic_t canjmp; static void sigusr1(int signum)
{ printf(“SIGUSR1 reveived\n”); // main process initialization is not completed
if(canjmp == 0) return; siglongjmp(jmpbuf, 1);
} satic void sigusr2(int signum)
{ printf(“SIGUSR2 reveived\n”); if(canjmp == 0)
return; siglongjmp(jmpbuf, 2);
} int main(void)
{ int n; int savemask = 1; signal(SIGUSR1, sigusr1);
signal(SIGUSR2, sigusr2); // need to save the procmask, otherwise, u have to reset the procmask
n = sigsetjmp(jmpbuf, savemask); if(n == 1) {
// jump from SIGUSR1 printf(“Jump to here from SIGUSR1\n”); if(savemask == 0) { // prevent from long jumping canjmp = 0; // reset the procmask, unblock SIGUSR1 sigset_t set; sigprocmask(SIG_SETMASK, NULL, &set); sigdelset(&set, SIGUSR1); sigprocmask(SIG_SETMASK, &set, NULL); canjmp = 1; } } else if(n == 2) { printf(“Jump to here from SIGUSR2\n”); if(savemask == 0) { canjmp = 0; sigset_t set; sigprocmask(SIG_SETMASK, NULL, &set); sigdelset(&set, SIGUSR1); sigprocmask(SIG_SETMASK, &set, NULL); canjmp = 1; } } canjmp = 1;
while(1)
pause(); exit(0);
} $ ./a.out & [1] 5485
$ kill -USR1 5485
SIGUSR1 recieved
Jump to here from SIGUSR1
$ kill -USR2 5485
SIGUSR2 recieved
Jump to here from SIGUSR2
例程6告訴我們,根據(jù)sigsetjmp()的返回值,我們也可以通過(guò)信號(hào)實(shí)現(xiàn)程序的多分支控制。另外如果沒(méi)有在sigsetjmp()時(shí)設(shè)置了savesigs,那么在siglongjmp()返回后,就要重新設(shè)置進(jìn)程的信號(hào)屏蔽字,否則該信號(hào)在一次siglongjmp()之后將被永久阻塞。 難以捉摸的sigsuspend函數(shù) int sigsuspend(const sigset_t * sigmask); 對(duì)于這個(gè)函數(shù),我始終無(wú)法清晰的理解,關(guān)于它的用法,它的作用,它的語(yǔ)義,都讓我一頭霧水?!禪NIX環(huán)境高級(jí)編程》,Linux手冊(cè),看了幾遍,都無(wú)法開(kāi)塞,真是愚鈍至極?。?/div> 從《UNIX環(huán)境高級(jí)編程》對(duì)sigsuspend()的引言看,該函數(shù)的出現(xiàn)是為了解決早期不可靠信號(hào),即信號(hào)丟失的問(wèn)題的。在早期的信號(hào)機(jī)制中,對(duì)信號(hào)解除阻塞和等待信號(hào)需要兩步進(jìn)行: sigprocmask(SIG_SETMASK, &unblockmask, NULL); pause();
在對(duì)信號(hào)解除阻塞之后和調(diào)用pause()之前有一個(gè)時(shí)間窗口,所以在這之間產(chǎn)生的信號(hào)就可能會(huì)丟失,從而是本該返回的pause()沒(méi)有返回。 sigsuspend()能讓解除信號(hào)阻塞和等待信號(hào)成為一個(gè)原子操作,這樣就避免了上述的問(wèn)題。它會(huì)把當(dāng)前進(jìn)程的信號(hào)屏蔽字設(shè)定為sigmask指定的值,所以在等待信號(hào)期間,sigmask中的信號(hào)會(huì)被暫時(shí)阻塞,而sigmask之外的信號(hào)都會(huì)被暫時(shí)解除阻塞。然后sigsuspend()掛起當(dāng)前進(jìn)程,等待,直到捕捉到一個(gè)信號(hào)或發(fā)生了一個(gè)會(huì)終止該進(jìn)程的信號(hào)。如果是捕捉到一個(gè)信號(hào)并從出來(lái)程序中返回,則sigsuspend()返回-1,把進(jìn)程信號(hào)屏蔽字設(shè)回調(diào)用sigsuspend()之前的值,并將errno設(shè)為EINTR。注意,指定為忽略的信號(hào),并不會(huì)導(dǎo)致sissuspend()返回。 注意,sigsuspend()只是暫時(shí)解除對(duì)不在sigmask中的信號(hào)的阻塞,在捕捉到一個(gè)信號(hào)后,以前阻塞的信號(hào)還會(huì)被重新阻塞,所以如果你要對(duì)一個(gè)以前阻塞的信號(hào)解除阻塞的話,在sigsuspend()返回之后,還要重新用sigprocmask來(lái)解除對(duì)該信號(hào)的阻塞。這就是我的疑問(wèn)了,如果我無(wú)意讓進(jìn)程等待任何信號(hào)的話,那這個(gè)sigsuspend()不是對(duì)我?guī)缀鹾翢o(wú)用處嗎? sigsuspend()的另一個(gè)用途就是讓進(jìn)程等待一個(gè)信號(hào)處理程序來(lái)設(shè)置一個(gè)全局變量。這個(gè)似乎還比較有用,在進(jìn)程等待某一信號(hào)時(shí),可讓進(jìn)程先掛起,直到收到該信號(hào),設(shè)置的全局變量并導(dǎo)致sigsuspend()返回,進(jìn)程才處理相應(yīng)的任務(wù),避免了CPU的無(wú)謂等待。如下例程就展示了如何讓進(jìn)程等待SIGUSR1,并在每收到一次SIGUSR1后去執(zhí)行一組相同的操作。 例程7 sigsuspend的一個(gè)應(yīng)用 static volatile sig_atomic_t ok;
static void sigusr(int signum)
{ // do nothing, set the flag only ok = 1; } int main(void)
{ sigset_t emptymask, waitmask, oldmask; // block SIGUSR1 first
sigemptyset(&waitmask); sigaddset(&waitmask, SIGUSR1); sigprocmask(SIG_BLOCK, &waitmask, &oldmask); // set SIGUSR1 handler
signal(SIGUSR1, sigusr); sigemptyset(&emptymask);
while(1) { // waiting for SIGUSR1 to set ok while(ok == 0) sigsuspend(&emptymask); // sigsuspend return, SIGUSR1 has come, and now SIGUSR1 is blocked
// reset the flag, so the process will keep waiting after the // following things are done ok = 0; // // other things need to do here // } exit(0);
} 我對(duì)sigsuspend()的理解僅限于此,不敢多賣弄,就此打住吧:) 發(fā)送SIGABRT的專用函數(shù)abort int abort(void); abort會(huì)向當(dāng)前進(jìn)程發(fā)送SIGABRT信號(hào),讓進(jìn)程做一些“善后”處理,如刷新流緩沖區(qū),關(guān)閉文件等,然后終止進(jìn)程。 其它幾個(gè)有用的小函數(shù): extern char * sys_siglist[]; 這是一個(gè)以信號(hào)為索引的字符串?dāng)?shù)組,通過(guò)它可以很容易的找到信號(hào)的字符串名稱。
void psignal(int signum, const char * msg); 此函數(shù)類似于perror(),輸出對(duì)指定信號(hào)的字符串描述。
char * strsignal(int signum);
返回指定信號(hào)的字符串描述。
例程8 其它函數(shù) int main(void) {
printf("sys_siglist:\n"); printf("SIGUSR1: %s\n", sys_siglist[SIGUSR1]); printf("SIGSEGV: %s\n", sys_siglist[SIGSEGV]); printf("SIGHUP: %s\n\n", sys_siglist[SIGHUP]); printf("strsignal:\n");
printf("SIGUSR1: %s\n", strsignal(SIGUSR1)); printf("SIGSEGV: %s\n", strsignal(SIGSEGV)); printf("SIGHUP: %s\n\n", strsignal(SIGHUP)); printf("psignal:\n");
psignal(SIGUSR1, "SIGUSR1"); psignal(SIGSEGV, "SIGSEGV"); psignal(SIGHUP, "SIGHUP"); exit(0);
} 輸出形式如下: sys_siglist: SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
strsignal: SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
psignal: SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
最后的忠告:千萬(wàn)不要去寫一個(gè)復(fù)雜的信號(hào)處理程序,那是最出力不討好的事情,信號(hào)處理程序每多一行,你的程序莫名崩潰的可能性就增大一分,誰(shuí)能保證你在信號(hào)處理函數(shù)里調(diào)用的都是可重入函數(shù)呢?本文中的很多例程使用的printf()就不是可重入的……信號(hào)處理程序應(yīng)該盡量簡(jiǎn)單,對(duì)稍微復(fù)雜的任務(wù),應(yīng)該想辦法(如siglongjmp(),設(shè)置全局標(biāo)志等)交給主程序處理。 吼吼,花了一天時(shí)間,終于把它整理完了,亂七八糟,但會(huì)方便各位查閱吧!其中大部分內(nèi)容參考了《UNIX環(huán)境高級(jí)編程》,有不對(duì)的地方大家盡管拍磚,但不要罵俺抄襲,會(huì)臉紅的^_^。圖靈教育說(shuō)的好,站在巨人的肩膀上,Standing on shoulders of the giants, I think I can fly... 參考資料: [1] [美]W. Rechard Stevens/Stephen A. Rago UNIX環(huán)境高級(jí)編程 (第2版). 人民郵電出版社. 2006
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/wangxg_7520/archive/2008/09/08/2901309.aspx |
|
|