← Back to team overview

helenos-xhci team mailing list archive

Odložené zpracování událostí

 

Zdravím,

rád bych otevřel otázku, kterou jsme řešili osobně i na slacku,
aprobral ji i s někým kvalifikovanějším.

V současnosti řešíme problém, že zpracování událostí (např. port status
change event při připojení zařízení) má vyvolat sérii příkazů, kterým se
připojené zařízení inicializuje z pohledu HC. Na tyto příkazy je potřeba
čekat, protože v potvrzující zprávě od HC jsou data, která jsou nadále
potřeba pro další příkazy. Bohužel, tyto zprávy přichází do stejného
event ringu, jako samotný port status change event. Nelze tedy obsluhu
této události provádět z vlákna, ve kterém dochází k obsluze událostí
o dokončení příkazu.

Nabízí se tedy obsluhu události odložit, a zpracovat v jiném
threadu/fibrilu. Již je implementováno čekání na dokončení příkazu
uspáním fibrilu.

Možnosti jsou následující:

1) Vytvořit thread/fibril pool konstantní velikosti, který bude
   obsluhovat top-half události. Implementace jednoduchá, stačí k tomu
   nějaká fronta, do které zkopírujeme TRB o výsledku eventu. Nevýhodou
   je, že pokud hloubka čekání na jiný výsledek v součtu přesáhne
   velikost poolu, máme tu deadlock. Pokud využijeme thready, pak
   vyžaduje synchronizaci a tím úpravu existujících struktur, aby byly
   thread-safe. Odměnou však bude paralelismus ve zpracování událostí.

2) Kvůli každému typu eventu vytvořit vlákno, které obsluhuje tento
   kontrétní event. Náladu trochu kazí opět port status change, ve
   kterém je potřeba zrestartovat port, a tedy čekat na další port
   status change. Nicméně tuto situaci lze vyřešit rozdělením
   a zapamatováním stavu u portu. Nevýhoda je jasná, událostí je poměrně
   hodně. Lze se však omezit na "těžkotonážní" události, a tím počet
   vláken zredukovat. Vyžaduje synchronizaci.

3) Vytvořit fibril pro každou událost tohoto typu, tedy místo okamžitého
   řešení události vytvořit fibril, který ji obslouží. Události jsou
   označeny za vyřešené okamžitě, fibrily se pak budou postupně
   probouzet a blokovat na čekání. Tím se uvolní prostor pro obsluhu
   dalších událostí v původním fibrilu. Jednoduchá implementace. Můžeme
   si však dovolit vytvářet fibril při každé významnější události?
   Výhodou je, že toto řešení nevyžaduje synchronizaci, dokud se fibrily
   přeplánovávají pouze explicitně.

4) Inspirovat se tím, co dělá například Fuchsia, a v podstatě
   reimplementovat lightweight fibrily - mít frontu handlerů, které se
   postupně spouští, ověří podmínky pro běh, a pokud nejsou splněny,
   naplánují se na konec fronty. Fronta se spouští, když dojde k eventu.
   Nevýhodou je, že kód pro obsluhu událostí se musí rozdělit do fází,
   které se plánují do front. V zásadě se pak musí místo
   synchronizačních primitiv používat odplánování, a tím si primitiva
   implementovat pokaždé "ručně". Ale je to dobré řešení z hlediska výkonu.

5) Řešit konkrétně port status change event tím, že vytvoříme fibril pro
   každý obsluhovaný port. To významně usnadní psaní kódu pro tuto část
   roothubu - celý stavový automat pak lze napsat jako jednu funkci,
   která v nečinnosti blokuje, a stav si udržuje implicitně stavem
   fibrilu. Nevýhodou je však menší robustnost - možná nekonzistence
   stavu automatu a skutečného stavu portu. Pokud si stav zjišťujeme
   z HW při začátku obsluhy, máme větší šanci, že se tomuto vyhneme.

Přes snahu se mi nepodařilo vyluštit, jak tento problém řeší v Linuxu.
Ostatní kernely jsem zatím nestudoval.

Jaké jsou vaše názory, jak se se situací vypořádat?

Ondra


Follow ups