在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 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

#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;
}

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_DISABLEDlocal_irq_disable();
#elselockdep_assert_irqs_disabled();
#endifaccount_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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919428.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919428.shtml
英文地址,请注明出处:http://en.pswp.cn/news/919428.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络聚合链路与软件网桥配置指南

网络聚合链路与软件网桥配置指南一、聚合链路&#xff08;Team&#xff09; 网络组队&#xff08;聚合链路&#xff09;是一种将多个网络接口控制器&#xff08;NIC&#xff0c;Network Interface Controller&#xff09;以逻辑方式组合在一起的技术&#xff0c;通过这种方式可…

IDE/去读懂STM32CubeMX 时钟配置图(有源/无源晶振、旁路/晶振模式、倍频/分频)

文章目录概述配置图元素说明RCCHSI/LSI/HSE/LSEAHB 和 APBSYSCLK 和 HCLKMux 多路复用器Prescaler 预分频器PLL 锁相环PLL 配置寄存器时钟物理源内部时钟和驱动无源晶振和驱动有源晶振和驱动MCO 时钟信号音频时钟配置晶体振荡器&#xff1f;外部时钟源类型RCC 如何选择旁路模式…

8 文本分析

全文检索与常规关系型数据库SQL查询的显著区别&#xff0c;就是全文检索具备对大段文本进行分析的能力&#xff0c;它可以通过文本分析把大段的文本切分为细粒度的分词。 elasticsearch在两种情况下会用到文本分析&#xff1a; 原始数据写入索引时&#xff0c;如果索引的某个字…

告别 Count Distinct 慢查询:StarRocks 高效去重全攻略

在大数据分析中&#xff0c;去重计算&#xff08;如 Count Distinct&#xff09;是一个常见但计算开销极高的操作&#xff0c;尤其在高基数和高并发场景下&#xff0c;常常成为查询性能的瓶颈。以用户访问行为为例&#xff0c;同一用户一天内多次访问页面时&#xff0c;PV 会累…

MVC、MVP、MVCC 和 MVI 架构的介绍及区别对比

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; ✨特色专栏&#xff1a; 知识分享 &#x1f96d;本…

【运维进阶】Ansible 角色管理

Ansible 角色管理 实验环境 [lthcontroller ~ 21:47:45]$ mkdir web && cd web[lthcontroller web 21:47:50]$ cat > ansible.cfg <<EOF [defaults] remote_user lth inventory ./inventory[privilege_escalation] become True become_user root become_m…

个人笔记SpringMVC

SpringMVC 1. 1.新建一个Maven-webapp项目 2.导入依赖pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.…

arcgis-提取范围中最大占比面积的信息或唯一值

此方法本来应用于计算图斑占最大面积的房屋质量等别/高程/坡度级别&#xff08;房屋质量等别/高程/耕地坡度计算在无特定条件下&#xff0c;遵循按面积占比最大值信息赋值&#xff09;。1、测试数据中&#xff0c;增加唯一值字段&#xff1a;WYZ&#xff0c;并刷上图斑唯一值信…

Webapi发布后IIS超时(.net8.0)

文章目录前言一、报错信息二、日志分析三、分析四、最终解决办法&#xff1a;前言 最近实现服务器数据导出&#xff1a; .net8.0的webapi 获取到post请求&#xff0c;查询数据后dbReader导出到workbook. 并保存Excel到远程的文件服务器。 问题&#xff1a;本地调试无问题&…

linux中的hostpath卷、nfs卷以及静态持久卷的区别

在 Linux 容器环境中&#xff0c;HostPath 卷、NFS 卷和静态持久卷&#xff08;Static PersistentVolume&#xff09;是数据持久化的重要方案。三者的核心差异体现在管理方式、适用场景、跨节点能力等方面。核心定义与工作原理1. HostPath 卷定义&#xff1a;直接将容器所在宿主…

Unity 中控开发 多路串口服务器(一)

一 Unity 中控开发 多路串口服务器 多路串口服务器基础型号配置被控投影设备LG-UART8 算法配置软件结果测试多路串口服务器 你好&#xff01; 这是关于一篇使用TCP调用多路串口服务器的系列文章。在后续文章中,会结合使用Unity做一个中控系统 基础 型号 ULEGS 多路串口服务…

服务器数据恢复—硬盘坏道离线导致raid崩溃的StorNext文件系统数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 一台昆腾存储设备中有一组raid5磁盘阵列。阵列上有两块硬盘先后离线&#xff0c;raid5磁盘阵列不可用。服务器存储数据恢复过程&#xff1a; 1、将故障服务器存储内的所有磁盘编号后取出&#xff0c;将所有没有离线的硬盘以只读方…

C++小游戏NO.1游戏机

#include<conio.h> #include<windows.h> #include<bits/stdc.h> #include<cstdlib> #include<ctime> #include<vector> #include<string> using namespace std; int Your6,Other6; string daojuname[]{"放大镜","sho…

OpenHarmony WebView引擎:从Chromium魔改到分布式渲染的终极解析

🏗️ 架构解析 arkweb是OpenHarmony webview组件的Native引擎,基于Chromium和CEF构建。 OpenHarmony WebView是基于Chromium CEF构建的高性能Web渲染引擎,为OpenHarmony系统提供完整的Web内容展示能力。该引擎采用分层架构设计,实现了与ArkUI的深度集成。 🏗️ 架构设…

Mybatis-3自己实现MyBatis底层机制

MyBatis整体架构分析一图胜千言1、Mybatis核心框架示意图2、对上图的解读1)mybatis的核配置文件mybatis-config.xml:进行全局配置&#xff0c;全局只能有一个这样的配置文件XxxMapper.xml配置多个SQL,可以有多个XxxMappe.xml配置文件 2)通过mybatis-config.xml配置文件得到SqlS…

Uniapp 之renderjs解决swiper+多个video卡顿问题

一、效果图二、示例代码 test.vue<template><view style"" :style"{height: windowHeightpx}"><swiper class"video-swiper" vertical change"swiperChange" :current"current" animationfinish"swiper…

设计模式之【快速通道模式】,享受VIP的待遇

文章目录一、快速通道模式简介1、简介2、适用场景二、示例1、JDK源码&#xff1a;ArrayList构造方法2、String.intern()方法3、缓存系统设计&#xff08;典型&#xff09;三、注意事项1、核心设计原则2、避坑指南参考资料一、快速通道模式简介 1、简介 快速通道模式是一种基于…

NineData云原生智能数据管理平台新功能发布|2025年7月版

本月发布 23 项更新&#xff0c;其中重点发布 8 项、功能优化 15 项。重点发布数据库 DevOps - 非表对象调试新增存储过程、函数、包的调试功能&#xff0c;支持对象编译、断点设置、执行控制&#xff08;continue/step into/step over&#xff09;、变量调试等全流程操作。数据…

APM32芯得 EP.29 | 基于APM32F103的USB键盘与虚拟串口复合设备配置详解

如遇开发技术问题&#xff0c;欢迎前往开发者社区&#xff0c;极海技术团队将在线为您解答~ 极海官方开发者社区​https://community.geehy.cn/ 《APM32芯得》系列内容为用户使用APM32系列产品的经验总结&#xff0c;均转载自21ic论坛极海半导体专区&#xff0c;全文未作任何修…

css过渡属性

前言 该属性用于元素各种 “改变” 后的过渡效果动画&#xff0c;包括但不限于颜色、宽高、缩放等。 如下图所示&#xff0c;使用过渡属性便可轻松完成。 示例代码 您可以直接复制运行&#xff0c;查看效果。 <div>demo</div>div {width:100px; height:100px;/* …