linux用户态各定时器抖动测试
在打了实时补丁的linux系统上测试定时器的抖动,测试系统调度抖动最大在30μs左右。
定时器的抖动为相邻两次触发时间之差相对于定时器的周期的差值。
实际使用中建议使用posix ttimer或sleep timer。
1timefd
timerfd体现了linux中一切皆文件的思想。
空载情况下,定时器抖动最大达到300μs;加压情况下可达到6000μs。
#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 1000typedef 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
使用SIGEV_THREAD_ID触发方式,用户创建线程,定时器超时之后,回调函数在用户线程中执行。使用实时信号34,即SIGRTMIN。在空载和加压情况下,最大抖动比较稳定,稳定在60μs左右。实际测试时,使用普通信号,抖动情况和实时信号是相近的。
#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 nslong 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;
}
3rtc定时器
rtc定时器最大抖动与posix timer相近,但是这种使用方式有一个限制,即只能启动一个这样的定时器,启动第二个时会报device is busy。
#include <stdio.h>
#include <fcntl.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdint.h>
#include <pthread.h>#define RTC_DEVICE "/dev/rtc"
#define RTC_FREQ 1024
#define EXPECTED_INTERVAL_NS (1000000000/RTC_FREQ)// 全局统计结构
volatile struct {long long max_jitter; // 最大抖动(纳秒)long long min_jitter; // 最小抖动(纳秒)long long total_jitter; // 抖动累计值int count; // 有效样本数struct timespec prev_ts;// 上一次中断时间戳
} stats = { .min_jitter = 1000000};int main() {int fd = open(RTC_DEVICE, O_RDONLY);if (fd < 0) {perror("open");exit(EXIT_FAILURE);}// 设置RTC中断频率if (ioctl(fd, RTC_IRQP_SET, RTC_FREQ) < 0) {perror("ioctl(RTC_IRQP_SET)");close(fd);exit(EXIT_FAILURE);}// 启用周期性中断if (ioctl(fd, RTC_PIE_ON, 0) < 0) {perror("ioctl(RTC_PIE_ON)");close(fd);exit(EXIT_FAILURE);}// 初始化首次时间戳clock_gettime(CLOCK_MONOTONIC, &stats.prev_ts);unsigned long data;const int SAMPLE_COUNT = 10000;struct sched_param param;param.sched_priority = 30;pthread_t current_thread = pthread_self();pthread_setschedparam(current_thread, SCHED_FIFO, ¶m);while (stats.count < SAMPLE_COUNT) {if (read(fd, &data, sizeof(data)) < 0) {perror("read");break;}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);// 计算抖动 = |实际间隔 - 预期间隔|long long jitter = llabs(actual_interval - EXPECTED_INTERVAL_NS);// 更新统计stats.total_jitter += jitter;if (jitter > stats.max_jitter) stats.max_jitter = jitter;if (jitter < stats.min_jitter) stats.min_jitter = jitter;}stats.prev_ts = curr_ts;stats.count++;}// 关闭中断并清理ioctl(fd, RTC_PIE_OFF, 0);close(fd);// 输出统计结果printf("\n--- RTC Timer Jitter Statistics (Frequency: %d Hz) ---\n", RTC_FREQ);printf("Samples: %d\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 0;
}
4sleep timer
完全通过sleep来实现定时器,最大抖动20μs。比上边三种定时器的抖动都要小。
#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 1000typedef struct {uint64_t max_delta; // 最大偏差(μs)uint64_t min_delta; // 最小偏差(μs)uint64_t total_delta; // 偏差总和(μs)uint32_t count; // 实际采样计数
} TimerStats;void* timerfd_monitor_thread() {TimerStats stats = {0, UINT64_MAX, 0, 0};struct timespec prev_ts = {0, 0};struct sched_param param;param.sched_priority = 20;pthread_t current_thread = pthread_self();pthread_setschedparam(current_thread, SCHED_FIFO, ¶m);struct timespec req = {.tv_sec = 0,.tv_nsec = 10000000};clock_gettime(CLOCK_MONOTONIC,&prev_ts);while (stats.count < SAMPLE_COUNT) {int ret = clock_nanosleep(CLOCK_MONOTONIC, 0, &req, NULL);if (ret) {if (ret == EINTR) {printf("休眠被信号中断\n");} else {perror("clock_nanosleep 错误");}} else {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() {pthread_t monitor_thread;if (pthread_create(&monitor_thread, NULL, timerfd_monitor_thread, NULL)) {perror("pthread_create");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);return 0;
}