当前位置: 首页 > news >正文

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, &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

使用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, &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;
}

 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, &param);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, &param);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;
}

 

http://www.xdnf.cn/news/1167463.html

相关文章:

  • 操作符练习
  • 【Linux内核模块】模块声明与描述
  • nginx使用手册
  • 在easyui中如何自定义表格里面的内容
  • MCU中的总线桥是什么?
  • 分布在内侧内嗅皮层(MEC)的边界细胞对NLP中的深层语义分析的积极影响和启示
  • 深入浅出理解 TCP 与 UDP:网络传输协议的核心差异与应用
  • JMeter groovy 编译成.jar 文件
  • oracle里面concat函数用法,oracle wm_concat函数用法-
  • python学习-读取csv大文件
  • Apache Ignite实现无死锁特性
  • PHP与Web页面交互:从基础表单到AJAX实战
  • k8s:利用helm离线部署consul v1.21.2
  • 【菜狗学聚类】时间序列聚类主要方法—20250722
  • web3.0怎么入局
  • PePeOnTron上线 Binance Alpha:中文社区正走出自己的Web3之路
  • 内核协议栈源码阅读(一) ---驱动与内核交互
  • 进程优先级切换调度-进程概念(6)
  • Taro 网络 API 详解与实用案例
  • SecretFlow (3) --- 添加合作方并创建项目
  • JavaScript,发生异常,try...catch...finally处理,继续向上层调用者传递异常信息
  • RabbitMQ03——面试题
  • uniapp各大平台导航组件
  • 在 Ubuntu 22.04 上安装并优化 Nginx nginx入门操作 稍难,需要有一定理论 多理解 多实践
  • 《Uniapp-Vue 3-TS 实战开发》自定义时间选择
  • Kafka基础理论速通
  • IDEA全局Maven配置
  • 比特币技术简史 第六章:网络协议 - P2P网络、节点类型与消息传播
  • 未来趋势:LeafletJS 与 Web3/AI 的融合
  • Visual Studio Code 远端云服务器开发使用指南