在linux中开发引用,timerfd和posix timer是最常用的定时器。timerfd是linux特有的定时器,通过fd来实现定时器,体现了linux"一切皆文件"的思想;posix timer,只要符合posix标准的操作系统,均应支持。
在开发确定性应用时,需要选用确定性的定时器。搜索网络上的一些资料,往往说这两种定时器的精度可以达到纳秒级。但是在实际测试中,尤其是在压测时,即使linux打了实时补丁,两种定时器的抖动也会比较大,可以达到700μs,甚至更大。本文分析在压力情况下,timerfd和posix timer抖动大的原因。
1timerfd
#include <sys/timerfd.h>
#include <pthread.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#define NS_PER_US 1000
#define US_PER_SEC 1000000
#define TIMER_INTERVAL_US 10000
#define SAMPLE_COUNT 1000
typedef struct {
uint64_t max_delta; // 最大偏差(μs)
uint64_t min_delta; // 最小偏差(μs)
uint64_t total_delta; // 偏差总和(μs)
uint32_t count; // 实际采样计数
} TimerStats;
void* timerfd_monitor_thread(void* arg) {
int timer_fd = *(int*)arg;
TimerStats stats = {0, UINT64_MAX, 0, 0};
struct timespec prev_ts = {0, 0};
uint64_t expirations;
struct sched_param param;
param.sched_priority = 30;
pthread_t current_thread = pthread_self();
pthread_setschedparam(current_thread, SCHED_FIFO, ¶m);
// 第一次读取(建立基准)
if (read(timer_fd, &expirations, sizeof(expirations)) > 0) {
clock_gettime(CLOCK_MONOTONIC, &prev_ts);
}
while (stats.count < SAMPLE_COUNT) {
int ret = read(timer_fd, &expirations, sizeof(expirations));
if (ret > 0) {
struct timespec curr_ts;
clock_gettime(CLOCK_MONOTONIC, &curr_ts);
uint64_t interval_us = (curr_ts.tv_sec - prev_ts.tv_sec) * US_PER_SEC +
(curr_ts.tv_nsec - prev_ts.tv_nsec) / NS_PER_US;
if (prev_ts.tv_sec != 0) {
int64_t delta = (int64_t)interval_us - TIMER_INTERVAL_US;
uint64_t abs_delta = llabs(delta);
if (abs_delta > stats.max_delta) stats.max_delta = abs_delta;
if (abs_delta < stats.min_delta) stats.min_delta = abs_delta;
stats.total_delta += abs_delta;
stats.count++;
}
prev_ts = curr_ts;
}
}
TimerStats* result = (TimerStats *)malloc(sizeof(TimerStats));
memcpy(result, &stats, sizeof(TimerStats));
return result;
}
int main() {
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timer_fd == -1) {
perror("timerfd_create");
exit(EXIT_FAILURE);
}
struct itimerspec timer_spec = {
.it_interval = {.tv_sec = 0, .tv_nsec = TIMER_INTERVAL_US * NS_PER_US},
.it_value = {.tv_sec = 0, .tv_nsec = 1}
};
if (timerfd_settime(timer_fd, 0, &timer_spec, NULL) == -1) {
perror("timerfd_settime");
close(timer_fd);
exit(EXIT_FAILURE);
}
pthread_t monitor_thread;
if (pthread_create(&monitor_thread, NULL, timerfd_monitor_thread, &timer_fd)) {
perror("pthread_create");
close(timer_fd);
exit(EXIT_FAILURE);
}
TimerStats* stats;
pthread_join(monitor_thread, (void**)&stats);
printf("max: %" PRIu64 "\n", stats->max_delta);
printf("min: %" PRIu64 "\n", stats->min_delta);
printf("avg: %.2f\n", (double)stats->total_delta / stats->count);
free(stats);
close(timer_fd);
return 0;
}
2posix timer
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <math.h>
#define _GNU_SOURCE
#include <sys/types.h>
// 全局统计数据结构
volatile struct {
long long max_jitter; // 最大抖动(纳秒)
long long min_jitter; // 最小抖动(纳秒)
long long total_jitter; // 抖动累计值(纳秒)
long long count; // 触发次数统计
struct timespec prev_ts; // 上一次触发时间戳
} stats = { .min_jitter = 100000 };
// 信号处理函数
void sig_handler(int a, siginfo_t *b, void *c) {
struct timespec curr_ts;
clock_gettime(CLOCK_MONOTONIC, &curr_ts);
// 第一次触发时只记录时间戳不计算抖动
if (stats.count > 0) {
// 计算实际时间差(纳秒)
long long actual_interval = (curr_ts.tv_sec - stats.prev_ts.tv_sec) * 1000000000LL
+ (curr_ts.tv_nsec - stats.prev_ts.tv_nsec);
// 计算抖动(实际间隔 - 设定周期10ms)
const long long expected_interval = 10 * 1000000LL; // 10ms in ns
long long jitter = actual_interval - expected_interval;
// 更新统计值
stats.total_jitter += llabs(jitter);
if (llabs(jitter) > stats.max_jitter) stats.max_jitter = llabs(jitter);
if (llabs(jitter) < stats.min_jitter) stats.min_jitter = llabs(jitter);
}
// 更新状态
stats.prev_ts = curr_ts;
stats.count++;
}
void create_timer(int signum, int period_in_ms, void (*cb)(int, siginfo_t*, void*), timer_t* timerid) {
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = cb;
sigemptyset(&sa.sa_mask);
sigaction(signum, &sa, NULL);
sigevent_t event;
event.sigev_notify = SIGEV_THREAD_ID;
event.sigev_signo = signum;
event._sigev_un._tid = syscall(SYS_gettid);
event.sigev_value.sival_ptr = NULL;
timer_create(CLOCK_MONOTONIC, &event, timerid);
struct itimerspec its;
its.it_interval.tv_sec = period_in_ms / 1000;
its.it_interval.tv_nsec = (period_in_ms % 1000) * 1000000;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 1; // 立即启动
timer_settime(*timerid, 0, &its, NULL);
}
void* thread_func(void* arg) {
struct sched_param param;
param.sched_priority = 30;
pthread_t current_thread = pthread_self();
pthread_setschedparam(current_thread, SCHED_FIFO, ¶m);
timer_t timerid;
create_timer(34, 10, sig_handler, &timerid);
while(stats.count < 1000) {
usleep(20000);
}
// 取消定时器
timer_delete(timerid);
// 打印统计结果
printf("\n--- Timer Jitter Statistics ---\n");
printf("Samples collected: %lld\n", stats.count - 1); // 有效样本数
printf("Max jitter: %lld ns (%.3f ms)\n", stats.max_jitter, stats.max_jitter / 1000000.0);
printf("Min jitter: %lld ns\n", stats.min_jitter);
printf("Avg jitter: %.2f ns (%.3f ms)\n",
(double)stats.total_jitter / (stats.count - 1),
(double)stats.total_jitter / (stats.count - 1) / 1000000.0);
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL);
return 0;
}
3抖动大的原因
3.1timerfd和posix timer均通过内核的hrtimer来实现
timerfd:
SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags)
{
...
hrtimer_setup(&ctx->t.tmr, timerfd_tmrproc, clockid, HRTIMER_MODE_ABS);
...
return ufd;
}
posix timer:
posix timer创建定时器,最终会调用函数common_timer_create来创建。
static int common_timer_create(struct k_itimer *new_timer)
{
hrtimer_setup(&new_timer->it.real.timer, posix_timer_fn, new_timer->it_clock, 0);
return 0;
}
3.2hrtimer通过软中断来处理
在函数raise_timer_softirq中唤醒软中断处理线程。而软中断处理线程的优先级是默认优先级,即SCHED_OTHER,nice值为0,这样在cpu加压情况下,软中断处理线程的抖动就是完全不可预期的,进而引起定时器抖动。
软中断处理线程中不仅仅是处理HRTIMER这一种软中断,还有NET_RX、NET_TX等,会进一步加剧抖动。
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 CPU8 CPU9 CPU10 CPU11
HI: 0 0 0 2 0 0 0 0 0 0 0 0
TIMER: 2923684 1037158 2312782 15780522 2292706 12511455 2021456 1911912 2777271 2135993 1 1
NET_TX: 1 1 1 3 3 1 0 3 4 0 0 0
NET_RX: 171182484 572625 467634 576342 322601 338762 298284 354496 223455 165924 0 0
BLOCK: 0 0 0 0 0 0 0 0 0 0 0 0
IRQ_POLL: 0 0 0 0 0 0 0 0 0 0 0 0
TASKLET: 124653 97476 70810 143113 50785 83217 65544 75319 20208 15287 0 0
SCHED: 80111744 12500903 3502611 13362108 2612070 10030360 2319687 2160967 2852872 2370172 0 0
HRTIMER: 7245889 3010058 2493355 3392701 1950261 1208348 1013954 1105595 1078221 983678 0 0
RCU: 0 0 0 0 0 0 0 0 0 0 0 0
void hrtimer_interrupt(struct clock_event_device *dev)
{
...
if (!ktime_before(now, cpu_base->softirq_expires_next)) {
cpu_base->softirq_expires_next = KTIME_MAX;
cpu_base->softirq_activated = 1;
raise_timer_softirq(HRTIMER_SOFTIRQ);
}
....
}
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();
}
3.3ktimers线程
在linux 6.x中引入了ktimers线程,引入该线程的目的是将HRTIMR这一软中断从softirq中隔离出来。使用专门的线程来处理hrtimer软中断。ktimers线程的调度策略是SCHED_FIFO实时调度策略,优先级是1。使用专门的线程来处理HRTIMER软中断,并且该线程还是实时调度策略,在很大程度上降低了定时器的抖动。
#ifdef CONFIG_IRQ_FORCED_THREADING
static void ktimerd_setup(unsigned int cpu)
{
/* Above SCHED_NORMAL to handle timers before regular tasks. */
sched_set_fifo_low(current);
}
static int ktimerd_should_run(unsigned int cpu)
{
return local_timers_pending_force_th();
}
void raise_ktimers_thread(unsigned int nr)
{
trace_softirq_raise(nr);
__this_cpu_or(pending_timer_softirq, BIT(nr));
}
static void run_ktimerd(unsigned int cpu)
{
unsigned int timer_si;
ksoftirqd_run_begin();
timer_si = local_timers_pending_force_th();
__this_cpu_write(pending_timer_softirq, 0);
or_softirq_pending(timer_si);
__do_softirq();
ksoftirqd_run_end();
}
static struct smp_hotplug_thread timer_thread = {
.store = &ktimerd,
.setup = ktimerd_setup,
.thread_should_run = ktimerd_should_run,
.thread_fn = run_ktimerd,
.thread_comm = "ktimers/%u",
};
#endif
如果条件满足,则唤醒ktimers线程:
static inline void raise_timer_softirq(unsigned int nr)
{
lockdep_assert_in_irq();
if (force_irqthreads())
raise_ktimers_thread(nr);
else
__raise_softirq_irqoff(nr);
}
在开启实时内核,并且打开配置CONFIG_IRQ_FORCED_THREADING的条件下,才会启用ktimers线程。
#ifdef CONFIG_IRQ_FORCED_THREADING
# ifdef CONFIG_PREEMPT_RT
# define force_irqthreads() (true)
...
在中断返回函数里,会直接唤醒ktimers线程。
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
wake_timersd();
tick_irq_exit();
}