free():仅在zerocopy DMA驱动程序访问后指针无效

时间:2017-10-05 09:47:58

标签: linux-kernel dma userspace

我有一个驱动程序,它将DMA传输到用户空间应用程序中malloc的内存,然后传递给内核(get_user_pages ...)。

问题:调用free()时出现“无效指针”消息。我打印两个指针值,它们是相等的,没有改变。当我使用posix_memalign()而不是malloc()时,不会发生错误。在posix_memalign()的情况下,我总是得到页面对齐的指针(0x .... 000)。使用malloc(),它通常是未对齐的地址。

我已经尝试完全跳过Driver DMA传输调用,基本上只是在应用程序中执行malloc()和free() - 然后它总是有效!

这告诉我内核似乎认为页面仍然在内核空间中“锁定”了。

我已经通过一些驱动程序挖掘,特别是drivers / media / pci / ivtv / ivtv-udma.c和ivtv-yuv.c看起来很有趣,因为它们看起来完全一样。

在那里我找到了我在驱动程序中尝试过的“put_page”功能,但它没有帮助,更多的是,驱动程序卡住了。

有人能指出我正确的方向或信息来源如何正确处理内核空间中的此类用户页面?

这里是相关代码。 订单基本上是: get_user_pages - > dma_map_page - >启动HW DMA(在我们的FPGA中) - > dma_unmap_page - > SetPageDirty - > put_page

非对齐地址仍会出现与以下相同的错误。

perform_user_dma_func(..)
{
[...]

    rv = get_user_pages( current, current->mm, uaddr, nr_pages, (direction == DMA_FROM_DEVICE), 0, pages, NULL);
    if( rv < nr_pages )
        goto CLEANUP;

    /*--- build scatter/gather list ---*/
    offset = uaddr & ~PAGE_MASK;
#ifdef VME4L_DBG_DMA_DATA
    initOffs = offset;
#endif

    for ( i = 0; i < nr_pages; ++i, sgList++ ) {
        struct page *page = pages[i];
        sgList->dmaLength = PAGE_SIZE - offset;
        dmaAddr = dma_map_page( pDev, page, 0x0, PAGE_SIZE, direction );
        if ( dma_mapping_error( pDev, dmaAddr ) ) {
            printk( KERN_ERR "error mapping DMA space with dma_map_page\n" );
            goto CLEANUP;
        } else {
            sgList->dmaDataAddress = dmaAddr + offset; /* Add offset between page begin and payload data, often > 0 */
            sgList->dmaPageAddress = dmaAddr; /* store page address for later dma_unmap_page */
        }

        if( totlen + sgList->dmaLength > count )
            sgList->dmaLength = count - totlen;

        VME4LDBG(" sglist %d: pageAddr=%p off=0x%lx dmaAddr=%p length=0x%x\n", i, page_address(page), offset, dmaAddr, sgList->dmaLength);
        totlen += sgList->dmaLength;
        offset = 0;
    }

    /*--- now do DMA in HW (device touches memory) ---*/
    rv = vme4l_perform_zc_dma( spc, sgListStart, nr_pages, blk->direction, blk->vmeAddr, swapMode );

CLEANUP:

    /*--- free pages ---*/
    sgList = sgListStart;
    for (i = 0; i < nr_pages; i++, sgList++) {
        dma_unmap_page( pDev, sgList->dmaPageAddress, PAGE_SIZE, direction );
    }

    /* mark pages as dirty */
    if( blk->direction == READ ) {
        for (i = 0; i < nr_pages; i++ ) {
            if ( !PageReserved( pages[i] ))
                SetPageDirty( pages[i] );
        }
    }

    sgList = sgListStart;
    for (i = 0; i < nr_pages; i++, sgList++) {
        /* __free_page( pages[i] ); */
        put_page( pages[i] );
    }
[...]
}

usimg malloc buffer(未对齐的内存)时的错误消息:

