信號在內核中的表示
實際執行信號的處理動作稱為信號遞達(Delivery),信號從產生到遞達之間的狀態,稱為信號未決(Pending)。進程可以選擇阻塞(Block)某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。信號在內核中的表示可以看作是這樣的:
1)block集(阻塞集、屏蔽集):一個進程所要屏蔽的信號,在對應要屏蔽的信號位置1
2)pending集(未決信號集):如果某個信號在進程的阻塞集中,則也在未決集中對應位置1,表示該信號不能被遞達,不會被處理3)handler(信號處理函數集):表示每個信號所對應的信號處理函數,當信號不在未決集中時,將被調用。
4)block狀態字、pending狀態字均64位(bit);
5)block狀態字用戶可以讀寫,pending狀態字用戶只能讀;這是信號設計機制。
那麼我們該如何對信號的屏蔽字狀態進行改變和讀取呢?接下來我們介紹一組信號集操作函數:
#includeint sigemptyset(sigset_t *set); //把信號集清零;(64bit/8=8字節) int sigfillset(sigset_t *set); //把信號集64bit全部置為1 int sigaddset(sigset_t *set, int signo); //根據signo,把信號集中的對應位置成1 int sigdelset(sigset_t *set, int signo); //根據signo,把信號集中的對應位置成0 int sigismember(const sigset_t *set, int signo); //判斷signo是否在信號集中
sigprocmask 功能:讀取或者更改進程的信號屏蔽字(Block)
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功則為0,若出錯則為-1
讀取:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。
更改:如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裡,然後根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值。
sigpending獲取信號未決狀態字(pending)信息,保存至set態,NSIG信號的最大值=64。
#includeint sigpending(sigset_t *set);
sigismember函數
用來測試參數signum 代表的信號是否已加入至參數set信號集裡。如果信號集裡已有該信號則返回1,否則返回0。如果有錯誤則返回-1。出錯的情況及其錯誤代碼見下:
EFAULT 參數set指針地址無法存取
EINVAL 參數signum 非合法的信號編號
int sigismember(const sigset_t *set,int signum);我們注冊一個SIGINT信號,打印出pending的狀態,結果如下:
void handler(int sig) { printf("recv a sig=%d\n",sig); } void printsigset(sigset_t *set) { int i; for(i=1;i信號沒有阻塞,不會發生未決狀態,直接遞達。
在接下來的例子中,我們先屏蔽SIGINT信號, 但是如果該進程接收到了SIGQUIT信號, 則將對SIGINT信號的屏蔽節解除,當然,我們需要先注冊SIGINT和SIGQUIT信號。
/*開始阻塞信號的程序,產生未決狀態*/ void handler(int sig) { if(sig==SIGINT) printf("recv a sig=%d\n",sig); else if(sig==SIGQUIT) //解除SIGINT的屏蔽 { sigset_t uset; sigemptyset(&uset); sigaddset(&uset,SIGINT); sigprocmask(SIG_UNBLOCK,&uset,NULL); } // printf("recv a sig=%d\n",sig); } void printsigset(sigset_t *set) { int i; for(i=1;i當我們按下ctrl+c產生信號時,信號被阻塞,處於未決狀態。接收到SIGQUIT信號時,解除阻塞,進入遞達狀態,但是只會對信號做出一次反應,即使你按了很多次ctrl+c,原因就在於,SIGINT是不可靠信號,不支持排隊,只保留了一個。 如果我們采用實時信號的話,例如SIGRTMIN,那麼對信號來說是支持排隊的,不會發生丟失的情況,在解除阻塞後,會對每個信號做出處理。
Sigaction
前面我們講過了使用signal安裝不可靠信號,雖然signal不如sigaction功能豐富,但是也可以安裝可靠信號;
#includeint sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 功能:
sigaction函數用於改變進程接收到特定信號後的行為。
簡而言之參數就是(信號,指針,原行為)
關於sigaction結構體
第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等
struct sigaction { //信號處理程序 不接受額外數據(比較過時) void (*sa_handler)(int); //信號處理程序能接受額外數據,和sigqueue配合使用(支持信號排隊,信號傳送其他信息),推薦使用 void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; //屏蔽 int sa_flags; //表示信號的行為:SA_SIGINFO表示能接受數據 void (*sa_restorer)(void); //廢棄不用了 };sa_handler的原型是一個參數為int,返回類型為void的函數指針。參數即為信號值,所以信號不能傳遞除信號值之外的任何信息;
sa_sigaction的原型是一個帶三個參數,類型分別為int,struct siginfo *,void *,返回類型為void的函數指針。第一個參數為信號值;第二個參數是一個指向struct siginfo結構的指針,此結構中包含信號攜帶的數據值;第三個參數沒有使用。
sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。默認當前信號本身被阻塞。
sa_flags包含了許多標志位,比較重要的一個是SA_SIGINFO,當設定了該標志位時,表示信號附帶的參數可以傳遞到信號處理函數中。即使sa_sigaction指定信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤。
sa_restorer已過時,POSIX不支持它,不應再使用。
注意:回調函數sa_handler和sa_sigaction只能選一個
因此,當你的信號需要接收附加信息的時候,你必須給sa_sigaction賦信號處理函數指針,同時還要給sa_flags賦SA_SIGINFO,
實例1:利用sigaction實現了signal函數的功能
__sighandler_t my_signal(int sig,__sighandler_t handler) { struct sigaction act; struct sigaction oldact; act.sa_handler=handler; sigemptyset(&act.sa_mask); act.sa_flags=0; if(sigaction(sig,&act,&oldact)<0) return SIG_ERR; return oldact.sa_handler; } void handler(int sig) { printf("recv a sig=%d\n",sig); } int main() { /* struct sigaction act; act.sa_handler=handler; sigemptyset(&act.sa_mask); act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); */ my_signal(SIGINT,handler); while(1) pause(); return 0; }sa_mask選項
在執行handler 的時候, 如果此時進程收到了sa_mask所包含的信號, 則這些信號將不會被響應, 直到handler函數執行完畢。
sigprocmask使其即使發生了也不能遞達,但是sa_mask 僅是在處理handler是屏蔽外來的信號;兩者的區別還是要好好搞一搞的。
void handler(int sig) { printf("recv a sig=%d\n",sig); sleep(5); } int main() { struct sigaction act; act.sa_handler=handler; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask,SIGQUIT);//屏蔽SIGQUIT信號 act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); while(1) pause(); return 0; }
在響應SIGINT信號即handler處理時,SIGQUIT暫時被屏蔽,但是一旦handler函數處理完後,立即對SIGQUIT進行響應。siginfo_t結構:
siginfo_t{ int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */ }
sigqueue
#includeint sigqueue(pid_t pid, int sig, const union sigval value);
功能
sigqueue是新的發送信號系統調用,主要是針對實時信號提出的支持信號帶有參數,與函數sigaction()配合使用。
和kill函數相比多了一個參數:const union sigval value(int kill(pid_t pid, int sig)),因此sigqueue()可以比kill()傳遞更多的信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程組。
參數
參數1是指定接收信號的進程id,參數2確定即將發送的信號;
參數3是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。
注意:要想在進程間通信的話,sa_flags要置為SA_SIGINFOsigval聯合體
typedef union sigval{ int sival_int; void *sival_ptr; } sigval_t;接下來我們模擬一下進程間通信的實例:先運行hello開啟接收,然後使用send發送信號;通過這種方式可以達到進程見通信的目的。
Hello.c void handler(int sig,siginfo_t *info,void *ctx) { printf("recv a sig=%d data=%d\n",sig,info->si_value.sival_int); } int main() { struct sigaction act; act.sa_sigaction=handler; sigemptyset(&act.sa_mask); act.sa_flags=SA_SIGINFO; if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error\n"); while(1) pause(); return 0; } Send int main(int argc,char *argv[]) { if(argc!=2) { fprintf(stderr,"Usage %s pid\n",argv[0]); exit(EXIT_FAILURE); } pid_t pid=atoi(argv[1]); union sigval v; v.sival_int=100; sigqueue(pid,SIGINT,v); return 0; }