分类:
2008-12-08 12:13:50
10.10 alarm and pause functions
本篇将讲述了使用pause,alarm来模仿sleep(n)的机制,以前有的事先就这样的,但是是有bug的。然后讲述了改良的使用pause, alarm, setlongjmp, longjmp来实现的sleep,但是,还是有bug。得出结论:这些东西要小心使用,并列举了几种合理的使用方法,特别是在作类似teminal device i/o等会block的操作的时候,需要一种超时检查机制。最后说其实,要实现i/o的block的超时检查机制,还可以使用select/poll。
#include unsigned int alarm(unsigned int seconds); |
Returns: 0 or number of seconds until previously set alarm |
#include int pause(void); |
Returns: 1 with errno set to EINTR |
(一)alarm函数
一个进程最多只有一个alarm,因此新的回覆盖旧的。
Alarm(0)用来取消以前定义的alarm。
SIGALRM信号的结果默认是进程被terminate。
(二)pause函数
当受到一个信号后,并且执行了该信号的handler之后,pause才返回。
(三)用pause和alarm模仿sleep(n),即sleep1
#include
#include
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 */
}
这样做的缺点:
1.没有考虑以前的alarm的超时时间还有多少,(我认为这一点并不至关重要)
2.你的sleep如果被别人调用,会有副作用,即将别人的SIGALRM信号的handler该改了,所以在你的sleep结束之前要给人家改回去。
3.存在race
condition,即调用了alarm(nsecs)之后,如果schedule执行别的进程去了,当超时后,kernel给本进程发送信号,本进程会执行handler。之后,等执行pause()的时候,信号可能不会再发出。就死死的block了。当然谁叫你没有将该信号block呢。
(四)用pause, alarm, setlongjmp, longjmp来改良sleep1=〉sleep2
#include
#include
#include
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
*/
}
这个实现第一次在执行setlongjmp的时候,返回0表明记录位置成功,然后alarm()被调用,handler执行,handler使用longjmp修改了执行路径,它并不是简单的返回,而是跳到setjmp处使其返回1, 这样就会跳过pause调用,从而不会block。
缺点:当此信号将别的信号的handler中断的时候,别的信号handler就不会成功完成。为什么我们只在此处关心他对信号handler的影响,因为在我们pause之后,是不可能在执行别的函数的,只可能去执行别的信号的handler,那么如果A的handler在执行,你的SIGALRM一来,就打断了当前的A的handler,而且最终跳回了sleep函数的stack frame,将A
handler的stack
frame就给取消了。看例子:
#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");
}
上述三,四是在表述几种naïve的使用signal的方法。接下来来展现合理的使用:
(五)使用 alarm来实现time limit time out的操作
#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 */
}
Timeout发生,signal handler启动, read被interrupted,返回即可。
缺点:
1.如果read自动restart的话,那么这个time out 检查机制就无效了。
2.Alarm(10)之后,调用read之前,可能发生类似上面sleep1的race condition。使丢失alarm信号。一直block,所以,alarm()的时间要足够长。
(六)改良,使我们的time out机制不因read自动restart而失效的方法,即要求超时了就必须得停下来
#include "apue.h"
#include
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);
}
一旦发生handler调用,就直接跳到setjmp调用处,判断情形,然后就执行别的代码去了,就不会再让被interrupted read返回了。默认情况下,如果handler返回,那么就会从被打断的system call处执行。可能system call就因此返回了,也可能他自己就restart了。