Linux signal events Learning note
reference document:
sigemptyset sigfillset sigprocmask 博文
1.信號的處理
信號的產生,使用kill -l
命令查看有哪些信號,具體使用man 7 signal
查看詳情頁。
信息的發生有兩個來源:硬件產生和軟件產生。硬件顧名思義就是例如鍵盤比如平常用的組合鍵(ctrl+c ctrl+z等);軟件則是使用系統函數或者命令發出信號。
系統爲我們提供的信號發生函數有很多,但是其中最常用的是kill、raise、alarm和 setitimert(定時器吧)。
提供前三個函數的聲明:
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int kill(pid_t pid,int sig);
int raise(int sig);
unisigned int alarm(unsigned int seconds);
kill函數中sig 爲向進程發送的觸發信號(pmst:kill的原因有很多,當然要區分。個人理解。),pid取值有三種情況:
- 正數。那麼sig自然會發送給pid號進程嘍
- 0。那麼信號信號sig被髮送到所有和pid進程同一個進程組的進程(pmst:一個應用不知一個進程吧)
- -1。信號發給所有進程表中的進程,除了最大的那個進程號。
TODO:以上理解有偏差,還需更正。
raise系統調用向自己發送一個sig信號,我們可以用上面那個函數來實現這個功能。
alarm函數和時間有點關係,允許在seconds秒後發送一個SIGALRM(14)信號。當然也可以使用setitimert+signal來實現。
測試了下alarm()
函數
#include <unistd.h>
#include <stdio.h>
main()
{
unsigned int i;
alarm(1);
for(i=0;1;i++)
printf("I=%d",i);
}
執行以上函數輸出不定(pmst:虛擬機CentOS 512M,輸出8127)。
本來alarm是在設定時間後自行相應的操作,但是缺省會默認執行結束進程操作。
2.信號的操作
信號操作 有時候我們希望進程正確的執行,而不想進程受到信號的影響,比如我
們希望上面那個程序在1秒鐘之後不結束.這個時候我們就要進行信號的操作了.
信號操作最常用的方法是信號屏蔽.信號屏蔽要用到下面的幾個函數.
以下是幾個信號操作的函數聲明:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(sigset_t *set,int signo);
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
- sigemptyset函數初始化信號集合set,將set設置爲空(pmst:我認爲就是kill -l輸出的那些就是信號集合了,這裏選擇都清除光)
- sigfillset也初始化信號集合,只是將信號集合設置爲所有信號的集合(pmst:這裏則是設置所有信號的集合)
- sigaddset將信號signo加入到信號集合之中(signo 自然就是信號number)
- sigdelset將信號從信號集合中刪除,同上
- sigismember查詢信號是否在信號集合之中.其實就是sig is member of process的縮寫吧
- sigprocmask是最爲關鍵的一個函數。使用執行sigprocmask函數前,要先設置好信號集合set.這個函數的作用是將指定的信號集合set加入到進程的信號阻塞集合之中去,如果提供了oset那麼當前的進程信號阻塞集合將會保存在oset裏面.參數how決定函數的操作方式.
- SIG_BLOCK:增加一個信號集合到當前進程的阻塞集合之中.
- SIG_UNBLOCK:從當前的阻塞集合之中刪除一個信號集合.
- SIG_SETMASK:將當前的信號集合設置爲信號阻塞集合.
注意: 信號的阻塞就是讓系統暫時保留信號留待以後發送。(注意:不是不發送,而是延遲發送)一般情況下信號的阻塞只是暫時的,只是爲了防止信號打斷敏感的操作。
可參考:信號的阻塞
測試用例:
#include <signal.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
double y;
sigset_t intmask;
int i,repeat_factor;
if(argc!=2)
{
fprintf(stderr,"Usage:%s repeat_factor/n/a",argv[0]);
exit(1);
}
if((repeat_factor=atoi(argv[1]))<1)repeat_factor=10;
sigemptyset(&intmask); /* 將信號集合設置爲空 */
sigaddset(&intmask,SIGINT); /* 加入中斷 Ctrl+C 信號*/
while(1)
{
/*阻塞信號,我們不希望保存原來的集合所以參數爲NULL*/
sigprocmask(SIG_BLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal blocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Blocked calculation is finished/n");
/* 取消阻塞 */
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal unblocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Unblocked calculation is finished/n");
}
exit(0);
}
我們先使用一個自定義的signal集合,然後將SIGINT加入到該信號集合中,即只要ctrl+c就會中斷程序。在while(1)裏我們使用sigprocmask
函數使用SIG_BLOCK指定當SIGINT信號觸發時放到信號阻塞集合中,馬上執行for(i=0;i<repeat_factor;i++)y=sin((double)i);
這條語句,設定repeat_factor
值大一些,所以在這一步中操作時間比較長,此時狀態時阻塞的,因此按下ctrl+c就把這個信號放入到阻塞集合中,並非立馬觸發,只有當unblock後會立馬觸發;當然倘若直接SIG_UNBLOCK
屏蔽信號取消了,這個信號就會發生作用。
此時我們希望對信號做出及時的反應,比如當用戶按下Ctrl+C時,我們希望做一些事情,例如告知用戶你的這個操作不好嗎,請不要重試,而不是什麼都告知。此時我們需要用到sigaction
函數。
先了解下函數的聲明:
#include <signal.h>
/* 函數 */
int sigaction(int signo,const struct sigaction *act,
struct sigaction *oact);
/* 結構體 */
struct sigaction {
void (*sa_handler)(int signo);// 函數指針 用於信號處理
void (*sa_sigaction)(int siginfo_t *info,void *act);//函數指針
sigset_t sa_mask; //信號集合
int sa_flags; //標誌位
void (*sa_restore)(void);//函數指針 用於重置
}
函數的三個傳入參數解釋:
- signo很簡單就是我們要處理的信號了,可以是任何的合法的信號.有兩個信號不能夠使用(SIGKILL和SIGSTOP).
- act包含我們要對這個信號進行如何處理的信息.具體看下面結構體。
- oact更簡單了就是以前對這個函數的處理信息了,主要用來保存信息的,一般用NULL就OK了
結構體解釋:
sa_handler
是一個函數型指針,這個指針指向一個函數,這個函數有一個參數.這個函數就
是我們要進行的信號操作的函數.sa_sigaction
,sa_restore
和sa_handler
差不多的,只是參數不同罷了.這兩個元素我們很少使用,就不管了.sa_flags
用來設置信號操作的各個情況.一般設置爲0好了.sa_mask
屏蔽信號集合??? 就是加入這個集合的信號都會被屏蔽掉。
在使用的時候我們用
sa_handler
指向我們的一個信號操作函數,就可以了.sa_handler
有兩個特殊的值:SIG_DEL
和SIG_IGN.SIG_DEL
是使用缺省的信號操作函數,而SIG_IGN
是使用忽略該信號的操作函數.
sigaction 測試用例:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define PROMPT "你想終止程序嗎?"
char *prompt=PROMPT;
void ctrl_c_op(int signo)
{
write(STDERR_FILENO,prompt,strlen(prompt));
}
int main()
{
struct sigaction act;
act.sa_handler=ctrl_c_op;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGINT,&act,NULL)<0)
{
fprintf(stderr,"Install Signal Action Error:%s/n/a",strerror(errno));
exit(1);
}
while(1);
}
非常簡單,就是當SIGINT觸發時調用 sigaction 中的handler方法處理。但是如果我們在處理信號的時候又有信號到來,這就有點棘手了。因此我們要使用sigaction 中的sa_mask屏蔽信號集合了!當我們進入handler處理事務時,不想被其他消息干擾,那麼就把那些消息加入到sa_mask屏蔽集合中,處理完後 在unblock下。以上是pmst的個人見解。
其他信號函數
先看下函數聲明:
pause 和 sigsuspend
#include <unistd.h>
#include <signal.h>
int pause(void);
int sigsuspend(const sigset_t *sigmask);
- pause函數很簡單,就是掛起進程直到一個信號發生了.
- sigsuspend也是掛起進程只是在調用的時候用sigmask取代當前的信號阻塞集合.
sigsetjmp 和siglongjmp
#include <sigsetjmp>
int sigsetjmp(sigjmp_buf env,int val);
void siglongjmp(sigjmp_buf env,int val);
這兩個信號跳轉函數也可以實現程序的跳轉讓我們可以從函數之中跳轉到我們需要的地方.
一個實例
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/ */
#define MAIL_DIR "/var/spool/mail/"
/* 睡眠10秒鐘 */
#define SLEEP_TIME 10
#define MAX_FILENAME 255
unsigned char notifyflag=1;
long get_file_size(const char *filename)
{
struct stat buf;
if(stat(filename,&;buf)==-1)
{
if(errno==ENOENT)return 0;
else return -1;
}
return (long)buf.st_size;
}
void send_mail_notify(void)
{
fprintf(stderr,"New mail has arrived/007/n");
}
void turn_on_notify(int signo)
{
notifyflag=1;
}
void turn_off_notify(int signo)
{
notifyflag=0;
}
int check_mail(const char *filename)
{
long old_mail_size,new_mail_size;
sigset_t blockset,emptyset;
sigemptyset(&;blockset);
sigemptyset(&;emptyset);
sigaddset(&;blockset,SIGUSR1);
sigaddset(&;blockset,SIGUSR2);
old_mail_size=get_file_size(filename);
if(old_mail_size<0)return 1;
if(old_mail_size>0) send_mail_notify();
sleep(SLEEP_TIME);
while(1)
{
if(sigprocmask(SIG_BLOCK,&;blockset,NULL)<0) return 1;
while(notifyflag==0)sigsuspend(&;emptyset);
if(sigprocmask(SIG_SETMASK,&;emptyset,NULL)<0) return 1;
new_mail_size=get_file_size(filename);
if(new_mail_size>old_mail_size)send_mail_notify;
old_mail_size=new_mail_size;
sleep(SLEEP_TIME);
}
}
int main(void)
{
char mailfile[MAX_FILENAME];
struct sigaction newact;
struct passwd *pw;
if((pw=getpwuid(getuid()))==NULL)
{
fprintf(stderr,"Get Login Name Error:%s/n/a",strerror(errno));
exit(1);
}
strcpy(mailfile,MAIL_DIR);
strcat(mailfile,pw->pw_name);
newact.sa_handler=turn_on_notify;
newact.sa_flags=0;
sigemptyset(&;newact.sa_mask);
sigaddset(&;newact.sa_mask,SIGUSR1);
sigaddset(&;newact.sa_mask,SIGUSR2);
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn On Error:%s/n/a",strerror(errno));
newact.sa_handler=turn_off_notify;
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn Off Error:%s/n/a",strerror(errno));
check_mail(mailfile);
exit(0);
}
關於 sigempty()
簡述
sigempty() 是衆多函數家庭衆多的一員,可用於手工設置信號集合。Signal Set是一些數據對象,允許讓一個線程持續管理一個信號組。例如,一個線程或許創建一個信號集合用於記錄那些被block的信號,也可以記錄那些掛起的信號。
我們可以通過sigprocmask()來管理這些信號集合,傳入SIG_BLCOK
、SIG_UNBLOCK
和SIG_SETMASK
;或者examine signal sets returned by other functions(such as sigpending())
正如sigempty名字所說,它初始化一個特定的集合爲空集合。換句話說,所有支持的信號都被排除在外了。
參數
IN:
*set
(Input) A pointer to a signal set.
Return Value
0 sigemptyset() wassuccessful.
Related Information
The
demo
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main( int argc, char *argv[] ) {
struct sigaction sigact;
sigset_t sigset;
sigemptyset( &sigact.sa_mask );
sigact.sa_flags = 0;
sigact.sa_handler = SIG_IGN;
sigaction( SIGUSR2, &sigact, NULL );
/*
* Unblocking all signals ensures that the signal
* handling action will be taken when the signal
* is generated.
*/
sigemptyset( &sigset );
sigprocmask( SIG_SETMASK, &sigset, NULL );
printf( "before kill()\n" );
kill( getpid(), SIGUSR2 );
printf( "after kill()\n" );
return( 0 );
}