GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Zero-Copy User-Space TCP-Send von dma_mmap_coherent() gemapptem Speicher

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:

  1. 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, durch 2**page_order angegeben wird .
  2. Teilen Sie die höherwertige/zusammengesetzte Seite in 0-wertige Seiten auf, indem Sie split_page(page, page_order); aufrufen . Das bedeutet jetzt, dass struct page* page ist ein Array mit 2**page_order geworden Einträge.

Um nun eine solche Region an den DMA zu übermitteln (für den Datenempfang):

  1. dma_addr = dma_map_page(dev, page, 0, length, DMA_FROM_DEVICE);
  2. dma_desc = dmaengine_prep_slave_single(dma_chan, dma_addr, length, DMA_DEV_TO_MEM, 0);
  3. 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:

  1. 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 in mmap() zusammenzubinden und sie mit Scatter-Gather (sg ) ruft anstelle von single 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.


Linux
  1. Linux Memory Management – ​​Swapping, Caches und Shared VM

  2. Was ist ioremap()

  3. Was ist der Unterschied zwischen dem Schreiben in eine Datei und einem zugeordneten Speicher?

  4. Wie wird Speicher zugewiesen, der auf die Seitengröße ausgerichtet ist?

  5. Jenkins aktiv (beendet)

Linux-Speichernutzung

Was ist NVM (nicht flüchtiger Speicher)?

Fax per Sip versenden?

Linux-Speicherverwaltung – Virtueller Speicher und Demand Paging

mmap:wird die zugeordnete Datei sofort in den Speicher geladen?

Inaktiver Linux-Speicher