将 DMA 缓冲区写入内存映射文件

leo*_*onp 5 linux mmap memory-mapped-files dma

我需要在嵌入式 Linux(2.6.37) 中尽可能快地将传入的 DMA 缓冲区写入到 HD 分区作为原始设备 /dev/sda1。缓冲区按要求对齐,长度相等,为 512KB。该过程可能会持续很长时间并填充多达例如 256GB 的数据。我需要使用内存映射文件技术(O_DIRECT 不适用),但无法理解如何执行此操作的确切方法。所以,在伪代码“正常”写作中:

fd=open(/dev/sda1",O_WRONLY);
while(1) {
    p = GetVirtualPointerToNewBuffer();
    if (InputStopped())
        break;
    write(fd, p, BLOCK512KB);
}
Run Code Online (Sandbox Code Playgroud)

现在,我将非常感谢如何利用内存映射技术编写本文的类似伪/真实代码示例。

UPDATE2:感谢 kestasx,最新的工作测试代码如下所示:

#define TSIZE   (64*KB)
void* TBuf;
int main(int argc, char **argv) {
    int fdi=open("input.dat", O_RDONLY);
    //int fdo=open("/dev/sdb2", O_RDWR);
    int fdo=open("output.dat", O_RDWR);
    int i, offs=0;
    void* addr;
        i = posix_memalign(&TBuf, TSIZE, TSIZE);
        if ((fdo < 1) || (fdi < 1)) {
            printf("Error in files\n");
            return -1; }
        while(1) {
            addr = mmap((void*)TBuf, TSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fdo, offs);
            if ((unsigned int)addr == 0xFFFFFFFFUL) {
                printf("Error MMAP=%d, %s\n", errno, strerror(errno));
                return -1; }
            i = read(fdi, TBuf, TSIZE);
            if (i != TSIZE) {
                printf("End of data\n");
                return 0; }
            i = munmap(addr, TSIZE);
            offs += TSIZE;
            sleep(1);
        };
}
Run Code Online (Sandbox Code Playgroud)

UPDATE3: 1. 为了精确模仿 DMA 工作,我需要在 mmp() 之前移动 read() 调用,因为当 DMA 完成时,它为我提供了放置数据的地址。所以,在伪代码中: while(1) { read(fdi, TBuf, TSIZE); addr = mmap((void*)TBuf, TSIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fdo, offs); munmap(addr, TSIZE); offs += TSIZE; }

此变体在(!)第一个循环之后失败 - read() 在 TBuf 上显示 BAD ADDRESS。在不完全理解我做什么的情况下,我用 msync() 替换了 munmap()。这工作得很好。
那么,这里的问题 - 为什么取消映射 addr 会影响 TBuf?

2.通过前面的示例工作,我使用 DMA 进入了真实系统。相同的循环,只是等待 DMA 缓冲区准备就绪并提供其虚拟地址,而不是 read() 调用。
没有错误,代码运行,但没有记录任何内容(!)。我的想法是 Linux 没有看到该区域已更新,因此不会同步()。
为了测试这一点,我在工作示例中消除了 read() 调用 - 是的,也没有记录任何内容。

所以,这里的问题 - 我如何告诉 Linux 映射区域包含新数据,请刷新它!

非常感谢!!!

kes*_*asx 1

mmap()如果我正确理解,如果您将文件(不确定是否可以mmap()原始分区/块设备)和通过 DMA 的数据直接写入此内存区域,这是有意义的。

为此,您需要能够控制p文件映射的位置(放置新缓冲区的位置)或地址。如果你不这样做 - 你将不得不复制内存内容(并且将失去 mmap 的一些好处)。

所以伪代码是:

truncate("data.bin", 256GB);
fd = open( "data.bin", O_RDWR );
p = GetVirtualPointerToNewBuffer();
adr = mmap( p, 1GB, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset_in_file );
startDMA();
waitDMAfinish();
munmap( adr, 1GB );
Run Code Online (Sandbox Code Playgroud)

这只是第一步,我不完全确定它是否适用于 DMA(没有这样的经验)。

我假设它是 32 位系统,但即使如此 1GB 映射文件大小也可能太大(如果您的 RAM 较小,您将进行交换)。

如果此设置有效,下一步将是循环映射不同偏移处的文件区域并取消映射已填充的区域。

您很可能需要对齐addr4KB 边界。

当您取消映射区域时,它的数据将同步到磁盘。因此,您需要进行一些测试来选择适当的映射区域大小(当下一个区域由 DMA 填充时,必须有足够的时间来取消映射/写入前一个区域)。

更新:

当您通过 DMA 填充映射区域时到底会发生什么我根本不知道(不确定如何检测脏页:什么是由硬件完成的,什么必须由软件完成)。

UPDATE2:据我所知:

DMA 的工作方式如下:

  • CPU安排DMA传输(将传输数据写入RAM的地址);
  • DMA控制器完成实际工作,而CPU可以并行地完成自己的工作;
  • 一旦 DMA 传输完成 - DMA 控制器通过 IRQ 线(中断)向 CPU 发出信号,以便 CPU 可以处理结果。

这看起来很简单,但不涉及虚拟内存:DMA 应该独立于正在运行的进程(CPU 使用的实际 VM 表)工作。然而,应该有某种机制使 DMA 物理 RAM 页面修改的 CPU 缓存无效(不知道 CPU 是否需要做某事,或者是由硬件自动完成的)。

mmap()按以下方式分叉:

  • 成功调用后mmap(),磁盘上的文件将附加到进程内存范围(很可能在操作系统内核中填充某些数据结构来保存此信息);
  • mmaped 范围内的 I/O(读取或写入)会触发pagefault,这是由内核从附加文件加载适当的块来处理的;
  • 对 mmaped 范围的写入由硬件处理(不知道具体如何:可能写入先前未修改的页面会触发一些故障,这是由内核将这些页面标记为脏来处理的;或者也许此标记完全在硬件中完成,并且此信息可用当需要将修改的页面刷新到磁盘时发送到内核)。
  • 修改()页面由操作系统写入磁盘(如果它认为合适)或者可以通过msync()或强制munmap()

理论上,应该可以对 mmaped 范围进行 DMA 传输,但是您需要找出页面如何标记为(如果您需要执行某些操作来通知内核哪些页面需要写入磁盘)。

更新3:

即使由 DMA 修改的页面未标记为dirty,您也应该能够通过重写(读取 ant 然后写入相同的内容)传输的每个页面(最有可能每个 4KB)中至少一个值来触发标记。只需确保编译器不会删除(优化)此重写即可。

更新4:

似乎打开的文件 O_WRONLY 无法进行 mmap(请参阅问题评论,我的实验也证实了这一点)。这是mmap()上述工作的逻辑结论。此处也确认了相同的情况(参考 POSIX 标准要求,以确保无论映射保护标志如何,文件都是可读的)。

除非有某种解决方法,否则实际上意味着通过使用mmap()您无法避免读取结果文件(在您的情况下是不必要的步骤)。

关于 DMA 传输到映射范围,我认为需要确保在 DMA 启动之前预分配映射页面(因此有分配给 DMA 和映射区域的实际内存)。在 Linux 上有 MAP_POPULATE mmap 标志,但从手册来看,它仅适用于 MAP_PRIVATE 映射(更改不会写入磁盘),因此很可能它是可用的。您可能必须通过访问每个映射页面来手动触发页面错误。这应该会触发结果文件的读取。

如果您仍然希望同时使用 mmap 和 DMA,但避免读取结果文件,则必须修改内核内部结构以允许 mmap 使用 O_WRONLY 文件(例如通过零填充触发页面,而不是从磁盘读取它们) 。