寻找Linux内核模块中意外抢占的原因

Eva*_*van 14 c real-time smp linux-kernel

我有一个小的Linux内核模块,它是用于尚不存在的硬件的设备驱动程序的原型.代码需要从开始到结束尽可能快地执行一小段计算,持续时间为几微秒.我试图用intel rdtscp指令测量是否可以使用ndelay()模拟计算的调用.我发现它有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;
}
Run Code Online (Sandbox Code Playgroud)

如果我跑:

# cat /proc/timing_test
min: 4176 max: 58248 above30k: 56
Average total ticks/iteration: 4365
Run Code Online (Sandbox Code Playgroud)

这是在3.4 GHz沙桥生成Core i7上.TSC的~4200刻度大约适用于1微秒以上的延迟.大约0.1%的时间我看到延迟比预期长10倍,在某些情况下我看到的时间长达120,000个.

这些延迟似乎太长而不能成为单个高速缓存未命中,即使对于DRAM也是如此 所以我认为它要么是几个缓存未命中,要么是在我的关键部分中间抢占CPU的另一个任务.我想了解可能的原因,看看它们是否是我们可以消除的东西,或者我们是否必须转向自定义处理器/ FPGA解决方案.

我尝试过的事情:

  • 我考虑过这是否可能是由缓存未命中引起的.我不认为可能是这种情况,因为我忽略了应该加载缓存的前几次迭代.我通过检查反汇编验证了两次调用rdtscp之间没有内存操作,所以我认为唯一可能的缓存未命中是指令缓存.
  • 为了以防万一,我在外部循环中移动了spin_lock调用.然后在第一次迭代后不应该有任何缓存未命中.然而,这使问题变得更糟.
  • 我听说SMM中断是不可屏蔽的,大部分是透明的,可能导致不必要的抢占.但是,您可以使用rdmsron 读取SMI中断计数MSR_SMI_COUNT.我尝试在之前和之后添加,并且在我的代码执行时没有发生SMM中断.
  • 据我所知,SMP系统中也存在可能会中断的处理器间中断,但我查看了之前和之后的/ proc/interrupts,并且没有看到足够的中断来解释这种行为.
  • 我不知道是否ndelay()考虑了可变时钟速度,但我认为CPU时钟只变化2倍,所以这不应该导致> 10倍的变化.
  • 我使用nopti启动以禁用页表隔离,以防出现问题.

Gri*_*tov 0

您在极端情况下观察到的 120,000 个刻度听起来很像 SMM 处理程序。较小的值可能是由各种微体系结构事件引起的(顺便说一句,您检查了所有可用的性能计数器吗?),但这一定是由没有编写他的/的人编写的子例程引起的她的代码实现了最小延迟。

但是您声明您已检查没有观察到 SMI。这让我认为要么是内核设施有问题来计数/报告它们,要么是您的管理方法有问题。在没有硬件调试器的情况下寻找 SMI 可能是一项令人沮丧的工作。

  • SMI_COUNT 在您的实验过程中没有变化,还是一直为零?后者可能表明它没有任何意义,除非您的系统完全没有 SMI,我对常规 Sandy Bridge 的情况表示怀疑。
  • SMI 可能被传递到系统中的另一个核心,并且 SMM 处理程序正在通过某种未显示在 SMI_COUNT 上的机制同步其他核心。你检查过其他核心吗?
  • 一般来说,我建议开始缩小被测系统的规模,以排除尽可能多的东西。您是否尝试过使用单核启动并且 BIOS 中未启用超线程?您是否尝试过在已知没有 SMI 的系统上运行相同的代码?在 BIOS 中禁用睿频加速和频率调节也是如此。尽可能多的与时间相关的内容必须去掉。