访问内存映射 I/O 很慢

Kar*_*hik 6 io arm virtual-memory

我有一个 Terasic-SoCKIT(fpga 和 arm cortex a9),我在 HPS 上运行 Linux。我正在尝试访问内存映射 I/O,编写了一个带有函数“request_mem_region”和“ioremap”的简单字符驱动程序。

内存映射 IO 是 AXI 总线,我可以使用它向 FPGA 传输数据。我看到每次写入大约需要 6us,对于我的应用程序,我需要它小于 1us。此外,驱动程序在几次写入后停止写入映射的 IO(看不到 fpga 中的数据正在更改;驱动程序中的缓冲区是否已满??)。

问题是我是否遗漏了什么,或者因为写入是从虚拟地址到物理地址发生的,所以它不能再快了?如果从虚拟地址写入速度变慢,有没有办法加快速度?我知道,ARM 有一个 DMAC,但我还没有探索它。

谢谢你,卡西克

对不起,我没说我正在测量用户空间代码中的时间。后来查了一下驱动写入的时间,是纳秒级。所以,我认为大部分时间是从用户空间写入内核。

所以,我做了一些进一步的阅读并了解到 ioremap() 将物理地址映射到内核虚拟地址,而 remap_pfn_range() 将物理地址/IO 内存映射到用户虚拟空间(这就是我需要的;从用户写入 IO 内存空间)。我使用了简单的 mmap 示例 - http://web.cecs.pdx.edu/~jrb/ui/linux/examples.dir/simple/simple.c作为内核驱动程序。以下代码是我的用户空间代码:

    using namespace std;
    #include <iostream>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <ctime>


    #define PAGE_SIZE 4096
    #define HPS2FPGA_BRIDGE_BASE 0xc0000000
    #define BLINK_OFFSET 0x0

    volatile unsigned char *blink_mem;
    void *bridge_map;

    int main()
    {
        int fd, ret = EXIT_FAILURE;
        unsigned int i;
        unsigned char value;
        int dummy;
        off_t blink_base = HPS2FPGA_BRIDGE_BASE;
        clock_t start, stop;
        double duration;

        /* open the memory device file */
        fd = open("/dev/HPS2FPGA", O_RDWR|O_SYNC);
        if (fd < 0) {
            perror("open");
            exit(EXIT_FAILURE);
        }

        /* map the LWHPS2FPGA bridge into process memory */
        bridge_map = mmap(NULL, PAGE_SIZE, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_SHARED,
                    fd, blink_base);
        if (bridge_map == MAP_FAILED) {
            perror("mmap");
            goto cleanup;
        }


        /* get the delay_ctrl peripheral's base address */
        blink_mem = (unsigned char *) (bridge_map + BLINK_OFFSET);

        start = clock();
        /* write the value */
        for(i = 0; i < 1000000; i++)
        {
            *blink_mem = i;
            dummy = *blink_mem;
        }
        stop = clock();
        duration = ( stop - start ) / (double) CLOCKS_PER_SEC;
        printf("%f", duration);

        if (munmap(bridge_map, PAGE_SIZE) < 0) {
            perror("munmap");
            goto cleanup;
        }

        ret = 0;

    cleanup:
        close(fd);
        return ret;
    }
Run Code Online (Sandbox Code Playgroud)

我正在写入虚拟地址空间 mmap 返回,我可以通过读取该地址的值来验证写入,但我没有看到该值在 FPGA 中得到更新。

当我写入用户虚拟空间时,物理地址如何写入?有没有办法调试并查看物理地址空间是否真正被写入?

Mur*_*sen 3

好吧,看起来这个问题的主题是一个毛皮......内存映射 I/O(正确完成)将与处理器为正在访问的硬件执行此操作一样快,并且执行此操作不会产生任何开销用户模式与内核模式相反(即没有“从用户空间写入内核”)。

但是,您仍然必须考虑当您对地址进行读取或写入时会发生什么(这就是问题所在)。在大多数架构中,有两种映射 - 虚拟到物理,以及物理到设备。第一个是在虚拟内存硬件中设置的,第二个是在内存控制器中设置的。

除了映射之外,所有访问通常都通过缓存硬件进行,因此您必须决定是否要缓存访问。如果正在访问的底层设备是某种 RAM,那么您通常希望缓存访问。对于其他类型的设备,通常不需要。

可能还有很多其他事情需要考虑(例如,VM 映射是否驻留在 VM 硬件中、访问的宽度和时间、优先级、权限等),但缓存是第一位的。

在@Karthik的情况下,因为他没有在映射中关闭缓存,所以根据缓存的类型,当他写入地址(直写)时,整个缓存正在被写入,或者写入被延迟(回写)(如果您想了解有关缓存的一些细节,请尝试这个)。

为了回答特定的(后续)问题,一旦虚拟地址映射完成并且缓存完成其工作,访问就会进入内存控制器 - 该硬件决定正在访问哪个总线和/或设备并执行“正确的操作”。该硬件的“东西”,通常涉及断言芯片选择和/或写使能信号,可能将部分或全部物理地址复制到地址线上,可能是一些设置时序等。

...调试这些东西的最佳方法是将某种分析器连接到您的设备或总线,或者如果这太困难/昂贵,则内存控制器中可能有一些调试支持。

blink_mem另一个次要但重要的一点...请注意上面代码中的声明-易失性类型限定符非常重要。它告诉编译器不要破坏对地址的访问。除此之外,您应该了解与内存访问有关的任何特殊管道指令(查看powerpc 中的eieio指令 - 有人有幽默感:-)

最后,只是为了重申评论中所说的内容,这实际上是问题的真正答案,当调用remap_pfn_range()时,您可以通过使用以下命令修改最后一个参数(prot)中指定的页面保护来关闭缓存pgprot_noncached()宏。另请阅读,特别是。干杯!