GNU/Linux >> Znalost Linux >  >> Linux

TCP odeslání nulové kopie uživatelského prostoru mapované paměti dma_mmap_coherent().

Jak jsem zveřejnil v aktualizaci v mé otázce, základním problémem je, že síť zerocopy nefunguje pro paměť, která byla namapována pomocí remap_pfn_range() (což dma_mmap_coherent() používá se také pod kapotou). Důvodem je, že tento typ paměti (s VM_PFNMAP flag set) nemá metadata ve tvaru struct page* přidružené ke každé stránce, kterou potřebuje.

Řešením je pak alokovat paměť způsobem struct page* s jsou spojené s pamětí.

Pracovní postup, který nyní funguje pro alokaci paměti, je:

  1. Použijte struct page* page = alloc_pages(GFP_USER, page_order); k alokaci bloku souvislé fyzické paměti, kde počet souvislých stránek, které budou přiděleny, je dán 2**page_order .
  2. Rozdělte stránku vyššího řádu/složené stránky na stránky nulového řádu voláním split_page(page, page_order); . To nyní znamená, že struct page* page se stalo polem s 2**page_order záznamy.

Nyní k odeslání takové oblasti do DMA (pro příjem dat):

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

Když dostaneme zpětné volání z DMA, že přenos skončil, musíme zrušit mapování regionu, abychom převedli vlastnictví tohoto bloku paměti zpět na CPU, které se stará o mezipaměti, aby se ujistil, že nečteme zastaralá data:

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

Nyní, když chceme implementovat mmap() , vše, co opravdu musíme udělat, je zavolat vm_insert_page() opakovaně pro všechny stránky s nulovou objednávkou, které jsme předem přidělili:

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

Po zavření souboru nezapomeňte uvolnit stránky:

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

Implementace mmap() tímto způsobem nyní umožňuje soketu používat tuto vyrovnávací paměť pro sendmsg() s MSG_ZEROCOPY vlajka.

Ačkoli to funguje, jsou dvě věci, které mi s tímto přístupem nesedí:

  • Touto metodou můžete přidělit pouze vyrovnávací paměti o velikosti 2, ačkoli můžete implementovat logiku pro volání alloc_pages tolikrát, kolikrát je potřeba, s klesajícími objednávkami, abyste získali libovolnou velikost vyrovnávací paměti složené z dílčích vyrovnávacích pamětí různých velikostí. To pak bude vyžadovat nějakou logiku, aby se tyto vyrovnávací paměti spojily dohromady v mmap() a k jejich DMA pomocí scatter-gather (sg ) spíše než single .
  • split_page() ve své dokumentaci říká:
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.

Tyto problémy by se daly snadno vyřešit, kdyby v jádře existovalo nějaké rozhraní pro alokaci libovolného množství souvislých fyzických stránek. Nevím, proč tomu tak není, ale výše uvedené problémy nepovažuji za tak důležité, abych se zabýval tím, proč to není k dispozici / jak to implementovat :-)


Možná vám to pomůže pochopit, proč alloc_pages vyžaduje číslo stránky s mocninou 2.

Pro optimalizaci procesu alokace stránek (a snížení externí fragmentace), která se často vyskytuje, vyvinulo linuxové jádro mezipaměť stránek pro jednotlivé procesory a alokátor kamarádů pro alokaci paměti (existuje další alokátor, slab, který slouží k alokaci paměti, která je menší než stránka).

Mezipaměť stránek na procesor obsluhuje jednostránkový požadavek na přidělení, zatímco buddy-allocator uchovává 11 seznamů, z nichž každý obsahuje 2^{0-10} fyzických stránek. Tyto seznamy fungují dobře, když alokujete a uvolníte stránky, a samozřejmě předpokladem je, že požadujete vyrovnávací paměť o síle 2.


Linux
  1. Co je ioremap()

  2. Ovladač zařízení linuxového jádra na DMA ze zařízení do paměti uživatelského prostoru

  3. Účinek SO_SNDBUF

  1. Rychlejší způsob, jak přesunout stránku paměti než mremap()?

  2. Jak přidělit paměť, která je zarovnána podle velikosti stránky?

  3. Jenkins aktivní (opuštěno)

  1. Linuxová neaktivní paměť

  2. Proč mají regiony mapované v paměti pouze pro čtení špinavé stránky?

  3. Proč jsou < nebo > vyžadovány pro použití /dev/tcp