Zkusmo jsem
zagooglil ohledně Zero Copy Networking - ale tenhle termit je zjevně vyhrazen pro jednu oblast ve vyšších vrstvách, konkrétně pro kopírování mezi kernel space a user space, tzn. "baví se můj user space prográmek po síti a chtěl by rychle".
V našem případě se bavíme o soft-bridgi, což je v linuxu modul v kernelu, paket vůbec neopustí kernel. Čili dotaz zjevně zní: "kolikrát se sk_buff zkopíruje v RAMce 'zbytečně' mezi RX DMA a TX DMA?" Jasnou odpověď jsem nenašel a nemám čas ji hledat, kdyžtak se zkuste dál už potápět sám. Níže uvedu pár odkazů, odkud by snad šlo začít.
Google našel
blogpost obsahující pár výživných diagramů. Obrázek je za tisíc slov. Další dva doprovodné blogposty jsou vyčerpávající, čistě textové a popisují
RX a
TX řetězce - ideální čtení před usnutím, pokud máte problém usnout.
Některé podrobnosti osvětlují dvě hesla na Linux Foundation Wiki, ohledně
networking control flow v kernelu (hledejte nadpisy ohledně "Layer 2" nebo text "bridge") a ohledně
NAPI (hledejte text "DMA"). Pokud mi paměť slouží, NAPI infrastruktura byla před lety zavedena pro snížení frekvence interruptů od síťových rozhraní.
Pokud se týče vlastního kernelového modulu, který realizuje bridge, můžete se pokochat přímo
aktuálními zdrojáky. Je to hrst jednotlivých souborů. Můžete začít třeba od toho,
co modul dělá při natažení do kernelu (= kam se zavěšuje), nebo se podívejte na
funkce, realizující forwardování paketů... Ono k Vašemu dotazu tam na první pohled není vidět mnoho, protože skutečné "maso" je skryto za hradbou interního kernel networking API.
V textech popisujících linuxové síťoviny se často vyskytuje klíčové slovo SKB nebo struct sk_buff. Jedná se zjevně o "kus paměti obsahující jednotlivý paket". Pokud je to jen trochu možné, předávají si funkce navzájem jenom pointer (odkaz), celý buffer se pokud možno nekopíruje. Otázkou tedy je, kdy a z jaké oblasti paměti se sk_buff naalokuje a za jakých okolností se uvolní, jaká je jeho "životnost" / životní cyklus.
Zatím co jsem si tak občas přečetl v dokumentaci PCI zařízení, RX DMA funguje velmi zjednodušeně tak, že v PCI zařízení nakonfigurujete bázovou adresu + délku okna v hostitelské paměti, a v tomto okně se Vám následně vynořují data = příchozí pakety, jeden za druhým. Modernější síťovky (nejméně např. Realtek 8168, tzn. žádný výkřik raketové technologie) umí běžet podle scatter-gather seznamu, takže DMA okno nemusí být ani spojité. Tak či onak, to cílové DMA okno nebo sg-list je cyklické, používá se na způsob kruhového bufferu (ring buffer). Klíčový je fakt, že síťovka se v RX směru sama rozhoduje, kdy se do DMA pustí, a teprve až když je přenos hotový, pošle "vám" (driveru síťovky v hostitelském kernelu) interrupt, že nějaká data přišla. To znamená, že "Vaše" hlavní starost je, data z ring bufferu pokud možno rychle někam odklidit, protože síťovka na Vás dlouho čekat nebude (pokud Vás v kroužku "doběhne", tak nevyzvednuté pakety přepíše, nebo se zastaví a další přišedší pakety začne zahazovat). Proč to říkám: protože pokud byste si usmyslel, že odchozí pakety skrz TX DMA budete posílat přímo z "příchozího DMA ring bufferu", abyste ušetřil kopírování, bude Vám vyprazdňování tohoto "bufferu s dvojím využitím" záviset na chování odchozích směrů do jednotlivých TX portů. Jednak by se ten ring buffer uvolňoval "napřeskáčku", druhak tam jaksi není garance, že se konkrétní paket vůbec někdy uvolní. Musel byste nad tím běžet nějaký softwarový "garbage collector" který bude pakety přímo v ring bufferu timeoutovat. Ten ring buffer by musel být veliký a jak by se pakety vyřizovaly "napřeskáčku" tak by "řídnul", jasně měl byste nad ním další spojové seznamy / fronty / indexy apod., ale přesto by Vás základní struktura "spojový seznam" mohla začít omezovat i co do výkonu apod.
Taky stojí za zamyšlení, že jak RX DMA ring buffer tak fronty pro TX DMA tvoří jádra kritických sekcí. Předávají si nad nimi veslo hardware síťovky s kernelem. A páchat nějaký složitý management nad kritickou sekcí s DMA... no nevím.
Přijde mi docela sjízdné, při vybírání paketů z RX DMA ring bufferu rovnou provést forwardovací rozhodnutí (pro nás funkce handle_bridge() zmíněná v literatuře) a na základě zvoleného TX portu provést jednu kopii v RAMce do odpovídající TX fronty. Pokud má forwardovací rozhodnutí deterministickou a dostatečně omezenou dobu provedení, nemusel by to být problém, a možná to přesně takto funguje.
Ještě mám pocit, že "kopie z paměti do paměti" by mohl akcelerovat
Intel I/OAT DMA engine. Software jenom staví sg-list a o zbytek se nestará. Tahle fičurka se vyskytuje v "serverových" motherboardech (Xeon CPU) ale tuším jsem to jednou chtěl vyzkoušet a měl jsem dost smíšené výsledky. Na některém HW to nešlo zapnout, nebo to mělo v kernelu nějak omezenou použitelnost pro velmi málo oblastí/driverů apod. Ono i to heslo na wikipedii nad tím v závěru ohrnuje nos.
Ten link na PDF co poslal Honza je zajímavé čtení - ovšem týká se zjevně offloadu "těžšího kalibru", kdy za vhodných okolností je paket switchnut přímo externím switchem (který s linuxovým kernelem na nějaké úrovni symbioticky spolupracuje) tzn. na PCI DMA do+z hostitelské RAM vůbec nemusí dojít. Je zajímavé vědět, že taková jemně vrstvená infrastruktura pro externí offload v kernelu existuje, nicméně bych řekl, že takto postavený hardware je zajímavý hybrid: switch ASIC zjevně s PCI apod. rozhraním k hostitelskému CPU. Toto nevídám moc často. Mnohem běžněji vídám schéma, kdy soběstačná switchovací matice má jeden MAC port vůči hostitelskému management CPU = vlastně VLAN trunk v podobě [S|R|G]MII rozhraní včetně MDIO kanálu. V tom případě se nejedná ani tak o nějaký chytristický offload, jako spíš o externí switchovací matici, kterou standardní Linux na mngt CPU víceméně na dálku konfiguruje :-)