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
相关产品推荐
相关产品推荐

