我有一个小的Linux内核模块,它是用于尚未存在的硬件的设备驱动程序的原型。代码需要从开始到结束尽可能快地执行一小段计算,持续时间为几微秒。我试图通过使用ndelay()
调用来模拟计算的intel rdtscp指令来测量这是否可行。我发现它有99.9%的时间按预期运行,但0.1%的时间它有一个非常大的延迟,好像其他东西正在抢占代码,尽管在一个应该禁用中断的自旋锁内运行。这是使用库存Ubuntu 64位内核(4.4.0-112)运行的,没有额外的实时或低延迟补丁。
以下是一些复制此行为的示例代码。这是作为/proc
文件系统条目的处理程序编写的,以便于测试,但我只显示了实际计算延迟的函数:
#define ITERATIONS 50000
#define SKIPITER 10
DEFINE_SPINLOCK(timer_lock);
static int timing_test_show(struct seq_file *m, void *v)
{
uint64_t i;
uint64_t first, start, stop, delta, max=0, min=1000000;
uint64_t avg_ticks;
uint32_t a, d, c;
unsigned long flags;
int above30k=0;
__asm__ volatile ("rdtscp" : "=a" (a), "=d" (d) : : "rcx");
first = a | (((uint64_t)d)<<32);
for (i=0; i<ITERATIONS; i++) {
spin_lock_irqsave(&timer_lock, flags);
__asm__ volatile ("rdtscp" : "=a" (a), "=d" (d) : : "rcx");
start = a | (((uint64_t)d)<<32);
ndelay(1000);
__asm__ volatile ("rdtscp" : "=a" (a), "=d" (d) : : "rcx");
stop = a | (((uint64_t)d)<<32);
spin_unlock_irqrestore(&timer_lock, flags);
if (i < SKIPITER) continue;
delta = stop-start;
if (delta < min) min = delta;
if (delta > max) max = delta;
if (delta > 30000) above30k++;
}
seq_printf(m, "min: %llu max: %llu above30k: %d\n", min, max, above30k);
avg_ticks = (stop - first) / ITERATIONS;
seq_printf(m, "Average total ticks/iteration: %llu\n", avg_ticks);
return 0;
}
然后如果我跑:
# cat /proc/timing_test
min: 4176 max: 58248 above30k: 56
Average total ticks/iteration: 4365
这是在3.4 GHz沙桥生成Core i7上。 TSC的~4200刻度大约适用于1微秒以上的延迟。大约0.1%的时间我看到延迟比预期长10倍,在某些情况下我看到的时间长达120,000滴。
这些延迟似乎太长而不能成为单个缓存未命中,即使对于DRAM也是如此。所以我认为它要么是几个缓存未命中,要么是在我的关键部分中间抢占CPU的另一个任务。我想了解可能的原因,看看它们是否是我们可以消除的,或者我们是否必须转向自定义处理器/ FPGA解决方案。
我尝试过的事情:
rdmsr
上使用MSR_SMI_COUNT
读取SMI中断计数。我尝试在之前和之后添加它,并且在我的代码执行时没有发生SMM中断。ndelay()
是否考虑了可变时钟速度,但我认为CPU时钟仅变化2倍,因此这不会导致> 10倍的变化。答案 0 :(得分:1)
我刚刚注意到的另一件事是,不清楚ndelay()
的作用。也许你应该把它展示出来,因为其中潜藏着非平凡的问题。
例如,我曾经观察到,当我的内核驱动程序代码内部有内存泄漏时,它仍被抢占,所以只要它达到某个水印限制,即使它禁用了中断,它也被置于一边
答案 1 :(得分:0)
在极端情况下您观察到的120,000个滴答声听起来很像SMM处理程序。较小的值可能是由各种各样的微架构事件引起的(顺便说一句,你有没有检查过你可以使用的所有性能计数器?),但这必须是由一个没有编写他/他的人编写的子程序引起的。她的代码实现了最小的延迟。
但是,您表示您已检查过没有观察到SMI。这使我认为内核工具要么计算/报告它们,要么用你的方法来管理它们。没有硬件调试器的SMI之后的狩猎可能是令人沮丧的努力。
答案 2 :(得分:0)
仅供参考,在我的系统中:
timingtest % uname -a
Linux xxxxxx 4.15.0-42-generic #45-Ubuntu SMP Thu Nov 15 19:32:57 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
复制您的示例(使用ndelay(1000);),我得到:
timingtest % sudo cat /proc/timing_test
min: 3783 max: 66883 above30k: 20
Average total ticks/iteration: 4005
timingtest % sudo cat /proc/timing_test
min: 3783 max: 64282 above30k: 19
Average total ticks/iteration: 4010
复制您的示例(使用udelay(1);),我得到:
timingtest % sudo cat /proc/timing_test
min: 3308 max: 43301 above30k: 2
Average total ticks/iteration: 3611
timingtest % sudo cat /proc/timing_test
min: 3303 max: 44244 above30k: 2
Average total ticks/iteration: 3600
ndelay(),udelay(),mdelay()用于原子上下文,如下所示: https://www.kernel.org/doc/Documentation/timers/timers-howto.txt 它们都依赖于__const_udelay()函数,该函数是vmlinux导出的符号(使用:LFENCE / RDTSC指令)。
无论如何,我将延迟替换为:
for (delta=0,c=0; delta<500; delta++) {c++; c|=(c<<24); c&=~(c<<16);}
一个简单的忙循环,结果相同。
我还尝试了_cli()/ _ sti(),local_bh_disable()/ local_bh_enable()和preempt_disable()/ preempt_enable()失败。
Examinig SMM中断(延迟之前和之后)具有:
__asm__ volatile ("rdmsr" : "=a" (a), "=d" (d) : "c"(0x34) : );
smi_after = (a | (((uint64_t)d)<<32));
我总是获得相同的号码(没有SMI或注册未更新)。
使用trace-cmd执行cat命令来探索正在发生的事情,我得到的结果令人惊讶地没有及时分散。 (!?)
timingtest % sudo trace-cmd record -o trace.dat -p function_graph cat /proc/timing_test
plugin 'function_graph'
min: 3559 max: 4161 above30k: 0
Average total ticks/iteration: 5863
...
在我的系统中,可以使用电源管理服务质量来解决问题,请参阅(https://access.redhat.com/articles/65410)。希望这会有所帮助