一、间隔定时器
1.setitimer函数
下列函数创建一个间隔定时器,这种定时器在某个时间点到期,并与此后每隔一段时间到期一次,创建的定时器可跨越exec()调用得以保存,但由fork()创建的子进程并不继承该定时器。
#include<sys/time.h>
int setitimer(int which,const struct itimerval *new_value,struct itimerval *old_value);
//成功返回0,错误返回-1。
创建3种不同类型的定时器which的类型:对下列情况所有这些信号的默认处置均会终止进程。
ITIMER_REAL
:创建真实时间倒计时的定时器,到期产生SIGALARM
信号并发送进程。ITIMER_VIRTUAL
:创建以进程虚拟时间倒计时的定时器。到期产生SIGALARM
信号。ITIMER_PROF
:创建一个profiling定时器,以进程时间倒计时。到期时,则会产生SIGPROF
信号。
new_value和old_value
均指向结构itimerval
的指针:
struct itimerval{
struct timeval it_inteerval;
struct timeval it_value;
};
//字段类型均为timeval结构:
struct timeval{
time_t tv_sec;//秒
suseconds_t tv_usec;//纳秒
}
-
new_value
的下属结构it_value
指定了距离定时器到期的延迟时间,另一个下属结构it_interval
说明定时器是否为周期定时器。 -
若
it_interval
的两个字段值均为0,那么该定时器就属于在it_value
所指定的时间间隔后到期的一次性定时器,否则,在每次定时器到期之后,都会将定时器重置为在指定间隔后再次到期。 -
进程只能拥有3种的一种,当第二次调用
setitmer()
时,修改已有定时器的属性要符合参数which
中的类型。若调用setitimer()
时将new_value.it_value
的两个字段均置为0,则会屏蔽任何已有的定时器。 -
old_value
不为NULL,则以其所指向的itimerval
结构来返回定时器的前一设置。 -
若
old_value.it_value
的两个字段为0,则定时器之前处于屏蔽状态;若old_value.it_interval
的两个字段值为0,那么该定时器之前被设置为历经old_value.it_value
指定时间而到期的一次性定时器。 -
不关心定时器的前一设置,可以将
old_value
置为NULL。 -
定时器会从初始值
(it_value)
倒计时一直到0.递减为0时,会将相应信号发送给进程,随后,如果时间间隔值(it_interval)
非0,那么会再次将it_value
加载至定时器,重新开始向0倒计时。
2.getitimer函数
可以在任何时刻调用getitimer()
,以了解定时器的当前状态、距离下次到期的剩余时间。
#include<sys/time.h>
int getitimer(int which,struct itimerval *curr_value);
//成功返回0,错误返回-1。
-
系统调用此函数返回由
which
指定定时器的当前状态,并置于由curr_value
所指向的缓冲区中。 -
子结构
curr_value.it_value
返回距离下一次到期所剩余的总时间 -
若设置定时器将
it_interval
置为非0值,那么会在定时器到期时将其重置。
子结构curr_value.it_interval
返回定时器的时间时间,除非再次调用setitimer(),否则该值一直不变。
3.setitimer()和getitimer()的使用
#include <signal.h>
#include <sys/time.h>
#include <time.h>
static volatile sig_atomic_t gotAlarm = 0;
/* 收到SIGALRM时设置为非零 */
/* 检索并显示实时,并且(如果“includeTimer”为TRUE)ITIMER_REAL计时器的当前值和间隔*/
static void displayTimes(const char *msg, Boolean includeTimer)
{
struct itimerval itv;
static struct timeval start;
struct timeval curr;
static int callNum = 0; /* 调用此函数的次数 */
if (callNum == 0) /* 初始化已用计时器 */
if (gettimeofday(&start, NULL) == -1)
errExit("gettimeofday");
if (callNum % 20 == 0) /*每隔20行打印页眉 */
printf(" Elapsed Value Interval\n");
if (gettimeofday(&curr, NULL) == -1)
errExit("gettimeofday");
printf("%-7s %6.2f", msg, curr.tv_sec - start.tv_sec +
(curr.tv_usec - start.tv_usec) / 1000000.0);
if (includeTimer) {
if (getitimer(ITIMER_REAL, &itv) == -1)
errExit("getitimer");
printf(" %6.2f %6.2f",
itv.it_value.tv_sec + itv.it_value.tv_usec / 1000000.0,
itv.it_interval.tv_sec + itv.it_interval.tv_usec / 1000000.0);
}
printf("\n");
callNum++;
}
static void
sigalrmHandler(int sig)
{
gotAlarm = 1;
}
int main(int argc, char *argv[])
{
struct itimerval itv;
clock_t prevClock;
int maxSigs; /* 退出前要捕捉的信号数 */
int sigCnt; /* 到目前为止捕获的信号数 */
struct sigaction sa;
if (argc > 1 && strcmp(argv[1], "--help") == 0)
usageErr("%s [secs [usecs [int-secs [int-usecs]]]]\n", argv[0]);
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sigalrmHandler;
//为SIGALRM信号创建处理器函数
if (sigaction(SIGALRM, &sa, NULL) == -1)
errExit("sigaction");
/* 从命令行参数设置计时器 */
itv.it_value.tv_sec = (argc > 1) ? getLong(argv[1], 0, "secs") : 2;
itv.it_value.tv_usec = (argc > 2) ? getLong(argv[2], 0, "usecs") : 0;
itv.it_interval.tv_sec = (argc > 3) ? getLong(argv[3], 0, "int-secs") : 0;
itv.it_interval.tv_usec = (argc > 4) ? getLong(argv[4], 0, "int-usecs") : 0;
//3个信号后退出,如果间隔为0,则在第一个信号时退出
maxSigs = (itv.it_interval.tv_sec == 0 &&
itv.it_interval.tv_usec == 0) ? 1 : 3;
displayTimes("START:", FALSE);
//利用命令行参数为实时定时器设置到期值及间隔时间
if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
errExit("setitimer");
prevClock = clock();
sigCnt = 0;
for (;;) {
/* 部循环至少消耗0.5秒CPU时间 */
while (((clock() - prevClock) * 10 / CLOCKS_PER_SEC) < 5) {
if (gotAlarm) { //判断是否收到信号
gotAlarm = 0;
displayTimes("ALARM:", TRUE);
sigCnt++;
if (sigCnt >= maxSigs) {
printf("That's all folks\n");
exit(EXIT_SUCCESS);
}
}
}
prevClock = clock();
displayTimes("Main: ", TRUE);
}
}
4.函数alarm
此函数为创建一次性实时定时器提供简单接口。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
//seconds:表示定时器到期的秒数。到期发送SIGALRM信号。
//返回值是定时器前一设置距离到期的剩余秒数,未设置定时器返回0.
二、为阻塞操作设置超时
实时定时器的用途之一为某个阻塞系统调用设置其处于阻塞状态的时间上限。例:当用户在一段时间内没有输入整行命令时,可能希望取消对终端的read()操作。步骤如下:
- 调用sigaction()为SIGALRM信号创建处理器函数,排除SA_RESTART标志以确保系统调用不会重新启动。
- 调用alarm()或setitimer()来创建一个定时器,同时设定希望系统调用阻塞的时间上限。
- 执行阻塞式系统调用。
- 系统调用后,再次调用alarm()或setitimer()以屏蔽定时器。
- 检查系统调用失败时是否将errno置为EINTR。
#include <signal.h>
#define BUF_SIZE 200
//SIGALRM处理程序:中断阻止的系统调用
static void handler(int sig)
{
printf("Caught signal\n"); /* 不安全 */
}
int main(int argc, char *argv[])
{
struct sigaction sa;
char buf[BUF_SIZE];
ssize_t numRead;
int savedErrno;
if (argc > 1 && strcmp(argv[1], "--help") == 0)
usageErr("%s [num-secs [restart-flag]]\n", argv[0]);
/* 为SIGALRM设置处理程序。允许中断系统调用,
除非提供了第二个命令行参数. */
sa.sa_flags = (argc > 2) ? SA_RESTART : 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = handler;
if (sigaction(SIGALRM, &sa, NULL) == -1)
errExit("sigaction");
alarm((argc > 1) ? getInt(argv[1], GN_NONNEG, "num-secs") : 10);
numRead = read(STDIN_FILENO, buf, BUF_SIZE);
savedErrno = errno; /* 万一alarm()改变了它*/
alarm(0); /* 确保计时器已关闭 */
errno = savedErrno;
/* 确定读取结果() */
if (numRead == -1) {
if (errno == EINTR)
printf("Read timed out\n");
else
errMsg("read");
} else {
printf("Successful read (%ld bytes): %.*s",
(long) numRead, (int) numRead, buf);
}
exit(EXIT_SUCCESS);
}
三、POSIX间隔式定时器
使用setitimer来设置间隔定时器会受到制约:
-
针对setitimer的三类定时器,每种只能设置一个。
-
只能通过发送信号的方式通知定时器。也不能改变到期时产生的信号。
-
若间隔式定时器到期多次,且相应信号遭到阻塞时,只调用一次循环处理器函数。
-
定时器的分辨率只能达到微秒级。
POSIX提供了一套API来突破这些限制,将定时器生命周期划分为如下几个阶段:
- 系统调用
timer_create()
创建一个新定时器,并定义其到期时对进程的通知方法。 - 系统调用
timer_settime()
来启动或停止一个定时器。 - 系统调用
timer_delete()
删除不需要的定时器。
1.创建定时器:time_create()
此函数用于创建新定时器,并由clockid指定的时钟来进行时间度量。
#include<signal.h>
#include<time.h>
int timer_creat(clock_t clockid,struct sigevent *evp,timer_t *timerid);
clockid属性值:
- 函数返回时会在参数
timerid
所指向的缓冲区中放置定时器句柄,供后续调用中指代该定时器用。缓冲区的类型为timer_t。 - 参数
evp
可决定定时器到期时对应用程序的通知方式,指向类型为sigevent
数据结构:
union sigval{
int sival_int;
void *sival_ptr;
};
struct sigevent{
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
union{
pid_t _tid;
struct{
void (_*function)(union sigval);
void *_attribute;
}_sigev_thread;
}_sigev_un;
};
#define sigev_notify_function _sigev_un._sigev_thread._function
#define sigev_notify_attributes _sigev_un.sigev_thread._attribute
#define sigev_notify_thread_id _sigev_un._tid
2.配置和解除定时器:timer_settime()
创建定时器之后,使用此函数对其进行配备(启动)或解除(停止)。
#include<time.h>
int timer_settime(timer_t timerid,,int flags,const struct itimerspec *value,struct itimerspec *old_value);
//flags为0,则会将value.it_value视为始于timer_settime调用时间点的相对值。
//flags:TIMER_ABSTIME则value.it_value绝对时间。
-
参数
timerid
是个定时器句柄,由timer_create()
的调用返回。 -
参数
value和old_value
则类似于函数setitimer()
的同名参数:value
中包含定时器的新设置,lod_value
则用于返回定时器的前一设置。 -
对定时器的前一设置不感兴趣,可将
old_value
置为NULL。
参数value和old_value
指向结构itimerspec
的指针:
struct itimerspec{
struct timespec it_interval;
struct timespec it_value;//指定定时器首次到期时间
};
//itimerspec所有子字段都是timespec类型的结构,,用秒和纳秒指定时间:
struct timespec{
time_t tv_sec;
long tv_nsec;
}
-
启动定时器,调用此函数,并将
value.it_value
的一个或全部下属字段设为非0值。配备过定时器,此函数将之前替换。 -
定时器值和间隔时间并非对应时钟分辨率的整数倍,那么会对这些值做向上取整。
-
解除定时器,调用此函数,并将
value.it_value
的所有字段指定为0。
3.获取定时器的当前值:time_gettime()
#include<time.h>
int timer_gettime(time_t timerid,struct itimerspec *curr_value);
//成功返回0;失败-1
curr_value
指针指向itimerspec
结构中的时间间隔以及距离下次定时器到期的时间。- 创建
TIMER_ABSTIME
标志的绝对时间定时器,在curr_value.it_value
字段中返回的也是距离定时器下次到期的时间值。 - 若返回结构
curr_value.it_value
的两个字段均为0,定时器处于停止状态。 - 若返回
curr_value.it_interval
的两个字段均为0,定时器在curr_value.it_value
给定时间到期一次。
4.删除定时器
使用此函数删除定时器释放资源。
#include<time.h>
int time_delete(time_t timerid);
//timerid是之前调用timer_creat()时返回的句柄。
5.使用POSIX定时器通知
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define TIMER_SIG SIGRTMAX
#ifndef ITIMERSPEC_FROM_STR_H
#define ITIMERSPEC_FROM_STR_H
#endif
//将"时间+间隔”的字符串转换为itimerspec的值
void itimerspecFromStr(char *str, struct itimerspec *tsp)
{
char *dupstr ,*cptr, *sptr;
dupstr = strdup(str);
cptr = strchr(dupstr, ':');
if (cptr != NULL)
*cptr = '\0';
sptr = strchr(dupstr, '/');
if (sptr != NULL)
*sptr = '\0';
tsp->it_value.tv_sec = atoi(dupstr);
tsp->it_value.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
if (cptr == NULL) {
tsp->it_interval.tv_sec = 0;
tsp->it_interval.tv_nsec = 0;
} else {
sptr = strchr(cptr + 1, '/');
if (sptr != NULL)
*sptr = '\0';
tsp->it_interval.tv_sec = atoi(cptr + 1);
tsp->it_interval.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
}
free(dupstr);
}
static void handler(int sig, siginfo_t *si, void *uc)
{
timer_t *tidptr;
tidptr = si->si_value.sival_ptr;
/* UNSAFE:此处理程序使用非异步信号安全函数 */
printf("[%s] Got signal %d\n", currTime("%T"), sig);
printf(" *sival_ptr = %ld\n", (long) *tidptr);
printf(" timer_getoverrun() = %d\n", timer_getoverrun(*tidptr));
}
int main(int argc, char *argv[])
{
struct itimerspec ts;
struct sigaction sa;
struct sigevent sev;
timer_t *tidlist;
int j;
if (argc < 2)
usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]]...\n", argv[0]);
tidlist = calloc(argc - 1, sizeof(timer_t));
if (tidlist == NULL)
errExit("malloc");
/* 建立通知信号处理程序 */
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
//为用于定时器通知的信号创建处理函数
if (sigaction(TIMER_SIG, &sa, NULL) == -1)
errExit("sigaction");
/* 为每个命令行参数创建并启动一个计时器*/
sev.sigev_notify = SIGEV_SIGNAL; /*通过信号通知 */
sev.sigev_signo = TIMER_SIG; /* 使用此信号通知 */
for (j = 0; j < argc - 1; j++) {
itimerspecFromStr(argv[j + 1], &ts);
sev.sigev_value.sival_ptr = &tidlist[j];
/* 允许处理程序获取此计时器的ID */
if (timer_create(CLOCK_REALTIME, &sev, &tidlist[j]) == -1)
errExit("timer_create");
printf("Timer ID: %ld (%s)\n", (long) tidlist[j], argv[j + 1]);
if (timer_settime(tidlist[j], 0, &ts, NULL) == -1)
errExit("timer_settime");
}
for (;;) /* 等待输入的定时器信号 */
pause();
}
四、定时器溢出
接收定时器信号之后,两种方法获得定时器溢出值:
- 调用
timer_getoverrun()
。 - 使用随信号一同返回的结构
siginfo_t中的si_overrun
字段值。避免第一种方法开销。
每次收到定时器信号后,都会重置定时器溢出计数。若自处理或接收定时器信号之后,定时器仅到期一次,则溢出计数为0。
#include<time.h>
int timer_getoverrun(time_t timerid);
//异步安全函数
//timerid指定定时器的溢出值
五、利用文件描述符进行通知的定时器:timerfd API
1.创建新的定时器:timerfd_create
此函数创建一个新的定时器对象,并返回指代该对象的文件描述符:
#include<sys/timerfd.h>
int timerfd_create(int clockid,int flags);
参数clockid属性值为:
CLOCK_REALTIME
CLOCK_MONOTONIC
参数flags属性值:
TFD_CLOEXEC
:为新的文件描述符设置运行时关闭标准。TFD_NONBLOCK
:为底层的打开文件设置此标志,随后读操作将是阻塞的。
此函数创建定时器使用完毕后,应调用close()关闭相应的文件描述符。以便内核能够释放与定时器相关的资源。
2.启动或停止定时器:timerfd_settime
此函数用于配备(启动)或解除(停止)由文件描述符fd所指代的定时器。
#include<sys/timerfd.h>
int timerfd_settime(int fd,int flags,const struct itimerspce *new_value,struct itimerspec *old_value);
//new_value为定时器指定新设置;
//old_value:返回定时器的前一设置
//两个参数均执行itimerspec结构
//flags:timer_settime相同,可以为0.
//将new_value.it_value的值视为相对于timefd_settime()时间点的相对时间。也可设置为TFD_TIMER_ABSTIME视为绝对值。
3.查看定时器间隔及剩余时间
此函数返回文件描述符fd所标识定时器的间隔及剩余时间。
#include<sys/timerfd.h>
int timefd_gettime(int fd,struct itimerspec *curr_value);
//TFD_TIMER_ABSTIME视为绝对值,curr_value.it_value字段返回的意义也会保持不变。
//curr_value.it_value中所有字段值均为0,定时器被解除。
//curr_value.it_interval中两字段均为0,定时器只会到期一次。到期时间在curr_value.it_value中给出。
//间隔以及下次到期的时间均返回curr_value指向的结构itimerspec中。
4.使用timefd API
#include <sys/timerfd.h>
#include <time.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define ITIMERSPEC_FROM_STR_H
void itimerspecFromStr(char *str, struct itimerspec *tsp)
{
char *dupstr ,*cptr, *sptr;
dupstr = strdup(str);
cptr = strchr(dupstr, ':');
if (cptr != NULL)
*cptr = '\0';
sptr = strchr(dupstr, '/');
if (sptr != NULL)
*sptr = '\0';
tsp->it_value.tv_sec = atoi(dupstr);
tsp->it_value.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
if (cptr == NULL) {
tsp->it_interval.tv_sec = 0;
tsp->it_interval.tv_nsec = 0;
} else {
sptr = strchr(cptr + 1, '/');
if (sptr != NULL)
*sptr = '\0';
tsp->it_interval.tv_sec = atoi(cptr + 1);
tsp->it_interval.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
}
free(dupstr);
}
int main(int argc, char *argv[])
{
struct itimerspec ts;
struct timespec start, now;
int maxExp, fd, secs, nanosecs;
uint64_t numExp, totalExp;
ssize_t s;
//从命令行获得两个参数。
if (argc < 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]] [max-exp]\n", argv[0]);
itimerspecFromStr(argv[1], &ts);
maxExp = (argc > 2) ? getInt(argv[2], GN_GT_0, "max-exp") : 1;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1)
errExit("timerfd_create");
if (timerfd_settime(fd, 0, &ts, NULL) == -1)
errExit("timerfd_settime");
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
errExit("clock_gettime");
for (totalExp = 0; totalExp < maxExp;) {
/* Read number of expirations on the timer, and then display
time elapsed since timer was started, followed by number
of expirations read and total expirations so far. */
s = read(fd, &numExp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
errExit("read");
totalExp += numExp;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
errExit("clock_gettime");
secs = now.tv_sec - start.tv_sec;
nanosecs = now.tv_nsec - start.tv_nsec;
if (nanosecs < 0) {
secs--;
nanosecs += 1000000000;
}
printf("%d.%03d: expirations read: %llu; total=%llu\n",
secs, (nanosecs + 500000) / 1000000,
(unsigned long long) numExp, (unsigned long long) totalExp);
}
exit(EXIT_SUCCESS);
}