0

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

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

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

这告诉我,内核似乎以某种方式认为页面仍然在内核空间中“锁定”左右。

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

在那里我找到了我已经在我的驱动程序中尝试过的函数“put_page”,但它没有帮助,而且驱动程序然后卡住了。

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

这里是相关代码。顺序基本上是:get_user_pages -> dma_map_page -> kick off HW DMA (in our 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 缓冲区(未对齐内存)时的错误消息:

*** 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
4

1 回答 1

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 */
             }
   } 
于 2017-11-15T15:30:13.473 回答