Wie ich in einem Update in meiner Frage gepostet habe, besteht das zugrunde liegende Problem darin, dass das Zerocopy-Netzwerk nicht für Speicher funktioniert, der mit remap_pfn_range()
zugeordnet wurde (welche dma_mmap_coherent()
kommt auch unter der Motorhaube zum Einsatz). Der Grund dafür ist, dass diese Art von Speicher (mit dem VM_PFNMAP
Flag gesetzt) hat keine Metadaten in Form von struct page*
jeder Seite zugeordnet, die sie benötigt.
Die Lösung besteht dann darin, den Speicher so zuzuweisen, dass struct page*
s sind dem Speicher zugeordnet.
Der Workflow, der jetzt für mich funktioniert, um den Speicher zuzuweisen, ist:
- Verwenden Sie
struct page* page = alloc_pages(GFP_USER, page_order);
einen Block zusammenhängenden physikalischen Speichers zuzuweisen, wobei die Anzahl der zusammenhängenden Seiten, die zugewiesen werden, durch2**page_order
angegeben wird . - Teilen Sie die höherwertige/zusammengesetzte Seite in 0-wertige Seiten auf, indem Sie
split_page(page, page_order);
aufrufen . Das bedeutet jetzt, dassstruct page* page
ist ein Array mit2**page_order
geworden Einträge.
Um nun eine solche Region an den DMA zu übermitteln (für den Datenempfang):
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);
Wenn wir vom DMA einen Rückruf erhalten, dass die Übertragung abgeschlossen ist, müssen wir die Zuordnung der Region aufheben, um den Besitz dieses Speicherblocks zurück an die CPU zu übertragen, die sich um die Caches kümmert, um sicherzustellen, dass wir keine veralteten Daten lesen:
dma_unmap_page(dev, dma_addr, length, DMA_FROM_DEVICE);
Nun, wenn wir mmap()
implementieren wollen , alles, was wir wirklich tun müssen, ist vm_insert_page()
aufzurufen wiederholt für alle Seiten der 0-Reihenfolge, die wir vorab zugewiesen haben:
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;
}
Wenn die Datei geschlossen ist, vergessen Sie nicht, die Seiten freizugeben:
for (i = 0; i < 2**page_order; ++i) {
__free_page(&dev->shm[i].pages[i]);
}
Implementierung von mmap()
Auf diese Weise kann ein Socket diesen Puffer jetzt für sendmsg()
verwenden mit dem MSG_ZEROCOPY
Flagge.
Obwohl dies funktioniert, gibt es zwei Dinge, die mir bei diesem Ansatz nicht gefallen:
- Sie können mit dieser Methode nur Puffer in Potenz von 2 zuweisen, obwohl Sie Logik implementieren könnten, um
alloc_pages
aufzurufen so oft wie nötig mit abnehmenden Ordnungen, um einen Puffer beliebiger Größe zu erhalten, der aus Unterpuffern unterschiedlicher Größe besteht. Dies erfordert dann etwas Logik, um diese Puffer inmmap()
zusammenzubinden und sie mit Scatter-Gather (sg
) ruft anstelle vonsingle
auf . split_page()
sagt in seiner Dokumentation:
* Note: this is probably too low level an operation for use in drivers.
* Please consult with lkml before using this in your driver.
Diese Probleme wären leicht zu lösen, wenn es im Kernel eine Schnittstelle gäbe, um eine beliebige Menge zusammenhängender physischer Seiten zuzuweisen. Ich weiß nicht, warum es das nicht gibt, aber ich finde die oben genannten Probleme nicht so wichtig, dass ich nachforsche, warum dies nicht verfügbar ist / wie man es implementiert :-)
Vielleicht hilft Ihnen das zu verstehen, warum alloc_pages eine Power-of-2-Seitenzahl benötigt.
Um den Seitenzuweisungsprozess zu optimieren (und externe Fragmentierungen zu verringern), der häufig verwendet wird, hat der Linux-Kernel einen CPU-Seiten-Cache und einen Buddy-Allocator entwickelt, um Speicher zuzuweisen (es gibt einen weiteren Allocator, slab, um Speicherzuweisungen zu bedienen, die kleiner als a sind Seite).
Der Seiten-Cache pro CPU dient der Zuordnungsanforderung für eine Seite, während buddy-allocator 11 Listen führt, die jeweils 2^{0-10} physische Seiten enthalten. Diese Listen funktionieren gut beim Zuweisen und Freigeben von Seiten, und die Prämisse ist natürlich, dass Sie einen Puffer in Potenz von 2 anfordern.