*** Error in `vme4l_rwex': free(): invalid pointer: 0x00000000008e4010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fe0a34367e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fe0a343f37a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe0a344353c]
vme4l_rwex[0x4010f8]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe0a33df830]
vme4l_rwex[0x401419]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:15 931122                             /usr/local/bin/vme4l_rwex
00601000-00602000 r--p 00001000 08:15 931122                             /usr/local/bin/vme4l_rwex
00602000-00603000 rw-p 00002000 08:15 931122                             /usr/local/bin/vme4l_rwex
008e4000-00906000 rw-p 00000000 00:00 0                                  [heap]
7fe09c000000-7fe09c021000 rw-p 00000000 00:00 0
7fe09c021000-7fe0a0000000 ---p 00000000 00:00 0
7fe0a31a9000-7fe0a31bf000 r-xp 00000000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a31bf000-7fe0a33be000 ---p 00016000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a33be000-7fe0a33bf000 rw-p 00015000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a33bf000-7fe0a357f000 r-xp 00000000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a357f000-7fe0a377f000 ---p 001c0000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a377f000-7fe0a3783000 r--p 001c0000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a3783000-7fe0a3785000 rw-p 001c4000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a3785000-7fe0a3789000 rw-p 00000000 00:00 0
7fe0a3789000-7fe0a378b000 r-xp 00000000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a378b000-7fe0a398a000 ---p 00002000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398a000-7fe0a398b000 r--p 00001000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398b000-7fe0a398c000 rw-p 00002000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398c000-7fe0a398e000 r-xp 00000000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a398e000-7fe0a3b8e000 ---p 00002000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b8e000-7fe0a3b8f000 r--p 00002000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b8f000-7fe0a3b90000 rw-p 00003000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b90000-7fe0a3bb6000 r-xp 00000000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3da6000-7fe0a3da9000 rw-p 00000000 00:00 0
7fe0a3db2000-7fe0a3db5000 rw-p 00000000 00:00 0
7fe0a3db5000-7fe0a3db6000 r--p 00025000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3db6000-7fe0a3db7000 rw-p 00026000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3db7000-7fe0a3db8000 rw-p 00000000 00:00 0
7ffd66125000-7ffd66146000 rw-p 00000000 00:00 0                          [stack]
7ffd661b4000-7ffd661b6000 r--p 00000000 00:00 0                          [vvar]
7ffd661b6000-7ffd661b8000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted

1 个答案:

答案 0 :(得分:0)

自己解决了。

结果表明,使用dma_map_page()进行映射可以使整个传输数据在RAM中正确显示,但该页面中的所有其他数据都被覆盖(包括在传输数据之前和之后的malloc指针管理数据,这导致到libc错误提示),缓存某种程度上没有像预期的那样工作。在dma_unmap_page()上,只有每次调用函数时数据才可见。

使用dma_map_single()看起来更好。我做get_user_pages_fast(),然后dma_map_single()页面虚拟地址加偏移 - 工作正常,内核中的DMA调试功能不会抱怨。请参阅下面的代码。

    for (i = 0; i < nr_pages; ++i, sgList++) {
            struct page *page = pages[i];
            sgList->dmaLength  = PAGE_SIZE - offset;
            pVirtAddr = ((unsigned char*)(page_address( page ))) + offset;

            if( totlen + sgList->dmaLength > count )
                sgList->dmaLength = count - totlen;

            dmaAddr = dma_map_single( pDev, pVirtAddr, sgList->dmaLength, direction );
            if ( dma_mapping_error( pDev, dmaAddr ) ) {
                   printk( KERN_ERR "*** error mapping DMA space!\n" );
                   goto CLEANUP;
            } else {
                   sgList->dmaAddress = dmaAddr;
            }

            VME4LDBG(" sglist %d: pageAddr=%p off=0x%04lx dmaAddr=%p length=0x%04x\n", i, page_address(page), offset, dmaAddr, sgList->dmaLength);
            totlen += sgList->dmaLength;
            offset = 0;
       }

       /*--- now do DMA in HW (device touches memory) ---*/
       rv = vme4l_perform_zc_dma( spc, sgListStart, nr_pages, blk->direction, blk->vmeAddr, swapMode );

CLEANUP:
       /*--- free pages ---*/
       if( locked ) {
             sgList = sgListStart;
             for (i = 0; i < nr_pages; i++, sgList++) {
                 dma_unmap_single( pDev, sgList->dmaAddress, sgList->dmaLength , direction );
                 put_page( pages[i] ); /* release pages locked with get_user_pages */
             }
   }