GNU/Linux >> Linux Esercitazione >  >> Linux

Invio TCP in spazio utente a copia zero della memoria mappata dma_mmap_coherent()

Come ho pubblicato in un aggiornamento nella mia domanda, il problema di fondo è che la rete zerocopy non funziona per la memoria che è stata mappata utilizzando remap_pfn_range() (che dma_mmap_coherent() capita di usare anche sotto il cofano). Il motivo è che questo tipo di memoria (con il VM_PFNMAP flag set) non ha metadati sotto forma di struct page* associato a ciascuna pagina, di cui ha bisogno.

La soluzione quindi è allocare la memoria in un modo che struct page* s sono associato alla memoria.

Il flusso di lavoro che ora funziona per me per allocare la memoria è:

  1. Usa struct page* page = alloc_pages(GFP_USER, page_order); per allocare un blocco di memoria fisica contigua, dove il numero di pagine contigue che verranno allocate è dato da 2**page_order .
  2. Dividi la pagina di ordine elevato/composta in pagine di ordine 0 chiamando split_page(page, page_order); . Questo ora significa che struct page* page è diventato un array con 2**page_order voci.

Ora per inviare tale regione al DMA (per la ricezione dei dati):

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

Quando riceviamo una richiamata dal DMA che il trasferimento è terminato, dobbiamo annullare la mappatura della regione per trasferire la proprietà di questo blocco di memoria alla CPU, che si occupa delle cache per assicurarsi che non stiamo leggendo dati obsoleti:

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

Ora, quando vogliamo implementare mmap() , tutto quello che dobbiamo fare è chiamare vm_insert_page() ripetutamente per tutte le pagine di ordine 0 che abbiamo preassegnato:

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

Quando il file è chiuso, non dimenticare di liberare le pagine:

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

Implementazione di mmap() in questo modo ora consente a un socket di utilizzare questo buffer per sendmsg() con il MSG_ZEROCOPY bandiera.

Anche se funziona, ci sono due cose che non mi stanno bene con questo approccio:

  • Puoi allocare solo buffer di dimensioni power-of-2 con questo metodo, sebbene tu possa implementare la logica per chiamare alloc_pages tutte le volte che è necessario con ordini decrescenti per ottenere un buffer di qualsiasi dimensione composto da sub-buffer di varie dimensioni. Ciò richiederà quindi una logica per legare insieme questi buffer nel mmap() e a DMA loro con scatter-gather (sg ) chiama invece di single .
  • split_page() dice nella sua documentazione:
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.

Questi problemi sarebbero facilmente risolti se ci fosse un'interfaccia nel kernel per allocare una quantità arbitraria di pagine fisiche contigue. Non so perché non ci sia, ma non trovo i problemi di cui sopra così importanti da andare a scavare nel motivo per cui non è disponibile / come implementarlo :-)


Forse questo ti aiuterà a capire perché alloc_pages richiede un numero di pagina con potenza di 2.

Per ottimizzare il processo di allocazione della pagina (e diminuire le frammentazioni esterne), che è frequentemente impegnato, il kernel Linux ha sviluppato cache di pagina per CPU e buddy-allocator per allocare memoria (c'è un altro allocatore, slab, per servire allocazioni di memoria che sono più piccole di un pagina).

La cache di pagina per CPU serve la richiesta di allocazione di una pagina, mentre buddy-allocator conserva 11 elenchi, ciascuno contenente rispettivamente 2^{0-10} pagine fisiche. Questi elenchi funzionano bene quando allocano e liberano pagine e, naturalmente, la premessa è che stai richiedendo un buffer di dimensioni power-of-2.


Linux
  1. Grep:Memoria esaurita?

  2. Inviare un fax tramite Sip?

  3. Limite di memoria PHP

  4. Cos'è ioremap()

  5. Qual è la differenza tra la scrittura su un file e una memoria mappata?

Che cos'è la NVM (memoria non volatile)?

Gestione della memoria Linux:memoria virtuale e paginazione della domanda

Driver del dispositivo del kernel Linux a DMA da un dispositivo nella memoria dello spazio utente

Effetto di SO_SNDBUF

Come allocare la memoria che è allineata alle dimensioni della pagina?

Perché sono necessari < o > per usare /dev/tcp