linux下timerfd和posix timer为什么存在较大的抖动?
在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, param); // 第一次读取(建立基准) if (read(timer_fd, expirations, sizeof(expirations)) 0) { clock_gettime(CLOCK_MONOTONIC, prev_ts); } while (stats.count SAMPLE_COUNT) { //expirations是定时器超时的次数 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, param); 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_OTHERnice值为0这样在cpu加压情况下软中断处理线程的抖动就是完全不可预期的进而引起定时器抖动。软中断处理线程中不仅仅是处理HRTIMER这一种软中断还有NET_RX、NET_TX等会进一步加剧抖动。CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 CPU8 CPU9 CPU10 CPU11HI: 0 0 0 2 0 0 0 0 0 0 0 0TIMER: 2923684 1037158 2312782 15780522 2292706 12511455 2021456 1911912 2777271 2135993 1 1NET_TX: 1 1 1 3 3 1 0 3 4 0 0 0NET_RX: 171182484 572625 467634 576342 322601 338762 298284 354496 223455 165924 0 0BLOCK: 0 0 0 0 0 0 0 0 0 0 0 0IRQ_POLL: 0 0 0 0 0 0 0 0 0 0 0 0TASKLET: 124653 97476 70810 143113 50785 83217 65544 75319 20208 15287 0 0SCHED: 80111744 12500903 3502611 13362108 2612070 10030360 2319687 2160967 2852872 2370172 0 0HRTIMER: 7245889 3010058 2493355 3392701 1950261 1208348 1013954 1105595 1078221 983678 0 0RCU: 0 0 0 0 0 0 0 0 0 0 0 0void 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 were in an interrupt or softirq, were 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(); }