mwait x86指令不等待DMA

Igo*_* R. 10 x86 linux-kernel dma

我正在尝试使用monitor/ mwait指令监视从设备到内存位置的DMA写入.在内核模块(char设备)中,我有一个在内核线程中运行的以下代码(非常类似于这段内核代码):

static int do_monitor(void *arg)
{
  struct page *p = arg; // p is a 'struct page *'; it's also remapped to user space
  uint32_t *location_p = phys_to_virt(page_to_phys(p)); 
  uint32_t prev = 0;
  int i = 0;
  while (i++ < 20) // to avoid infinite loop
  {
    if (*location_p == prev)
    {
        __monitor(location_p, 0, 0);
        if (this_cpu_has(X86_FEATURE_CLFLUSH_MONITOR))
          clflush(location_p);
        if (*location_p == prev)
          __mwait(0, 0);
    }
    prev = *location_p;
    printk(KERN_NOTICE "%d", prev);
  }
}
Run Code Online (Sandbox Code Playgroud)

在用户空间中,我有以下测试代码:

int fd = open("/dev/mon_test_dev", O_RDWR);
unsigned char *mapped = (unsigned char *)mmap(0, mmap_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
for (int i = 1; i <= 5; ++i)
  *mapped = i;
munmap(mapped, mmap_size);
close(fd);
Run Code Online (Sandbox Code Playgroud)

内核日志如下所示:

1
2
3
4
5
5
5
5
5
5
5 5 5 5 5 5 5 5 5 5
Run Code Online (Sandbox Code Playgroud)

即它似乎mwait不等待.可能是什么原因?

Gri*_*tov 5

MONITOR/MWAIT语义的定义没有明确指定DMA事务是否可以触发它.假设逻辑处理器的存储发生了触发.

英特尔官方软件开发人员手册中对MONITOR和MWAIT的最新描述对此非常模糊.但是,MONITOR部分有两条引起我注意的条款:

  1. EAX的内容是一个有效的地址(在64位模式下,使用RAX).默认情况下,DS段用于创建受监控的线性地址.

  2. 地址范围必须使用回写类型的存储器.只有回写存储器才能正确触发监控硬件.

第一个条款规定MONITOR应与线性地址一起使用,而不是物理地址.设备及其DMA仅适用于物理地址.所以基本上这意味着所有依赖于相同MONITOR范围的代理应该在虚拟内存空间的相同域中运行.

第二个子句要求受监视的内存区域可缓存(回写,WB).对于DMA,通常必须将相应的存储器范围标记为不可缓存,或者最好是写入组合(UC或WC).这甚至是一个更强有力的指标,表明您使用DMA触发MONITOR/MWAIT的意图不太可能在当前硬件上运行.


考虑到你的高级目标 - 能够告诉设备何时写入给定的内存范围 - 我不记得任何强大的方法来实现它,除了使用虚拟设备(VTd,IOMMU等)基本上,经典的方法外围设备在写入存储器时发出中断.在中断到来之前,CPU无法判断所有DMA字节是否已成功到达内存中的目标.

设备虚拟化允许以透明的方式从设备抽象物理地址,并且在尝试从存储器写入/读取时具有等效的页面错误.