alarm和pause函数
alarm函数允许我们设置一个计时器,该计时器会在未来超时到期。当计时间逾期,会产生SIGALRM信号。如果我们忽略或不捕获该信号,它的默认动作是终止进程。
|
参数seconds是一个单位为秒的时间,当这个时间到了就会产生信号。要知道当时间到了,信号是由内核产生,但是在进程获得控制该信号之前,还是有一段额外的时间,因为这是处理器调度延时。
早期的UNIX系统实现可能会通过早一秒发送信号来减少延时。POSIX.1不允许这么做。
每个处理器只能有一个闹钟。如果当我们调用alarm时,之前该处理器注册的计时器还没有逾期,那么会返回上一个计时器的剩余时间为当前调用的返回值。之前注册的计时器会被新的计时器代替。
如果之前注册的计时器还没有逾期并且如果当前alarm函数的seconds值是0,那么之前的计时器会被取消。之前计时器的剩余时间同样会做为函数返回值返回。
虽然SIGALRM的默认动作是终止进程,但多数进程仍然捕获该信号。如果进程想去终止自己,它能在结束之前执行一些清理工作。如果我们倾向于捕获SIGALRM,我们需要在调用alarm之前小心安装它的信号处理函数。如果先调用了alarm函数并发送了SIGALRM信号,而这时还没安装好信号处理函数,那么进程会终止。
pause函数挂起调用它的进程直到捕获相应信号。
|
只有当信号处理函数执行并返回时pause函数才会返回。在这种情况下pause返回1,errno设置成EINTR。
例子:
使用alarm和pause,我们可以让一个进程休眠指定时间:
Figure 10.7. Simple, incomplete implementation of sleep#include <signal.h> #include <unistd.h> static void sig_alrm(int signo) { /* nothing to do, just return to wake up the pause */ } unsigned int sleep1(unsigned int nsecs) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(nsecs); alarm(nsecs); /* start the timer */ pause(); /* next caught signal wakes us up */ return(alarm(0)); /* turn off timer, return unslept time */ } |
这个函数看上去像是sleep函数,但是这个简单实现有三个问题:
- 如果调用者已经设置了一个alarm,那么这个计时器会被新的代替。我们能纠正这个问题,通过观察alarm的返回值。如果之前设置的计时器会先到时,那我们要等待它逾期后再设置我们的计时器。如果之前的计时器会在我们的计时器之后逾期,那我们可以先暂时记录剩下的时间,等我们的计时器逾期后,重新设置它的时间。
- 我们已经修改了SIGALRM的动作。如果我们正在为另一个调用写函数,那我们应该把当前的动作记录下来,然后再修改动作,等调用完毕后再回复它。我们可以通过signal的返回值来保存之前的动作。
- 在第一个alarm和pause之间有竞争条件。在系统繁忙时,可能计时器逾期并调用信号处理函数在调用pause之前。如果发生了,调用者会被pause永久的挂起(假设其它信号不会被捕捉)。
例子:
SVR2的sleep实现使用了setjmp和longjmp,用于避免竞争条件。本例是个简单的版本,被称作sleep2,如Figure 10.8所示。(为了减少长度,我们没有处理上面所说的三个问题中的1、2)
sleep2函数避免了Figure 10.7的竞争条件。即使pause永远不被执行,当SIGALRM发生时,sleep2函数都会返回。
Figure 10.8. Another (imperfect) implementation of sleep#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
static jmp_buf env_alrm;
static void
sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
unsigned int
sleep2(unsigned int nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
if (setjmp(env_alrm) == 0) {
alarm(nsecs); /* start the timer */
pause(); /* next caught signal wakes us up */
}
return(alarm(0)); /* turn off timer, return unslept time */
}
|
该函数有一个不容易被发现的问题——当需要和其它信号互动的时候。如果SIGALRM中断了其它信号处理函数,当我们调用longjmp时,我们忽略了它其信号处理函数。
Figure 10.9. Calling sleep2 from a program that catches other signals#include "apue.h" unsigned int sleep2(unsigned int); static void sig_int(int); int main(void) { unsigned int unslept; if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); unslept = sleep2(5); printf("sleep2 returned: %u\n", unslept); exit(0); } static void sig_int(int signo) { int i, j; volatile int k; /* * Tune these loops to run for more than 5 seconds * on whatever system this test program is run. */ printf("\nsig_int starting\n"); for (i = 0; i < 300000; i++) for (j = 0; j < 4000; j++) k += i * j; printf("sig_int finished\n"); } |
Figure 10.9显示了这个方案。在SIGINT处理函数的循环中已经写了,它的执行在某些系统上要很久。我们只是简单的想让它执行的时间比sleeep2时间长一些。整型k被声明为volatile为了防止编译器优化循环中的k。执行Figure 10.9中的程序显示如下:
$ ./a.out
^? we type the interrupt character
sig_int starting
sleep2 returned: 0
|
sleep1和sleep2这两个函数的目的是显示使用信号的隐患。下面的章节将会围绕所有这些问题,如何可靠的处理信号并不添加多余的代码。
例子:
alarm和sleep函数的通常用法是,放一个计时器去限制某些操作产生阻塞。例如:如果我们read操作一个设备(该设备可能产生阻塞,它是一个“慢”速设备),在read一段时间后超时。Figure 10.10显示了从标准输入读一行,然后写到标准输出。
该程序是在UNIX下的程序,但是这个程序有两个问题。
- 该程序有像Figure10.7一样的瑕疵:在第一次调用alarm和调用read之间有竞争条件。如果内核在这两个函数间阻塞进程的时间超过计时器的周期,read可能永久的被阻塞 。多数这种操作使用一个较长的计时器周期,但无论怎样这都是一个竞争条件。
- 如果系统调用自动重启,当SIGALRM信号处理函数返回时,read不会被中断。在本例中,超时不会做任何时事。
Figure 10.10. Calling read with a timeout#include "apue.h"
static void sig_alrm(int);
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);
}
static void
sig_alrm(int signo)
{
/* nothing to do, just return to interrupt the read */
}
|
例子:
使用longjmp重写上面的例子。使用这种方法不用担心是否有一个慢系统调用被中断。
这个本版会如希望的那样工作,无论系统是否重新启动被中断的系统调用。实际上,这个版本仍然存在和其它信号交互的问题。
Figure 10.11. Calling read with a timeout, using longjmp#include "apue.h"
#include <setjmp.h>
static void sig_alrm(int);
static jmp_buf env_alrm;
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);
}
static void
sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
|
如果我们想设置一个计时器来限置I/O操作,我们需要使用longjmp,就像上面显示的那样,实际使用过程中它很有可能和其它信号处理函数的交互。另一个选择是使用select或poll函数。