Linux系统编程学习笔记(九)信号管理2
信号2:
1、信号集合:
信号集合以及其操作我们经常遇到,比如设置一些进程要阻塞的信号,进程的信号pending集合。
以下是常用的信号操作:
#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(const sigset *set, int signo);
sigemptyset用于初始化一个信号集合为空集合。sigfillset初始化信号集合为包含所有信号的集合。
返回0。
sigaddset将signo信号添加到信号集合中,sigdelset将signo从信号集合中删除。成功返回0,失败返回
-1,并将errno设置为EINVAL,表示signo不是一个合法的信号标识符。
sigismember返回1如果在信号集中,0如果不在,-1错误,并设置errno为EINVAL。
Linux提供了另外的一个非标准POSIX的信号集合操作函数:
#include <signal.h> int sigisemptyset(sigset_t *set); int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right); int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);
sigisemptyset返回1如果信号集是空,否则返回0
sigorsetleft和right两个信号集的并集。
sigandsetleft和right两个信号集的交集。
2、阻塞信号:
如果我们的程序需要在信号处理程序和程序其他部分共享数据,如果我们的程序中的某些部分在运行期间不希望被
信号中断(包括来自信号处理程序的中断),我们把不希望中断的部分成为临界区,我们可以通过临时挂起信号来
保护它。
我们称这些信号被阻塞,任何被挂起的信号都得不到处理,直到被解除阻塞,进程可以阻塞任意多个信号。
信号掩码就是要阻塞的信号集。一个信号集可以查看它的掩码,修改它的掩码,或者同时做这两个操作,
segprocmask提供这些操作:
1)设置信号掩码
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask的行为取决于how的值,它是一下标示之一:
SIG_SETMASK调用进程的信号掩码变成set。
SIG_BLOCKset中的信号被加入到调用信号的掩码中。也就是说,信号掩码编程了当前信号掩码和set的并集。
SIG_UNBLOCKset中的信号集合被从调用进程的信号掩码中移除。
如果oldset是非空,该函数将oldset设置为先前的信号集。
如果set是空的,该函数忽略how,并且不改变信号掩码,但仍然设置信号掩码。给set一个空值是检测当前
信号的一个方法。
调用成功,返回0,失败返回-1,把errno设置为EINVAL,表明how是无效的,或者设置为EFAULT,表示set或者
oldset为无效指针。
阻塞SIGKILL或者SIGSTOP是不允许的。
例子(查看当前进程的signalmask):
#include <signal.h> #include <unistd.h> #include <stdio.h> void pr_mask(const char *str){ sigset_t sigset; int errno_save; errno_save = errno; if(sigprocmask(0,NULL,&sigset) < 0){ perror("sigprocmask"); return 1; } printf("%s",str); if(sigismember(&sigset,SIGINT)) printf("SIGINT"); if(sigismember(&sigset,SIGQUIT)) printf("SIGQUIT"); if(sigismember(&sigset,SIGUSR1)) printf("SIGUSR1"); if(sigismember(&sigset,SIGALARM)) printf("SIGALARM"); printf("\n"); errno = errno_save; }
2)获取待处理信号:
当内核产生一个被阻塞的信号时,该信号未被发送。我们把它叫做待处理信号。当一个待处理信号解除
阻塞时,内核会把它发送给进程处理:
待处理的信号是介于发送和处理之间的状态。
POSIX定义了获取待处理信号的函数:
#include <signal.h> int sigpending(sigset_t *set);
成功将set设置为待处理的信号集返回,并返回0,失败返回-1。
下面例子展示了一些信号的使用方法:
#include <signal.h> #include <unistd.h> #include <stdio.h> stati void sig_quit(int); int main(void){ sigset_t newmask,oldmask,pendmask; if(signal(SIGQUIT,sig_quit) == SIG_ERR){ perror("signal"); return 1; } sigemptyset(&newmask); sigaddset(&newmask,SIGQUIT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0){ perror("sigprocmask"); return 1; } sleep(5); if(sigpending(&pendmask) < 0){ perror("sigprocmask"); return 1; } if(sigismember(&pendmask,SIGQUIT)){ printf("\nSIGQUIT pending\n"); return 1; } if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0){ perror("sigprocmask"); return 1; } printf("SIGQUIT unblocked\n"); sleep(5); exit(0); } static void sig_quit(int signo){ printf("caught SIGQUIT\n"); if( signal(SIGQUIT, SIG_DEL) == SIG_ERR) perror("SIGQUIT"); }
这个进程阻塞了SIGQUIT,保存了当前的信号掩码,然后休眠5秒钟,任何在这期间发生的quit信号都会被阻塞,
直到信号被解除阻塞,然后休眠5秒,我们可以查看它是处于pending还是解除阻止状态。
注意:在程序中我们首先保存了就得掩码,然后设置SIGQUIT被阻塞,然后使用SIG_SETMASK来恢复以前的掩码。
可选择的我们可以把我们阻塞的SIGQUIT通过SIG_UNBLOCK来恢复,但是这样可能会有问题,因为之前SIGQUIT
可能已经被设置阻塞。
运行:
$./a.out
^\
SIGQUITpending
caughtSIGQUIT
SIGQUITunblocked
^\Quit(coredump)
$./a.out
^\^\^\^\^\
SIGQUITpending
caughtSIGQUIT
SIGQUITunblocked
^\Quit(coredump)
可见信号并不是排在信号队列中,而是合并为一次。
3)等待信号集:
我们已经知道了如何改变信号掩码来阻塞和解除阻塞指定的信号。我们可以使用这个技术来保护临界区,来防止
被信号终端。如果我们想解除阻塞一个信号,然后pause,等待刚才阻塞的信号发生,该怎么做?假设信号时SIGINT,
我们可能写下如下代码:
sigset_t newmask, oldmask; sigemptyset(&newmask); sigaddset(&newmask,SIGINT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0){ perror("sigprocmask"); return 1; } /* critical region of code*/ /* reset signal mask, whick is unblocks SIGINT */ if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0){ perror("sigprocmask"); return 1; } /* wait for signal to occur */ pause();
这段代码时存在问题的:
如果信号发生在sigpromask之后,pause之前,则会丢失,pause会阻塞。解决的方法是还原信号和pause作为一个原子操作。
sigsuspend提供了这个功能。它可以临时改变信号掩码,该函数始终处于等待状态,直到产生一个终止进程或者被该进程处理的信号。
#include <signal.h> int sigsuspend(const sigset_t *set);
如果一个信号终止了进程,sigsuspend不返回,如果一个信号被发送并且处理了,sigsuspend在信号处理函数返回后
,返回-1,并将errno改为EINTR,如果set是一个无效指针,errno被设置为EFAULT。
一般在获得已经到达的信号和在程序运行期间阻塞在临界区信号时使用。进程首先sigprocmask来阻塞一个信号集,
将旧的信号掩码保存在oldset中。退出临界区后,进程会调用sigsuspend,将oldset赋值给set。
下面代码展示了使用sigsuspend来保护临界区:
#include <unistd.h> #include <signal.h> #include <stdio.h> static void sig_int(int); int main(void){ sigset_t newmask,oldmask,waitmask; pr_mask("program start:");//前面我们定义的打印信号掩码的函数 if(signal(SIGINT, sig_int) == SIG_ERR){ perror("signal"); return 1; } sigemptyset(&waitmask); sigaddset(&waitmask,SIGUSR1); sigemtpyset(&newmask); sigaddset(&newmask,SIGINT); /* block SIGINT and save current signal mask */ if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0){ perror("sigprocmask"); return 1; } pr_mask("in critical region: "); /* pause, allowing all signals except SIGUSR1. */ if(sigsuspend(&waitmask) != -1){ perror("sigsuspend"); return 1; } pr_mask("after return from sigsuspend:"); /* reset signal mask with unblock SIGINT */ if(sigmaskproc(SIG_SETMASK,&oldmask,NULL) < 0){ perror("sigmaskproc"); return 1; } pr_mask("program exit:"); exit(0); }
$./a.out
programstart:
incriticalregion:SIGINT
^?
insig_int:SIGINTSIGUSR1
afterreturnfromsigsuspend:SIGINT
programexit:
当我们调用sigsuspend的时候我们添加了SIGUSR1掩码,当sigsuspend返回时,它恢复了以前的掩码。
3、高级信号管理:
signal是一个很基本管理信号的函数,因为他是标准C的库,所以它只能对操作系统做最小的假设,它
只提供了最低限度的信号管理的标准。POSIX定义了sigaction系统调用,它提供了更强大的信号管理能力。
除此之外,当信号处理程序运行时,你可以用它来阻塞特定的信号的接收,获取信号发送时各种操作系统和
进程状态的信息:
#include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
调用sigaction会改变由signo表示信号的action,signo可以是出了SIGKILL和SIGSTOP外的任何信号。如果act
非空,该系统调用将该信号当前的行为替换成由act指定的行为。如果oldact是非空,该调用会先存储以前
给定的信号action。
sigaction接口允许精细的控制信号的结构。
struct sigaction{ void (*sa_handler)(int); /*addr of signal handler */ void (*sa_sigaction)(int siginfo_t *, void *);/* alternate handler */ sigset_t sa_mask; /*signals to block */ int sa_flags; /* flags */ void (*sa_restorer)(void); /* obsolete and non-POSIX */ };
sa_hanlder指定了接收到信号时采取的操作。和signal一样,该域可以是SIG_DEF,SIG_IGN,或者自定义的
处理函数,原型和sigaction一样:
void my_handler(int signo);
如果sa_flags被设置成SA_SIGINFO,那么将由sa_sigaction而不是sa_handler指定信号处理函数。该信号原型
略有不同:
void my_handler(int signo, siginfo_t *si, void *ucontext);
signo信号编号,siginfo_t结构作为第二个参数,ucontext_t结构作为第三个参数。
有些系统sa_handler和sa_sigaction是一个联合,所以不能给这两个域同时赋值。
sa_mask提供了应该在执行信号处理程序时被阻塞的信号集。这使得程序为多个信号处理程序间重入提供了适当
的保护。当前信号是被阻塞的以防止信号嵌套发送,除非sa_flags设置了SA_NODEFER标志。
当处理函数返回时sa_mask会被重置。
SA_NOCLDSTOP
IfsignoisSIGCHLD,thisflaginstructsthesystemtonotprovidenotificationwhen
achildprocessstopsorresumes.
SA_NOCLDWAIT
IfsignoisSIGCHLD,thisflagenablesautomaticchildreaping:childrenarenotconverted
tozombiesontermination,andtheparentneednot(andcannot)callwait()
onthem.SeeChapter5foralivelydiscussionofchildren,zombies,andwait().
SA_NOMASK
Thisflagisanobsoletenon-POSIXequivalenttoSA_NODEFER(discussedearlierin
thissection).UseSA_NODEFERinsteadofthisflag,butbepreparedtoseethis
valueturnupinoldercode.
SA_ONESHOT
Thisflagisanobsoletenon-POSIXequivalenttoSA_RESETHAND(discussedlaterin
thislist).UseSA_RESETHANDinsteadofthisflag,butbepreparedtoseethisvalue
turnupinoldercode.
SA_ONSTACK
Thisflaginstructsthesystemtoinvokethegivensignalhandleronanalternative
signalstack,asprovidedbysigaltstack().Ifyoudonotprovideanalternative
stack,thedefaultisused—thatis,thesystembehavesasifyoudidnotprovide
thisflag.Alternativesignalstacksarerare,althoughtheyareusefulinsome
pthreadsapplicationswithsmallerthreadstacksthatmightbeoverrunbysome
signalhandlerusage.Wedonotfurtherdiscusssigaltstack()inthisbook.
SA_RESTART
ThisflagenablesBSD-stylerestartingofsystemcallsthatareinterruptedbysignals.
SA_RESETHAND
Thisflagenables“one-shot”mode.Thebehaviorofthegivensignalisresetto
thedefaultoncethesignalhandlerreturns.
siginfo_t结构:
#include <sys/signal.h> typedef struct siginfo_t{ int si_signo; /*signal number*/ int si_errno; /* if nozero, errno value from <errno.h> */ int si_code; /*signal code*/ pid_t si_pid; /* sending process's PID */ uid_t si_uid; /* sending process's real UID */ 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 payload value */ int sig_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ void *si_addr; /* memory location that cause fault */ int si_band; /*band event*/ int si_fd; /* file descriptor */ };
POSIX.1兼容的实现至少包含si_signo和si_sigcode。
si_code:
sig_code字段指示了信号的原因。对于用户发送的信号,该域说明了信号是如何被发送的,对于内核信号
该域说明了信号是为什么被发送的。
详细可参见APUEp327Figure10.7siginfo_tcodevalues.
我们可以使用sigaction来实现signal函数:
#include <signal.h> SigFunc * signal(int signo,SigFunc * func){ struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(signo == SIGALARM){ #ifdef SA_INTERRUPT act.sa_flag |= SA_INTERRUPT; #endif }else{ #ifdef SA_RESTART act.sa_flag |= SA_RESTART; #endif } if(sigaction(signo,&act,&oact) < 0) return SIG_ERR; return (oact.sa_handler); }
我们将所有的信号除了SIGALARM都设置了SA_RESTART的flag,以为我们不想让SIALARMrestart,这样可以为I/O
操作设置超时时间。
4、发送带附加信息的信号:
我们看到当信号注册设置了SA_SIGINFO的flag后,siginfo结构的si_value提供了一些附加的信息。
有POSIX定义的sigqueue函数,允许进程发送带附加信息的信号,这样我们可以从siginfo中得到这个值:
#include <signal.h> int sigqueue(pid_t pid, int signo, const union sigval value);
这个和kill的运行方式很像。成功时由signo表示的信号被加入到由pid指定的进程或者进程队列中,
并返回0。附加的信息由sigval指定,他是一个整数和void指针的联合:
union sigval{ int sival_int; void *sival_ptr; },
例子:
sigval value; int ret; value.sival_int = 404; ret = sigqueue(1722,SIGUSR2,value); if(ret) perror("sigqueue");
5、abort函数:
abort函数会导致进程非正常终止:
#include <stdlib.h> void abort(void)
这个函数向调用者发送SIGABRT信号。进程不应该忽略这个信号。
abort实现:
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void abort(void){ sigset_t mask; struct sigaction action; /* Caller can't ignore a SIGABRT, if so reset to default */ sigaction(SIGABRT,NULL,&action); if(&action.sa_handler == SIG_IGN){ action.sa_handler = SIG_DFL; sigaction(SIGABRT,&action,NULL); } if(action.sa_handler == SIG_DFL){ fflush(NULL);/* flush all open stdio stream */ } /* caller can't block SIGABRT; make sure it's unblocked */ sigfillset(&mask); sigdelset(&mask,SIGABRT); sigprocmask(SIG_SETMASK,&mask,NULL); kill(getpid(),SIGABRT); /* If we're here,process caught SIGABRT and returned */ /* 如果信号被捕获,返回之后需要将handler设置为SIG_DFL,保证调用abort会终止进程*/ fflush(NULL); /* flush all open stdio stream */ action.sa_handler = SIG_DFL; sigaction(SIGABRT,&action, NULL); /* reset to default */ sigprocmask(SIG_SETMASK,&mask,NULL);/* just in case ...*/ kill(getpid(),SIGABRT);/* and one more time ...*/ exit(1); }
参考:
1、《Linuxsystemprogramming》
2、《Unixsystemprogramming》
3、《AdvancedProgrammingintheUnixEnvironment》