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 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;/* …