分类: LINUX
2012-01-28 20:34:44
++++++APUE读书笔记-10信号-10alarm和pause函数++++++
10、alarm和pause函数
================================================
alarm函数允许我们设置一个计时器,这个计时器可以在一个指定的时间内到期。当计时器到期的时候,SIGALRM信号就会产生。如果我们忽略或者不捕获这个信号,那么这个信号的默认处理动作就是把进程结束。
#include
unsigned int alarm(unsigned int seconds);
返回0或者返回前面设置的alarm的剩余到期秒。
秒值是信号将要发生的时钟的秒值。需要注意的是,如果时间到了,那么内核中发送信号,但是在进程真正处理这个信号之前,还会有一点延迟,这是处理器调度产生的延时。
早期的UNIX系统实现,会警告信号信号早一秒发送。POSIX.1不允许这样。
每一个进程只有一个这样的时钟。如果我们调用alarm,同时之前注册的alarm时钟没有到期,那么剩下的秒会被做为alarm函数的返回值,然后之前的注册的alarm始终被新的所替代。
如果进程之前注册的alarm时钟没有到期,并且alarm的seconds参数是0,那么之前的alarm时钟会被取消,同时返回之前时钟距到期剩余的秒数。
虽然SIGALRM信号的默认动作是终止进程,大多数进程使用alarm时钟来捕获信号。如果进程想要终止,那么它可以在终止之前进行需要的清理工作。如果我们想要捕获SIGALRM信号,我们需要注意在调用alarm之前来安装它的信号处理函数。因为如果我们先调用alarm,然后再我们安装信号处理函数之前发送SIGALRM信号了,那么我们的进程将会被终止。
pause函数会挂起调用它的进程,直到发起了一个信号。
#include
int pause(void);
返回1(一般是-1),同时设置errno为EINTR.
pause只能在信号处理函数被执行并且返回的时候才能返回。这个时候,pause返回的是1,同时设置errno为EINTR.
举例:
我们可以使用pause和alarm让进程sleep指定的时间.后面的sleep1函数就是这个样子的。
#include
#include
static void sig_alrm(int signo)
{ /* 什么也不做,只是返回并唤醒pause调用 */}
unsigned int sleep1(unsigned int nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
alarm(nsecs); /* 启动定时器 */
pause(); /* 暂停等待下一个信号来唤醒它 */
return(alarm(0)); /* 关闭计时器同时返回睡眠剩余的时间 */
}
这个函数的功能和sleep大体相当,但是这个简单的实现有以下三个问题:
a)这里的alarm可能会把以前的alarm设置的时钟给覆盖掉。为了修正这个问题,我们可以查看alarm的返回值:如果返回值小于我们的参数,那么我们只需等待那个alarm时钟到期就行了(为什么?)。如果返回值大于我们的参数,那么在返回之前我们应该重新设置alarm时钟在那个时间发生信号(不太明白?)
b)我们修改了SIGALRM的动作.如果我们写一个让别人调用的函数,我们应该在被调用的时候保存原来的处理动作,然后在我们返回的时候恢复原来的处理动作。
c)在alarm调用和pause调用之间有一个竞争条件。对于一个比较忙的系统,可能在我们调用pause之前就发生了alarm信号并且这个信号处理函数已经被调用了。如果这样的话之后我们再调用pause这个pause就永远不会再醒来了(假设期间不会再有其他的信号发生)。
早期实现的sleep和我们这里给处的类似,同时它修正了以上描述的前两个问题,对于第三个问题,有两种解决的方法:第一个方法是使用setjmp我们后面会看到这个,另外一个方法是使用sigprocmask和sigsuspend我们在后面会提到这个。
SVR2使用setjmp和longjmp来避免描述前面例子中存在的第三个问题.实现的函数这里是sleep2,后面给出(为了减少例子的代码量,这里我们没有处理第一、二个问题)。
#include
#include
#include
static jmp_buf env_alrm;
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1); /*通过这个跳转就不用担心pause无法返回了,因为一旦发生信号就跳到pause后面执行*/
}
unsigned int sleep2(unsigned int nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
if (setjmp(env_alrm) == 0) {
alarm(nsecs);
pause();
}
return(alarm(0));
}
这里,sleep2有另外一个问题,它会干扰其他的信号。如果SIGALRM打断了其他信号的信号处理函数,当我们调用longjmp的时候,实际上我们也取消了那个被打断的信号处理函数的处理,那个被打断的信号处理函数的剩下的部分就无法执行到了。这里也有一个例子,我就不在本文中列出了,需要看的话请参考参考资料吧。
这里给出的sleep1和sleep2这两个例子,是为了说明如果过于简单的处理信号,会出现什么问题。后面的章节中,将会展示所有和这些有关的问题,这样我们就可以可靠地处理信号,同时不干扰其他的代码。
另外一个例子
除了sleep之外,一个比较常用的使用alarm函数的另外一个例子就是设置一个阻塞操作的时间上限。例如,我们有一个慢设备,在这个设备上面进行可以阻塞的读取操作,我们可能只想等待读取一定的时间让它超时,下面的例子就展示了这个。它从标准输入读取一行,然后输出到标准输出。
static void sig_alrm(int signo)
{}
int main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
alarm(10);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
这样的代码次序在UNIX程序中是非常常见的,但是这个程序有两个问题,
a,在第一个alarm调用和read之间有一个时间竞争窗口.如果内核在这两个函数之间阻塞进程并且阻塞的时间比alarm设置的时间长,那么read函数将会被永远阻塞。大多数这个类型的操作使用一个比较长的时钟时间,例如一分钟,或者更多,这样发生这样无限阻塞的时间就非常的少了,但是这也毕竟是一个竞争时间窗口。
b,如果系统调用被自动重新启动了,那么read在AIGALRM信号处理函数返回的时候并不会被打断。这时候,超时不会发生任何事情。
这里,我们特别地,需要一个慢的系统调用被中断。POSIX.1没有为我们提供一个可以移植的办法来做这件事,然而,Single UNIX Specification的XSI扩展做了这件事情,后面会讨论更多的相关内容。
这里,我们使用longjmp来重新实现这个例子,这样我们就不用担心一个慢的系统调用是否被中断了。这个例子工作的很正常,不用考虑系统是否重启被打断的系统调用,然而我们还需要注意在和其他信号处理函数进行交互的时候还是会有问题的。
static jmp_buf env_alrm;
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
int main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
if (setjmp(env_alrm) != 0)
err_quit("read timeout");
alarm(10);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
如前面所示,如果我们想要设置一个I/O操作的时间限制,那么我们需要使用longjmp,但是我们也要意识到它可能和其他信号处理函数交互。另一个可选的方法是使用select或者poll函数,后面会讲到。
参考: