定时器概述
定时器是操作系统提供的核心机制之一,用于在指定时间后或按固定间隔触发事件。在C语言中,定时器广泛应用于协议超时检测、周期性任务调度、性能统计、心跳机制等场景。
Linux定时器方案分类
- sleep族函数:
sleep、usleep、nanosleep,简单但阻塞线程 - alarm/setitimer:基于信号,仅支持秒/微秒级,一个进程只能有一个
- POSIX定时器:
timer_create/timer_settime,纳秒级精度,可自定义通知方式 - timerfd:Linux特有,将定时器与文件描述符结合,可配合epoll使用
时钟类型
CLOCK_REALTIME:系统实时时钟,受NTP调整影响CLOCK_MONOTONIC:单调递增,不受系统时间修改影响,适合测量间隔CLOCK_BOOTTIME:同MONOTONIC但包含休眠时间CLOCK_PROCESS_CPUTIME_ID:进程CPU时间
sleep族函数
sleep族函数是最简单的定时方式,让当前线程暂停执行指定时间。
#include <unistd.h>
#include <time.h>
unsigned int sleep(unsigned int seconds); /* 秒级 */
int usleep(useconds_t usec); /* 微秒级(已废弃) */
int nanosleep(const struct timespec *req, struct timespec *rem); /* 纳秒级 */
nanosleep正确用法
#include <stdio.h>
#include <time.h>
#include <errno.h>
int main(void) {
struct timespec req = { .tv_sec = 0, .tv_nsec = 500000000 };
struct timespec rem;
/* 被信号中断时需继续休眠剩余时间 */
while (nanosleep(&req, &rem) == -1) {
if (errno == EINTR) {
req = rem;
} else {
perror("nanosleep");
return 1;
}
}
printf("Sleep completed\n");
return 0;
}
sleep族函数的局限:阻塞线程、精度受内核调度限制(1-4ms)、无法实现多个并发定时任务、被信号中断需手动处理。
POSIX定时器接口
POSIX定时器支持纳秒级精度,每个进程可创建多个独立定时器,支持信号或线程通知。
#include <time.h>
#include <signal.h>
/* 创建定时器 */
int timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid);
/* 设置定时器 */
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *new_value, struct itimerspec *old_value);
/* 获取剩余时间 */
int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
/* 删除定时器 */
int timer_delete(timer_t timerid);
/* 获取超期溢出次数 */
int timer_getoverrun(timer_t timerid);
关键数据结构
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 (0-999999999) */
};
struct itimerspec {
struct timespec it_value; /* 首次超时时间 */
struct timespec it_interval; /* 周期间隔(0=单次) */
};
通知方式
SIGEV_NONE:不通知,仅通过timer_gettime查询SIGEV_SIGNAL:超时时发送信号,在信号处理函数中处理SIGEV_THREAD:超时时在新线程中调用指定函数SIGEV_THREAD_ID(Linux扩展):发送信号到指定线程
信号驱动定时器
信号驱动是最常用的POSIX定时器模式,定时器超时时发送信号,进程在信号处理函数中执行定时逻辑。
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#define SIG_TIMER SIGRTMIN
static volatile int tick_count = 0;
static void timer_handler(int sig, siginfo_t *si, void *uc) {
if (sig == SIG_TIMER) {
tick_count++;
const char msg[] = "tick\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}
}
int main(void) {
struct sigaction sa = {0};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIG_TIMER, &sa, NULL);
timer_t timerid;
struct sigevent sev = {0};
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG_TIMER;
sev.sigev_value.sival_ptr = &timerid;
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
/* 1秒后首次超时,之后每500ms一次 */
struct itimerspec its = {
.it_value = { .tv_sec = 1, .tv_nsec = 0 },
.it_interval = { .tv_sec = 0, .tv_nsec = 500000000 }
};
timer_settime(timerid, 0, &its, NULL);
while (tick_count < 20)
pause();
timer_delete(timerid);
printf("Stopped after %d ticks\n", tick_count);
return 0;
}
使用实时信号避免丢失
标准信号不可排队,同一信号多次发送只记录一次。实时信号(SIGRTMIN~SIGRTMAX)会排队不会丢失。通过 timer_getoverrun 可获取因排队限制丢失的超时次数。
线程通知模式
SIGEV_THREAD 模式在定时器超时时创建新线程执行回调,避免了信号处理的限制:
#define _GNU_SOURCE
#include <stdio.h>
#include <time.h>
#include <unistd.h>
static void timer_callback(union sigval sv) {
int *counter = (int *)sv.sival_ptr;
(*counter)++;
printf("Timer tick #%d\n", *counter);
}
int main(void) {
static int counter = 0;
timer_t timerid;
struct sigevent sev = {0};
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = timer_callback;
sev.sigev_value.sival_ptr = &counter;
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
struct itimerspec its = {
.it_value = { .tv_sec = 1, .tv_nsec = 0 },
.it_interval = { .tv_sec = 1, .tv_nsec = 0 }
};
timer_settime(timerid, 0, &its, NULL);
while (counter < 5) sleep(1);
timer_delete(timerid);
return 0;
}
SIGEV_THREAD 的优点是回调在独立线程执行,不受异步信号安全限制;缺点是每次超时创建线程,开销较大,不适合高频定时器。
定时器精度与漂移
影响精度的因素
- 内核HZ:时钟中断频率(100/250/1000Hz),决定最小调度粒度
- 调度延迟:定时器到期后需等待内核调度
- 信号延迟:不可中断系统调用期间信号会延迟投递
- NTP调整:CLOCK_REALTIME 受NTP同步影响可能导致跳变
避免累积漂移
/* 错误:每次从当前时间计算下次超时,产生累积漂移 */
while (running) {
nanosleep(&interval, NULL);
do_periodic_task(); /* 任务执行时间被加入间隔 */
}
/* 正确:使用 TIMER_ABSTIME 绝对时间避免漂移 */
struct itimerspec its;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
its.it_value.tv_sec = now.tv_sec + 1; /* 绝对时间 */
its.it_value.tv_nsec = now.tv_nsec;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 500000000;
/* TIMER_ABSTIME:it_value 为绝对时间戳 */
timer_settime(timerid, TIMER_ABSTIME, &its, NULL);
使用 TIMER_ABSTIME 时,it_value 被解释为绝对时间戳而非相对时间,内核自动在正确时间点触发定时器,消除累积漂移。
实战:多定时器管理框架
以下实现基于POSIX定时器和实时信号的多定时器管理框架,支持动态添加、删除定时器。
/* timer_framework.h */
#ifndef TIMER_FRAMEWORK_H
#define TIMER_FRAMEWORK_H
#include <time.h>
#include <signal.h>
#define MAX_TIMERS 32
typedef void (*timer_cb_t)(void *data);
typedef struct {
timer_t timerid;
int active;
int id;
timer_cb_t callback;
void *user_data;
} tmr_entry_t;
typedef struct {
tmr_entry_t timers[MAX_TIMERS];
int count;
int next_id;
} tmr_manager_t;
int tmr_init(tmr_manager_t *mgr);
int tmr_add(tmr_manager_t *mgr, double delay, double interval,
timer_cb_t cb, void *data);
int tmr_remove(tmr_manager_t *mgr, int id);
void tmr_destroy(tmr_manager_t *mgr);
tmr_manager_t *tmr_get_manager(void);
#endif
框架实现
/* timer_framework.c */
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include "timer_framework.h"
#define TIMER_SIGNAL SIGRTMIN
static tmr_manager_t g_mgr;
static void timer_signal_handler(int sig, siginfo_t *si, void *uc) {
if (sig != TIMER_SIGNAL) return;
tmr_entry_t *e = (tmr_entry_t *)si->si_value.sival_ptr;
if (e && e->active && e->callback)
e->callback(e->user_data);
}
int tmr_init(tmr_manager_t *mgr) {
memset(mgr, 0, sizeof(*mgr));
mgr->next_id = 1;
struct sigaction sa = {0};
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sa.sa_sigaction = timer_signal_handler;
sigemptyset(&sa.sa_mask);
return sigaction(TIMER_SIGNAL, &sa, NULL);
}
int tmr_add(tmr_manager_t *mgr, double delay, double interval,
timer_cb_t cb, void *data) {
if (mgr->count >= MAX_TIMERS) return -1;
int slot = -1;
for (int i = 0; i < MAX_TIMERS; i++)
if (!mgr->timers[i].active) { slot = i; break; }
if (slot < 0) return -1;
tmr_entry_t *e = &mgr->timers[slot];
e->id = mgr->next_id++;
e->callback = cb;
e->user_data = data;
e->active = 1;
struct sigevent sev = {0};
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = TIMER_SIGNAL;
sev.sigev_value.sival_ptr = e;
if (timer_create(CLOCK_MONOTONIC, &sev, &e->timerid) == -1)
return -1;
struct itimerspec its;
its.it_value.tv_sec = (time_t)delay;
its.it_value.tv_nsec = (long)((delay - (time_t)delay) * 1e9);
its.it_interval.tv_sec = (time_t)interval;
its.it_interval.tv_nsec = (long)((interval - (time_t)interval) * 1e9);
if (interval <= 0) { its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; }
if (timer_settime(e->timerid, 0, &its, NULL) == -1) {
timer_delete(e->timerid);
return -1;
}
mgr->count++;
return e->id;
}
int tmr_remove(tmr_manager_t *mgr, int id) {
for (int i = 0; i < MAX_TIMERS; i++) {
if (mgr->timers[i].active && mgr->timers[i].id == id) {
timer_delete(mgr->timers[i].timerid);
mgr->timers[i].active = 0;
mgr->count--;
return 0;
}
}
return -1;
}
void tmr_destroy(tmr_manager_t *mgr) {
for (int i = 0; i < MAX_TIMERS; i++) {
if (mgr->timers[i].active) {
timer_delete(mgr->timers[i].timerid);
mgr->timers[i].active = 0;
}
}
mgr->count = 0;
}
tmr_manager_t *tmr_get_manager(void) { return &g_mgr; }
使用示例
#include <stdio.h>
#include <unistd.h>
#include "timer_framework.h"
static void heartbeat_cb(void *data) {
int *c = (int *)data;
(*c)++;
printf("[heartbeat] count=%d\n", *c);
}
static void timeout_cb(void *data) {
printf("[timeout] One-shot timer fired!\n");
}
int main(void) {
tmr_manager_t *mgr = tmr_get_manager();
tmr_init(mgr);
static int hb_count = 0;
/* 1秒间隔的周期性心跳定时器 */
int hb_id = tmr_add(mgr, 1.0, 1.0, heartbeat_cb, &hb_count);
/* 5秒单次超时定时器 */
int to_id = tmr_add(mgr, 5.0, 0.0, timeout_cb, NULL);
sleep(10);
tmr_remove(mgr, hb_id);
tmr_destroy(mgr);
printf("Total heartbeats: %d\n", hb_count);
return 0;
}
# 编译(需链接 rt 和 pthread 库)
gcc -o timer_demo timer_framework.c main.c -lrt -lpthread
./timer_demo
总结
- sleep族函数 - 最简单的定时方式,但阻塞线程且精度有限
- POSIX定时器 - 高精度、可扩展,支持信号和线程两种通知方式,推荐方案
- 信号驱动 - 使用实时信号避免丢失,信号处理函数中只能调用异步安全函数
- 线程通知 - SIGEV_THREAD更灵活但开销大,适合低频定时任务
- 精度控制 - CLOCK_MONOTONIC避免时间跳变,TIMER_ABSTIME避免累积漂移
- 多定时器框架 - 基于POSIX定时器和实时信号封装,通过sigev_value传递上下文
定时器编程是系统编程的重要技能。对于生产环境,推荐使用 CLOCK_MONOTONIC + TIMER_ABSTIME 组合确保精度和稳定性,注意信号安全和溢出处理。在需要与事件循环集成的场景中,可考虑 Linux 特有的 timerfd 机制,将定时器与 epoll 无缝结合。