我试图描述Linux上的计时器抖动。我的任务是运行100毫秒计时器,看看数字是如何运作的。
我正在研究多核机器。我使用了setitimer()的标准用户程序,以root身份运行,然后使用处理器亲和性,最后使用处理器亲和性和进程优先级。然后我使用PREEMPT_RT内核运行相同的内容,然后使用clock_nanosleep()运行示例,如PREEMPT_RT页面上的演示代码中所示。在所有运行中,计时器性能非常相似,尽管有变化,但没有真正的差异。
我们的最终目标是稳定计时器。我经常得到的最好的最坏情况是大约200us。所有案例的直方图都显示出奇怪的行为。首先,我不希望计时器提前开火。但他们这样做。正如您在直方图中看到的那样,我在0偏移的任一侧得到了低谷。这些在第二个图中的三个波段中可见。在第一个图中,X轴以微秒为单位。在第二个图中,Y轴以微秒为单位。
我进行了30次测试(即300次计时器事件)100次以生成一些数字。您可以在下图中看到它们。 200us大幅下降。所有30000个定时器事件时钟偏移都在第二个图表中绘制,您可以在其中看到一些异常值。
所以问题是,之前有其他人做过这种分析吗?你有没有看到同样的行为?我的假设是RT内核可以帮助重载系统,但在我们的例子中,它无助于消除定时器抖动。这是你的经历吗?
这是代码。就像我之前说过的,我修改了使用clock_nanosleep()函数的PREEMPT_RT站点上的示例代码,因此我不会包含我的最小更改。
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <stdlib.h>
#define US_PER_SEC 1000000
#define WAIT_TIME 100000
#define MAX_COUNTER 300
int counter = 0;
long long last_time = 0;
static long long times[MAX_COUNTER];
int i = 0;
struct sigaction sa;
void timer_handler(int signum)
{
if (counter > MAX_COUNTER)
{
sigaction(SIGALRM, &sa, NULL);
for (i = 0; i < MAX_COUNTER; i++)
{
printf("%ld\n", times[i]);
}
exit(EXIT_SUCCESS);
}
struct timeval t;
gettimeofday(&t, NULL);
long long elapsed = (t.tv_sec * US_PER_SEC + t.tv_usec);
if (last_time != 0)
{
times[counter] = elapsed - last_time;
++counter;
}
last_time = elapsed;
}
int main()
{
struct itimerval timer;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &timer_handler;
sigaction(SIGALRM, &sa, NULL);
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 1;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = WAIT_TIME;
setitimer(ITIMER_REAL, &timer, NULL);
while (1)
{
sleep(1);
}
}
编辑:这是在Xeon E31220L上运行,运行速度为2.2 GHz,运行x86_64 Fedora Core 19。
答案 0 :(得分:2)
你没有想到计时器会提前开火 - 但他们没有。 明显的提前解雇是因为您没有测量自上一个计时器到期以来的时间 - 您正在测量自上一次gettimeofday()
呼叫以来的时间。如果计时器到期与实际进行计划的过程之间存在延迟,那么您将看到此gettimeofday()
运行较晚,并且下一个运行时间相同。
不是记录后续gettimeofday()
次调用之间的差异,而是尝试记录返回的绝对时间,然后将返回的时间与初始时间后的N * 100ms进行比较。
如果您希望PREEMPT_RT
为您提供帮助,则需要为您的测试程序(SCHED_FIFO
或SCHED_RR
)设置实时调度程序策略,该策略需要root。
答案 1 :(得分:0)
我对您的代码进行了一些更改,主要按照以下方式替换了timer
,并以RT进度(SCHED_FIFO)的方式运行了该过程。
setitimer() -> timer_create()/timer_settime()
gettimeofday() -> clock_gettime()
我的测试平台是i9-9900k CPU和带有5.0.21内核的PREEMPT-RT修补Linux。计时器的时间间隔为1毫秒,程序运行约10个小时以产生以下结果。
我还在机器上运行Cyclictest
(基于nanosleep()
),它显示出更好的延迟控制(最大延迟小于15us)。因此,我认为,如果您想自己实现一个高分辨率计时器,那么在隔离内核上运行nanosleep的独立RT线程可能会有所帮助。我是RT系统的新手,欢迎发表评论。