?? linux環境進程間通信(二):信號(下).htm
字號:
<BLOCKQUOTE>在信號(上)中,討論了linux信號種類、來源、如何安裝一個信號以及對信號集的操作。本部分則首先討論從信號的生命周期上認識信號,或者宏觀上看似簡單的信號機制(進程收到信號后,作相應的處理,看上去再簡單不過了),在微觀上究竟是如何實現的,也是在更深層次上理解信號。接下來還討論了信號編程的一些注意事項,最后給出了信號編程的一些實例。</BLOCKQUOTE>
<P><A name=1><SPAN
class=atitle2>一、信號生命周期</SPAN></A><BR>從信號發送到信號處理函數的執行完畢</P>
<P>對于一個完整的信號生命周期(從信號發送到相應的處理函數執行完畢)來說,可以分為三個重要的階段,這三個階段由四個重要事件來刻畫:信號誕生;信號在進程中注冊完畢;信號在進程中的注銷完畢;信號處理函數執行完畢。相鄰兩個事件的時間間隔構成信號生命周期的一個階段。</P>
<P><IMG height=34 alt="" src="" width=594></P>
<P>下面闡述四個事件的實際意義:</P>
<OL>
<LI>信號"誕生"。信號的誕生指的是觸發信號的事件發生(如檢測到硬件異常、定時器超時以及調用信號發送函數kill()或sigqueue()等)。
<LI>信號在目標進程中"注冊";進程的task_struct結構中有關于本進程中未決信號的數據成員: <PRE><CODE>struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
</PRE></CODE><FONT
face="宋體, MS Song">第三個成員是進程中所有未決信號集,第一、第二個成員分別指向一個sigqueue類型的結構鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個sigqueue結構刻畫一個特定信號所攜帶的信息,并指向下一個sigqueue結構:
</FONT><PRE><CODE>struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
</PRE></CODE><FONT
face="宋體, MS Song">信號在進程中注冊指的就是信號值加入到進程的未決信號集中(sigpending結構的第二個成員sigset_t
signal),并且信號所攜帶的信息被保留到未決信號信息鏈的某個sigqueue結構中。只要信號在進程的未決信號集中,表明進程已經知道這些信號的存在,但還沒來得及處理,或者該信號被進程阻塞。
</FONT>
<P><B>注:</B><BR>當一個實時信號發送給一個進程時,不管該信號是否已經在進程中注冊,都會被再注冊一次,因此,信號不會丟失,因此,實時信號又叫做"可靠信號"。這意味著同一個實時信號可以在同一個進程的未決信號信息鏈中占有多個sigqueue結構(進程每收到一個實時信號,都會為它分配一個結構來登記該信號信息,并把該結構添加在未決信號鏈尾,即所有誕生的實時信號都會在目標進程中注冊);<BR>當一個非實時信號發送給一個進程時,如果該信號已經在進程中注冊,則該信號將被丟棄,造成信號丟失。因此,非實時信號又叫做"不可靠信號"。這意味著同一個非實時信號在進程的未決信號信息鏈中,至多占有一個sigqueue結構(一個非實時信號誕生后,(1)、如果發現相同的信號已經在目標結構中注冊,則不再注冊,對于進程來說,相當于不知道本次信號發生,信號丟失;(2)、如果進程的未決信號中沒有相同信號,則在進程中注冊自己)。</P>
<LI>信號在進程中的注銷。在目標進程執行過程中,會檢測是否有信號等待處理(每次從系統空間返回到用戶空間時都做這樣的檢查)。如果存在未決信號等待處理且該信號沒有被進程阻塞,則在運行相應的信號處理函數前,進程會把信號在未決信號鏈中占有的結構卸掉。是否將信號從進程未決信號集中刪除對于實時與非實時信號是不同的。對于非實時信號來說,由于在未決信號信息鏈中最多只占用一個sigqueue結構,因此該結構被釋放后,應該把信號在進程未決信號集中刪除(信號注銷完畢);而對于實時信號來說,可能在未決信號信息鏈中占用多個sigqueue結構,因此應該針對占用sigqueue結構的數目區別對待:如果只占用一個sigqueue結構(進程只收到該信號一次),則應該把信號在進程的未決信號集中刪除(信號注銷完畢)。否則,不應該在進程的未決信號集中刪除該信號(信號注銷完畢)。<BR>進程在執行信號相應處理函數之前,首先要把信號在進程中注銷。
<LI>信號生命終止。進程注銷信號后,立即執行相應的信號處理函數,執行完畢后,信號的本次發送對進程的影響徹底結束。
<P><B>注:</B><BR>1)信號注冊與否,與發送信號的函數(如kill()或sigqueue()等)以及信號安裝函數(signal()及sigaction())無關,只與信號值有關(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進程接收到就被注冊)。<BR>2)在信號被注銷到相應的信號處理函數執行完畢這段時間內,如果進程又收到同一信號多次,則對實時信號來說,每一次都會在進程中注冊;而對于非實時信號來說,無論收到多少次信號,都會視為只收到一個信號,只在進程中注冊一次。</P></LI></OL>
<P><A name=2><SPAN class=atitle2>二、信號編程注意事項</SPAN></A></P>
<OL>
<LI>防止不該丟失的信號丟失。如果對八中所提到的信號生命周期理解深刻的話,很容易知道信號會不會丟失,以及在哪里丟失。
<LI><B>程序的可移植性</B><BR>考慮到程序的可移植性,應該盡量采用POSIX信號函數,POSIX信號函數主要分為兩類:
<UL>
<LI>POSIX 1003.1信號函數:
Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
<LI>POSIX 1003.1b信號函數。POSIX 1003.1b在信號的實時性方面對POSIX
1003.1做了擴展,包括以下三個函數:
sigqueue()、sigtimedwait()、sigwaitinfo()。其中,sigqueue主要針對信號發送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函數,后面有相應實例。
<PRE><CODE>#include <signal.h>
int sigwaitinfo(sigset_t *set, siginfo_t *info).
</PRE></CODE><FONT
face="宋體, MS Song">該函數與sigsuspend()類似,阻塞一個進程直到特定信號發生,但信號到來時不執行信號處理函數,而是返回信號值。因此為了避免執行相應的信號處理函數,必須在調用該函數前,使進程屏蔽掉set指向的信號,因此調用該函數的典型代碼是:
</FONT><PRE><CODE>sigset_t newmask;
int rcvd_sig;
siginfo_t info;
sigemptyset(&newmask);
sigaddset(&newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &newmask, NULL);
rcvd_sig = sigwaitinfo(&newmask, &info)
if (rcvd_sig == -1) {
..
}
</PRE></CODE><FONT
face="宋體, MS Song">調用成功返回信號值,否則返回-1。sigtimedwait()功能相似,只不過增加了一個進程等待的時間。
</FONT></LI></UL>
<LI><B>程序的穩定性。</B><BR>為了增強程序的穩定性,在信號處理函數中應使用可重入函數。
<P>信號處理程序中應當使用可再入(可重入)函數(注:所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯)。因為進程在收到信號后,就將跳轉到信號處理函數去接著執行。如果信號處理函數中使用了不可重入函數,那么信號處理函數可能會修改原來進程中不應該被修改的數據,這樣進程從信號處理函數中返回接著執行時,可能會出現不可預料的后果。不可再入函數在信號處理函數中被視為不安全函數。</P>
<P>滿足下列條件的函數多數是不可再入的:(1)使用靜態的數據結構,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函數實現時,調用了malloc()或者free()函數;(3)實現時使用了標準I/O函數的。The
Open Group視下列函數為可再入的:
<P><CODE><FONT
face=新宋體>_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()。
</FONT></CODE>
<P>即使信號處理函數使用的都是"安全函數",同樣要注意進入處理函數時,首先要保存errno的值,結束時,再恢復原值。因為,信號處理過程中,errno值隨時可能被改變。另外,longjmp()以及siglongjmp()沒有被列為可再入函數,因為不能保證緊接著兩個函數的其它調用是安全的。</P></LI></OL>
<P><A name=3><SPAN class=atitle2>三、深入淺出:信號應用實例</SPAN></A></P>
<P>linux下的信號應用并沒有想象的那么恐怖,程序員所要做的最多只有三件事情:</P>
<OL>
<LI>安裝信號(推薦使用sigaction());
<LI>實現三參數信號處理函數,handler(int signal,struct siginfo *info, void *);
<LI>發送信號,推薦使用sigqueue()。 </LI></OL>
<P>實際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是與信號集相關的幾種操作。</P>
<P><B>實例一:信號發送及處理</B><BR>實現一個信號接收程序sigreceive(其中信號安裝由sigaction())。</P><PRE><CODE>#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
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)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
</PRE></CODE>
<P>說明,命令行參數為信號值,后臺運行sigreceive signo
&,可獲得該進程的ID,假設為pid,然后再另一終端上運行kill -s signo
pid驗證信號的發送接收及處理。同時,可驗證信號的排隊問題。<BR><B>注:</B>可以用sigqueue實現一個命令行信號發送程序sigqueuesend,見<A
href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part2/index2.shtml#5">附錄1</A>。
</P>
<P><B>實例二:信號傳遞附加信息</B><BR>主要包括兩個實例:</P>
<OL>
<LI>向進程本身發送信號,并傳遞指針參數; <PRE><CODE>#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i < 5;i++)
data[i]='2';
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;//三參數信號處理函數
act.sa_flags=SA_SIGINFO;//信息傳遞開關
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval);//向本進程發送信號,并傳遞附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三參數信號處理函數的實現
{
int i;
for(i=0;i<10;i++)
{
printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
}
printf("handle signal %d over;",signum);
}
</PRE></CODE>
<P>這個例子中,信號實現了附加信息的傳遞,信號究竟如何對這些信息進行處理則取決于具體的應用。</P>
<LI>2、 不同進程間傳遞整型參數:把1中的信號發送和接收放在兩個程序中,并且在發送過程中傳遞整型參數。<BR>信號接收程序: <PRE><CODE>#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;
pid=getpid();
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&act,NULL)<0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d \n",info->si_int);
}
</PRE></CODE>
<P>信號發送程序:命令行第二個參數為信號值,第三個參數為接收進程ID。</P><PRE><CODE>#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -