1、alarm与pause
- 每个进程只能有一个alarm,如果之前有个alarm,在本次alarm执行完毕的时候如果还没有结束,则本次alarm返回上次alarm剩余的值。如果本次alarm执行的结果为0,则取消原来的闹钟。
- SIGALARM的默认执行动作是终止进程(异常终止),但是大多数闹钟会捕获该信号,如果要捕获该信号,应该在alarm执行前,注册信号处理函数signal或者sigaction!
使用alarm函数可以设置一个计时器,在将来某个指定的时间,该计时器会超时。当计时器超时时,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。
#include <unistd.h>
unsigned int alarm( unsigned int seconds );
返回值:0或以前设置的闹钟时间的余留秒数
其中,参数seconds的值是秒数,经过了指定的seconds秒后会产生信号SIGALRM。要了解的是,经过了指定的秒数后,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一些时间。
每个进程只能有一个闹钟时钟。如果在调用alarm时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数调用的返回值。以前登记的闹钟时钟则被新值代替。
如果有以前为进程登记的尚未超过的闹钟时钟,而且本次调用的seconds值是0,则取消以前的闹钟时钟,其余留值仍作为alarm函数的返回值。
pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void);
返回值:-1,并将errno设置为EINTR
只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,并将errno设置为EINTR。
1.1 例子分析
2、sigsetjmp、siglongjmp
- sigsetjmp与siglongjmp是为了解决,当捕捉信号进入信号处理函数时后,该信号被进入信号屏蔽集,但是如果跳转后,该信号不会自动从信号屏蔽集中清除的问题,而正常的处理函数返回时,会自动从屏蔽集里,清除该信号。 结合信号响应位图来分析,当处于信号处理函数时,mask该信号位为0,pending为0,如果在信号处理函数跳转,则不会回到kernel态,不会将mask置1,导致永远屏蔽该信号。信号响应机制
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回非0值
void siglongjmp(sigjmp_buf env, int val);
若siglongjmp(j,r)函数中r是大于0的整数,则跳转到sigsetjmp(j)会返回r;若siglongjmp(j,r)中r是0,即siglongjmp(j,0),那么跳转到sigsetjmp(j)后,sigsetjmp(j)返回时1
函数说明:sigsetjmp()会保存目前堆栈环境,然后将目前的地址作一个记号,
而在程序其他地方调用siglongjmp()时便会直接跳到这个记号位置,然后还原堆栈,继续程序的执行。
参数env为用来保存目前堆栈环境,一般声明为全局变量
参数savemask若为非0则代表搁置的信号集合也会一块保存
当sigsetjmp()返回0时代表已经做好记号上,若返回非0则代表由siglongjmp()跳转回来。
返回:若直接调用则为0,若从siglongjmp调用返回则为非0
在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。
但是,调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。(仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值:链接)如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢()?(setjmp和longjmp保存和恢复信号屏蔽字,还是不保存和恢复,不同的实现各有不同。)
为了允许两种形式的行为并存,POSIX.1并没有说明setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmp和siglongjmp。在信号处理程序中进行非局部转移时使用这两个函数。
- 下面例子中在执行完sigsetjmp后,将canjump 赋1,为了避免信号因为随时可能发生,导致先siglongjmp的情况
#include "apue.h"
#include <setjmp.h>
#include <time.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
int
main(void)
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
pr_mask("starting main: ");
if (sigsetjmp(jmpbuf, 1))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for(; ;)
pause();
}
static void
sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
starttime = time(NULL);
for(; ;) /* busy wait for 5 seconds */
if (time(NULL) > starttime + 5)
break;
pr_mask("finishing sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1); /* jump back to main, don't return */
}
static void
sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
3、sigsuspend
相关链接
问题引入:
如果在信号阻塞时将其发送给进程,那么该信号的传递就被推迟直到对它解除了阻塞。对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号,所以从这种意义上而言,在此时间窗口(解除阻塞和pause之间)中发生的信号丢失了,这样就使pause永远阻塞。
为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能是由sigsuspend函数提供的。
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/* critical region of code */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/* window is open */
pause(); /* wait for signal to occur */
/* continue processing */
#include <signal.h>
int sigsuspend( const sigset_t *sigmask );
返回值:-1,并将errno设置为EINTR
- 在sigprocmask和pause之间发生了信号,进程还未pause,那么如果后续没有信号后,进程一直在pause,永久挂起
- sigsuspend是为了解决前面阻塞了一个信号后,解除阻塞,并且挂起的操作如果进程切换会永久挂起的问题。将其变成原子操作
- sigsuspend是临时替换进程信号屏蔽集,并将进程挂起,如果接收到信号,且执行完信号处理函数后,会继续执行进程,且将临时信号屏蔽集替换为原信号屏蔽集!
- 将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
- 此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。
sigsuspend细述
3.1 例子1
#include "apue.h"
static void sig_int(int);
void fun();
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
//errno_save = errno; /* we can be called by signal handlers */
if (sigprocmask(0, NULL, &sigset) < 0)
printf("sigprocmask error");
printf("%s,%d", str,getpid());
if (sigismember(&sigset, SIGINT)) printf("SIGINT ");
if (sigismember(&sigset, SIGQUIT)) printf("SIGQUIT ");
if (sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 ");
if (sigismember(&sigset, SIGALRM)) printf("SIGALRM ");
/* remaining signals can go here */
printf("\n");
//errno = errno_save;
}
int main(void)
{
sigset_t newmask, oldmask, waitmask;
pr_mask("program start: ");
if(signal(SIGINT, sig_int) == SIG_ERR)
printf("signal(SIGINT) error");
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// if(signal(SIGUSR2, fun) == SIG_ERR)
// printf("signal(SIGUSR2) error");
signal(SIGUSR2, fun);
/*
* Block SIGINT and save current signal mask.
*/
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
printf("SIG_BLOCK error: ");
/*
* Critical region of code.
*/
pr_mask("in critical region: ");
/*
* Pause, allowing all signals except SIGUSR1.
*/
if(sigsuspend(&waitmask) != -1)
printf("sigsuspend error");
pr_mask("after return from sigsuspend: ");
/*
* Reset signal mask which unblocks SIGINT.
*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error");
/*
* And continue processing...
*/
pr_mask("program exit: ");
exit(0);
}
static void
sig_int(int signo)
{
pr_mask("\nin sig_int: ");
}
void fun(int signo)
{
pr_mask("USR2\n");
}
执行结果如下1
lxz@lxz-VirtualBox:~/liuxz/testapue$ gcc sigsuspend.c -o sigsuspend
lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend
program start: ,2597
in critical region: ,2597SIGINT //表示SIGINT在sigprocmask后被阻塞
USR2
,2597SIGUSR1
after return from sigsuspend: ,2597SIGINT //表示在sigsuspend执行完毕后,SIGINT恢复阻塞
program exit: ,2597 //解除阻塞
执行结果如下2
lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend
program start: ,2642
in critical region: ,2642SIGINT
^C // 执行INT,发现sigsuspend解除了INT的阻塞
in sig_int: ,2642SIGUSR1
after return from sigsuspend: ,2642SIGINT
program exit: ,2642
3.2 例子2
#include "apue.h"
volatile sig_atomic_t quitflag; /* set nonzero by signal handler */
static void
sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */
{
signal(SIGINT, sig_int); //这里要注意对于老版本的linux,执行完信号处理函数后,信号处理方式变成SIG_DFL
//但是新版本不会,而需要在sigaction,会SA_RESETHAND
signal(SIGQUIT, sig_int);
if (signo == SIGINT)
printf("\ninterrupt\n");
else if (signo == SIGQUIT)
quitflag = 1; /* set flag for main loop */
}
int
main(void)
{
sigset_t newmask, oldmask, zeromask;
if(signal(SIGINT, sig_int) == SIG_ERR)
printf("signal(SIGINT) error");
if(signal(SIGQUIT, sig_int) == SIG_ERR)
printf("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/*
* Block SIGQUIT and save current signal mask.
*/
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
printf("SIG_BLOCK error");
while(quitflag == 0)
{
sigsuspend(&zeromask);
}
/*
* SIGQUIT has been caught and is now blocked; do whatever.
*/
quitflag = 0;
/*
* Reset signal mask which unblocks SIGQUIT.
*/
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error");
exit(0);
}
结果1,按照上述写法,老版linux
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
结果2,没有在信号处理函数中重新写signal,老版linux