信號在內核中的表示
實際執行信號的處理動作稱為信號遞達(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功能豐富,但是也可以安裝可靠信號;
#include
int 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
#include
int 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_SIGINFO
sigval聯合體
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;
}