alarm函数允许我们设置一个在未来的某一时刻终止的定时器,当定时器终止的时候,SIGALRM信号就被发出,如果我们忽略或者不捕获这一信号的话,该信号的默认行为是终止进程.
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Returns:0 or number of seconds until previously set alarm.
seconds的数值指定了未来信号生成所需要的秒数,当事件到达,信号就会被内核生成,虽然在进程获得控制权开始处理信号之前会因为处理器调度而多花一些额外的时间。
早期版本的UNIX系统实现可以允许信号被提前1秒中发送,但是POSIX.1并不允许这种行为。
这些alarm时钟对于每一个进程来说只能至多拥有一个,如果,当我们调用alarm的时候,之前注册的一个alarm时钟还没有终止,那么早先设置的alarm时钟剩余的描述将被alarm函数作为返回值返回。同时前一个时钟将会被新的数值替换掉。
如果之前注册的alarm时钟还没有终止,而本次调用alarm函数的参数seconds为0,那么前一次alarm定时就会被取消,前一次设置的alarm时钟剩余的秒数将被作为函数的返回值返回。
虽然SIGALRM信号的默认处理是终止进程,但是许多实用alarm时钟的进程会捕获这一信号。如果我们需要捕获信号SIGALRM,我们必须小心:必须在调用alarm之前安装信号处理函数,否则可能由于在信号处理函数安装之前先收到了SIGALRM信号而导致进程终止.
函数pause用于挂起当前进程,直到捕获到一个信号:
#include <unistd.h>
int pause(void);
Returns:-1 with errno set to EINTR
pause返回的唯一的时间是一个信号处理函数被调用,然后该信号处理函数返回,在这种情况下,pause将返回-1,并且将errno设置为EINTR.
使用函数alarm以及pause,我们可以将一个进程放到睡眠状态一段指定的时间,函数sleep1就是为了达成这一目的,但是正如后面将会看到的,该函数有一个问题.
#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 seconds)
{
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
return (seconds);
}
alarm(seconds); /*start the timer*/
pause(); /*next caught signal wakes us up*/
return(alarm(0));/*turn off timer,return unslept time*/
}
该函数看起来与函数sleep差不多,但是这个简单的实现有三个问题:
早期版本的sleep函数与我们的上述程序比较相似,但是解决了其中提到的第1和2个问题。对于第三个问题有两种方式改正,第一种是使用函数setjmp,该函数我们将在下一章中讲解,另一种方式是使用函数sigprocmask以及sigsuspend,我们将在10.19中进行讲述。
SVR2实现中的sleep函数使用了函数setjmp以及longjmp来避免上面提到的竞态条件的出现,该函数的一个简单版本,称为sleep2,如图10.8中显示的那样,(为了减小该函数的尺寸,我们并没有解决上述提到的问题1以及问题2).
#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 seconds)
{
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
return (seconds);
if(setjmp(env_alrm) == 0)
{
alarm(seconds); /*start the timer*/
pause(); /*next caught signal wake us up*/
}
return (alarm(0)); /*turn off timer,return unslept time*/
}
Figure 10.8 Another (imperfect )implementation of sleep
函数sleep2避免了图10.7中的竞态条件问题,即使pause函数从来没有被执行过,当SIGALRM信号出现的时候,sleep2总是会成功返回。
然而,当我们涉及到与其他信号进行交互的时候,sleep2就会出现一个微妙的错误:如果SIGALRM中断了一些其他信号处理函数,然后当我们执行函数longjmp的时候,我们就会终止掉其他的信号处理函数,图10.9显示了这样的一个场景,在SIGINT处理函数中有一个循环,该循环保证了系统需要执行该函数5S以上,我们的目标是时期执行时间超过sleep2的参数,整形变量k被声明为volatile,是为了防止编译器优化的时候丢弃这个循环。
#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 wahtever 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");
}
当我们在执行10_9所示的程序的时候,按下终端字符来中断睡眠状态的进程,我们将得到如下的输出:
os@debian:~/UnixProgram/Chapter10$ ./10_9.exe
^C
sig_int starting
sleep2 returned:0
os@debian:~/UnixProgram/Chapter10$
我们可以看出sleep2中的longjmp函数会终止其他的信号处理函数sig_int,即是其他的信号处理函数还没有完成,如果你将SVR2的sleep函数与其他的信号处理函数混合使用的话,就会遇到这样的问题,详见练习10.3
sleep1与sleep2例子的目的是为了展示天真无邪地处理信号的过程中遇到的陷阱,接下来的章节将会接收避开上述问题的方法,因此你可以可靠地处理信号,并不需要与其他的代码段有干涉。
对于函数alarm的常见的使用,除了sleep函数的实现之外,就是设置可能发生阻塞的操作的时间上限,举例来说,如果我们对一个可能引起阻塞的设备执行读取操作,我们想要read函数可以超时退出,在图10.10中的程序就是这样做的,从标准输入中读取一行输入,然后将其写出到一个标准输出中去。
#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*/
}
Figure10.10 Calling read with a timeout
上述代码的处理序列在UNIX应用程序中是常见的,但是该应用程序有两个问题:
在这个例子中,我们想要一个慢速的系统调用被中断,我们将在10.14中看到一种可移植的方法来实现上述功能。
然我们再一次使用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);
}
Figure 10.11 Calling read with a timeout, using longjmp.
这一版本能够按照预期的进行工作,无论系统是否会自动重启被中断的系统调用函数,注意,对于上述程序仍然有与其他信号发生冲突的问题。
如果我们想要对一个IO操作设置一个时间限制,我们需要使用函数longjmp,同时需要识别可能与其他信号处理函数发生的冲突。另一个选择是使用函数select或者是poll,这些内容将在14.4.1以及14.4.2中讲解。
原文:http://www.cnblogs.com/U201013687/p/5518355.html