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) {
        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_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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值