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

时间:2014-12-22 15:29:55

标签: linux mmap memory-mapped-files dma

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

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

现在,我将非常感谢有关如何利用内存映射技术进行写作的类似伪/实际代码示例。

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);
        };
}

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去了真正的系统。相同的循环,而不是read()调用是等待DMA缓冲区准备好并提供其虚拟地址的调用。
没有错误,代码运行,但没有记录(!)。 我的想法是Linux没有看到该区域已更新,因此不会同步()一件事 为了测试这个,我在工作示例中删除了read()调用 - 是的,也没有记录任何内容。

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

非常感谢!!!

1 个答案:

答案 0 :(得分:0)

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

为此工作您需要能够控制p(放置新缓冲区的位置)或地址文件的地址。如果你没有 - 你必须复制内存内容(并且会失去mmap的一些好处)。

所以psudo代码是:

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 );

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

我认为它是32位系统,但即使这样,1GB映射文件的大小也可能太大(如果你的RAM较小,你将会交换)。

如果此设置可行,则下一步是循环以映射不同偏移量的文件区域,并取消已经填充的区域。

您很可能需要将addr与4KB边界对齐。

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

更新:

当您通过DMA填写mmap&#39; ed区域时到底发生了什么?我根本不知道(不确定如何检测到脏页:硬件做了什么,以及什么做了什么必须由软件完成。)

UPDATE2:据我所知:

DMA以下列方式工作:

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

这似乎很简单,而不涉及虚拟内存:DMA应独立于运行进程(CPU使用的实际VM表)工作。然而,由DMA物理RAM页面修改的CPU缓存无效应该是一些误解(不知道CPU是否需要做某事,或者它是由硬件自动完成的)。

mmap()以下列方式分叉:

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

理论上应该可以将DMA传输到mmaped范围,但是你需要找出,如何准确地将页面标记为 dirty (如果你需要做一些事情来通知内核哪些页面需要写入磁盘)。

UPDATE3:

即使被DMA修改了页面也没有标记为 dirty ,你应该能够通过重写(读取ant然后写入相同的)每个页面至少有一个值来标记标记(最有可能每个4KB)转移。只需确保编译器不会删除(优化)重写。

UPDATE4:

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

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

关于DMA传输到映射范围,我认为在DMA启动之前确保maped页面是preloalocated是必需的(所以真实内存分配给DMA和maped区域)。在Linux上有MAP_POPULATE mmap标志,但是从手动它接缝它只适用于MAP_PRIVATE映射(更改没有写入磁盘),因此很可能它是可用的。可能你必须通过访问每个maped页面来手动删除页面故障。这应该可以阅读结果文件。

如果你仍然希望一起使用mmap和DMA,但是避免读取结果文件,你必须修改内核内部以允许mmap使用O_WRONLY文件(例如通过零填充三角形页面,而不是从磁盘读取它们。