GNU/Linux >> Tutoriels Linux >  >> Linux

Envoi TCP zéro copie dans l'espace utilisateur de la mémoire mappée dma_mmap_coherent()

Comme je l'ai posté dans une mise à jour de ma question, le problème sous-jacent est que la mise en réseau zerocopy ne fonctionne pas pour la mémoire qui a été mappée à l'aide de remap_pfn_range() (dont dma_mmap_coherent() arrive à utiliser sous le capot aussi). La raison est que ce type de mémoire (avec le VM_PFNMAP flag set) n'a pas de métadonnées sous la forme struct page* associé à chaque page, dont il a besoin.

La solution est alors d'allouer la mémoire d'une manière qui struct page* s sont associé à la mémoire.

Le flux de travail qui fonctionne maintenant pour moi pour allouer la mémoire est :

  1. Utilisez struct page* page = alloc_pages(GFP_USER, page_order); pour allouer un bloc de mémoire physique contigu, où le nombre de pages contiguës qui seront allouées est donné par 2**page_order .
  2. Divisez la page d'ordre supérieur/composée en pages d'ordre 0 en appelant split_page(page, page_order); . Cela signifie maintenant que struct page* page est devenu un tableau avec 2**page_order entrées.

Maintenant, pour soumettre une telle région au DMA (pour la réception des données) :

  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);

Lorsque nous recevons un rappel du DMA indiquant que le transfert est terminé, nous devons démapper la région pour transférer la propriété de ce bloc de mémoire au CPU, qui s'occupe des caches pour s'assurer que nous ne lisons pas de données obsolètes :

  1. dma_unmap_page(dev, dma_addr, length, DMA_FROM_DEVICE);

Maintenant, quand nous voulons implémenter mmap() , tout ce que nous avons vraiment à faire est d'appeler vm_insert_page() à plusieurs reprises pour toutes les pages de commande 0 que nous avons pré-attribuées :

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;
}

Lorsque le dossier est fermé, n'oubliez pas de libérer les pages :

for (i = 0; i < 2**page_order; ++i) {
    __free_page(&dev->shm[i].pages[i]);
}

Implémenter mmap() cette méthode permet maintenant à une socket d'utiliser ce tampon pour sendmsg() avec le MSG_ZEROCOPY drapeau.

Bien que cela fonctionne, il y a deux choses qui ne me conviennent pas avec cette approche :

  • Vous ne pouvez allouer que des tampons de puissance 2 avec cette méthode, bien que vous puissiez implémenter une logique pour appeler alloc_pages autant de fois que nécessaire avec des ordres décroissants pour obtenir un tampon de n'importe quelle taille composé de sous-tampons de tailles variables. Cela nécessitera alors une certaine logique pour lier ces tampons ensemble dans le mmap() et de les DMA avec scatter-gather (sg ) appelle plutôt que single .
  • split_page() dit dans sa documentation :
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.

Ces problèmes seraient facilement résolus s'il y avait une interface dans le noyau pour allouer un nombre arbitraire de pages physiques contiguës. Je ne sais pas pourquoi il n'y en a pas, mais je ne trouve pas les problèmes ci-dessus si importants pour aller chercher pourquoi ce n'est pas disponible / comment l'implémenter :-)


Cela vous aidera peut-être à comprendre pourquoi alloc_pages nécessite un numéro de page puissance de 2.

Pour optimiser le processus d'allocation de page (et réduire les fragmentations externes), qui est fréquemment engagé, le noyau Linux a développé un cache de page par processeur et un répartiteur de contacts pour allouer de la mémoire (il existe un autre répartiteur, slab, pour servir les allocations de mémoire qui sont plus petites qu'un page).

Le cache de page par processeur sert la demande d'allocation d'une page, tandis que buddy-allocator conserve 11 listes, chacune contenant respectivement 2^{0-10} pages physiques. Ces listes fonctionnent bien lorsqu'elles allouent et libèrent des pages, et bien sûr, le principe est que vous demandez un tampon de puissance de 2.


Linux
  1. Utilisation de la mémoire Linux

  2. Grep :Mémoire épuisée ?

  3. Qu'est-ce que ioremap()

  4. Pilote de périphérique du noyau Linux vers DMA à partir d'un périphérique dans la mémoire de l'espace utilisateur

  5. Effet de SO_SNDBUF

Qu'est-ce que la NVM (mémoire non volatile) ?

Envoyer un fax via SIP ?

Limite de mémoire PHP

Gestion de la mémoire Linux - Mémoire virtuelle et pagination à la demande

Mémoire inactive Linux

Pourquoi les régions mappées en mémoire en lecture seule ont des pages sales ?