Seperti yang saya posting di pembaruan dalam pertanyaan saya, masalah mendasarnya adalah jaringan zerocopy tidak berfungsi untuk memori yang telah dipetakan menggunakan remap_pfn_range()
(yang dma_mmap_coherent()
kebetulan juga digunakan di bawah tenda). Alasannya adalah jenis memori ini (dengan kode VM_PFNMAP
flag set) tidak memiliki metadata berupa struct page*
terkait dengan setiap halaman, yang dibutuhkannya.
Solusinya kemudian adalah mengalokasikan memori dengan cara struct page*
s adalah terkait dengan memori.
Alur kerja yang sekarang berfungsi bagi saya untuk mengalokasikan memori adalah:
- Gunakan
struct page* page = alloc_pages(GFP_USER, page_order);
untuk mengalokasikan blok memori fisik yang berdekatan, di mana jumlah halaman yang berdekatan yang akan dialokasikan diberikan oleh2**page_order
. - Pisahkan halaman orde tinggi/gabungan menjadi halaman orde 0 dengan memanggil
split_page(page, page_order);
. Ini sekarang berarti bahwastruct page* page
telah menjadi larik dengan2**page_order
entri.
Sekarang untuk mengirimkan wilayah tersebut ke DMA (untuk penerimaan data):
dma_addr = dma_map_page(dev, page, 0, length, DMA_FROM_DEVICE);
dma_desc = dmaengine_prep_slave_single(dma_chan, dma_addr, length, DMA_DEV_TO_MEM, 0);
dmaengine_submit(dma_desc);
Saat kami mendapat panggilan balik dari DMA bahwa transfer telah selesai, kami perlu membuka peta wilayah untuk mentransfer kepemilikan blok memori ini kembali ke CPU, yang menangani cache untuk memastikan kami tidak membaca data basi:
dma_unmap_page(dev, dma_addr, length, DMA_FROM_DEVICE);
Sekarang, ketika kita ingin mengimplementasikan mmap()
, yang harus kita lakukan hanyalah memanggil vm_insert_page()
berulang kali untuk semua halaman 0 pesanan yang telah kami alokasikan sebelumnya:
static int my_mmap(struct file *file, struct vm_area_struct *vma) {
int res;
...
for (i = 0; i < 2**page_order; ++i) {
if ((res = vm_insert_page(vma, vma->vm_start + i*PAGE_SIZE, &page[i])) < 0) {
break;
}
}
vma->vm_flags |= VM_LOCKED | VM_DONTCOPY | VM_DONTEXPAND | VM_DENYWRITE;
...
return res;
}
Saat file ditutup, jangan lupa untuk membebaskan halaman:
for (i = 0; i < 2**page_order; ++i) {
__free_page(&dev->shm[i].pages[i]);
}
Menerapkan mmap()
cara ini sekarang memungkinkan soket untuk menggunakan buffer ini untuk sendmsg()
dengan MSG_ZEROCOPY
bendera.
Meskipun ini berhasil, ada dua hal yang tidak cocok dengan saya dengan pendekatan ini:
- Anda hanya dapat mengalokasikan buffer berukuran power-of-2 dengan metode ini, meskipun Anda dapat mengimplementasikan logika untuk memanggil
alloc_pages
sebanyak yang dibutuhkan dengan urutan yang menurun untuk mendapatkan buffer ukuran apa pun yang terdiri dari sub-buffer dengan berbagai ukuran. Ini kemudian akan membutuhkan beberapa logika untuk mengikat buffer ini bersama-sama dimmap()
dan untuk DMA mereka dengan scatter-gather (sg
) panggilan daripadasingle
. split_page()
mengatakan dalam dokumentasinya:
* Note: this is probably too low level an operation for use in drivers.
* Please consult with lkml before using this in your driver.
Masalah-masalah ini akan mudah dipecahkan jika ada beberapa antarmuka di kernel untuk mengalokasikan jumlah halaman fisik yang bersebelahan secara sewenang-wenang. Saya tidak tahu mengapa tidak ada, tetapi menurut saya masalah di atas tidak begitu penting untuk menggali mengapa ini tidak tersedia / bagaimana menerapkannya :-)
Mungkin ini akan membantu Anda memahami mengapa halaman alokasi membutuhkan nomor halaman berkekuatan 2.
Untuk mengoptimalkan proses alokasi halaman (dan mengurangi fragmentasi eksternal), yang sering digunakan, kernel Linux mengembangkan cache halaman per-cpu dan pengalokasi sobat untuk mengalokasikan memori (ada pengalokasi lain, slab, untuk melayani alokasi memori yang lebih kecil dari halaman).
Cache halaman per-cpu melayani permintaan alokasi satu halaman, sedangkan buddy-allocator menyimpan 11 daftar, masing-masing berisi 2^{0-10} halaman fisik. Daftar ini bekerja dengan baik saat mengalokasikan dan membebaskan halaman, dan tentu saja, premisnya adalah Anda meminta buffer berukuran kekuatan 2.