You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kernel 5.xx中gettimeofday()秒内时间更新异常及PCAP时间戳问题

问题

我在循环读取PCAP数据包并为其添加时间戳时遇到异常:在2.6内核(如CentOS 6.8)上运行正常,但在5.15内核(如OEL 8)中,1秒内时间变化几乎不超过100µsec,且秒数切换时微秒值会大幅跳变。这是否与“NO_HZ: Reducing Scheduling-Clock Ticks”特性有关?该程序在CentOS 6.8上编译运行正常,但在OEL 7或8上异常。

代码片段

while ((prec = pcap_next_ex(cap, &header, &pkt_data)) >= 0) {
    // more code

    // Time in msec
    struct timeval tv;
    time_t sec_midnight = time(NULL) % 86400;
    if (gettimeofday(&tv, NULL)) {
        printf("ERROR\n");
    }
    double usec = (double) tv.tv_usec;
    double frametime = (double) sec_midnight;
    frametime = frametime + usec / 1000000.0;

    struct timespec ts;
    clock_gettime(CLOCK_REALTIME_COARSE, &ts);
    printf("Frametime = %f %f %i\n", frametime, usec, ts.tv_nsec);
}

程序输出

Output of the program:

Frametime = 31139.900089 900089.000000 899621443
Frametime = 31139.900145 900145.000000 899621443
Frametime = 31139.900200 900200.000000 899621443
Frametime = 31139.900256 900256.000000 899621443
Frametime = 31139.900311 900311.000000 899621443
Frametime = 31139.900366 900366.000000 899621443
Frametime = 31139.900422 900422.000000 899621443
Frametime = 31139.900477 900477.000000 899621443
Frametime = 31139.900533 900533.000000 899621443
            ==> When the second changes, the usec changes with a big step.    
Frametime = 31140.543455 543455.000000 542621443
Frametime = 31140.543519 543519.000000 542621443
Frametime = 31140.543575 543575.000000 542621443
Frametime = 31140.543630 543630.000000 543621443
Frametime = 31140.543656 543656.000000 543621443
Frametime = 31140.543712 543712.000000 543621443
Frametime = 31140.543768 543768.000000 543621443
Frametime = 31140.543823 543823.000000 543621443
Frametime = 31140.543878 543878.000000 543621443

解答

问题根源

代码存在核心错误:time(NULL)与gettimeofday()的调用非原子操作,两者存在时间差。在新内核中,NO_HZ特性导致time(NULL)的更新频率降低,很容易出现time(NULL)返回旧秒数,但gettimeofday()已获取新秒数的微秒值,拼接后生成错误时间戳——这就是输出中秒数切换时微秒跳变的原因。

与NO_HZ的关系

是,NO_HZ是问题在新内核中暴露的直接原因:

  • 2.6内核(CentOS 6.8)默认未启用NO_HZ,系统保持固定时钟滴答(通常1000Hz),time(NULL)更新频繁,两次调用间秒数切换概率极低,问题不易显现。
  • 5.15内核(OEL 8)默认启用NO_HZ_FULL(完全无滴答),time(NULL)不再依赖固定时钟中断,仅在必要时更新,大幅增加了两次调用间秒数不一致的概率,导致问题凸显。

另外,输出中1秒内时间变化小,是因为你使用了CLOCK_REALTIME_COARSE,它为性能牺牲精度,时间更新间隔可达100ms。

修复方案

方案1:从同一时间源获取完整时间

不要拆分调用time()和gettimeofday(),改用单源原子获取:

while ((prec = pcap_next_ex(cap, &header, &pkt_data)) >= 0) {
    // more code

    struct timeval tv;
    if (gettimeofday(&tv, NULL)) {
        printf("ERROR\n");
        continue;
    }
    // 计算距午夜的秒数
    struct tm local_tm;
    localtime_r(&tv.tv_sec, &local_tm);
    time_t sec_midnight = local_tm.tm_hour * 3600 + local_tm.tm_min * 60 + local_tm.tm_sec;
    
    double frametime = (double)sec_midnight + (double)tv.tv_usec / 1000000.0;

    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts); // 替换COARSE获取高精度时间
    printf("Frametime = %f %ld %ld\n", frametime, tv.tv_usec, ts.tv_nsec);
}

方案2:直接使用PCAP自带的时间戳

PCAP的pcap_pkthdr已包含数据包捕获时的时间戳,无需自行调用系统时间,更可靠:

while ((prec = pcap_next_ex(cap, &header, &pkt_data)) >= 0) {
    // more code

    // 使用PCAP内置时间戳
    struct timeval *tv = &header->ts;
    struct tm local_tm;
    localtime_r(&tv->tv_sec, &local_tm);
    time_t sec_midnight = local_tm.tm_hour * 3600 + local_tm.tm_min * 60 + local_tm.tm_sec;
    
    double frametime = (double)sec_midnight + (double)tv->tv_usec / 1000000.0;

    printf("Frametime = %f %ld\n", frametime, tv->tv_usec);
}

额外建议

  • 用CLOCK_REALTIME替代CLOCK_REALTIME_COARSE,以获取高精度时间。
  • 用线程安全的localtime_r()替代localtime(),避免多线程环境下的异常。

内容的提问来源于stack exchange,提问作者Wim69

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 03:14:54