free(): неверный указатель только после обращения к драйверу ZMA
У меня есть драйвер, который выполняет передачу DMA в память, которая malloc'ed в приложении пространства пользователя, а затем передается в ядро (get_user_pages...).
Проблема: я получаю сообщение "неверный указатель" при вызове free(). Я печатаю оба значения указателя, они равны, а не изменены. Ошибка НЕ происходит, когда я использую posix_memalign() вместо malloc(). В случае posix_memalign() я всегда получаю указатели с выравниванием страницы ( 0x....000). С malloc () его часто выровненные адреса.
Я уже пытался полностью пропустить перевод драйвера DMA полностью, в основном просто делая malloc () и free () в приложении - тогда он всегда работает!
Это говорит мне о том, что ядро почему-то думает, что страницы все еще "заблокированы" или около того в пространстве ядра.
Я копался уже через некоторые драйверы, особенно драйверы / 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] );
}
[...]
}
Сообщение об ошибке при использовании буфера 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
1 ответ
Решил это сам.
оказалось, что сопоставление с помощью dma_map_page() делает все переданные данные видимыми в ОЗУ правильно, но все остальные данные на этой странице перезаписываются (включая данные управления указателями 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 */
}
}